Sports Inventory Reliability and Troubleshooting
Sports Inventory ingestion is asynchronous. Build the client around acceptance, safe retries, deterministic entity keys, and useful diagnostics.
What 202 Accepted means
The API validates the request and accepts the whole JSON array for asynchronous processing. A successful default response is:
HTTP/1.1 202 Accepted
Content-Type: application/json{
"success": true
}This confirms acceptance only. It does not confirm that:
- every entity is already visible to downstream Optimove Personalize experiences;
- all hierarchy references point to existing parents;
- a PUT matched an existing row;
- a DELETE matched an existing row;
- downstream processing has completed.
There is currently no public endpoint for polling processing status.
Inspect the body of every 202
An acceptance failure can be represented as:
{
"success": false
}Treat 202 plus success: false as retryable. Do not rely on the HTTP status alone.
Get an acceptance reference
Send this fixed protocol media type:
Accept: application/vnd.optimove.optix.inventory.records.v2+jsonThe response adds an acceptance reference for each request:
{
"success": true,
"records": [
"rcpt-3f9a2c7e14b8"
]
}The full request array is accepted as a single unit, so the reference represents the whole HTTP batch rather than an individual Category, Event, or Selection. Treat it as an opaque string: retain it for support diagnostics, but do not parse or depend on its format, which may change.
Use a returned reference as an acceptance confirmation only when success is true. If acceptance fails, the response can contain "unknown" instead of a reference. Keep the complete response and retry the whole batch.
Persist these values with:
- UTC request timestamp;
- HTTP method;
- brand key, masked where required by your security policy;
- entity count and entity types;
- a client-generated correlation ID in your own logs;
- HTTP status and parsed response body.
Do not put an unsupported correlation field into the Sports Inventory JSON. Keep it in your own request log.
Retry policy
Use bounded exponential backoff with jitter. Honor Retry-After when it is present.
| Outcome | Retry? | Action |
|---|---|---|
202 with success: true | No | Record acceptance and continue |
202 with success: false | Yes | Retry the same batch |
| 400 validation error | No | Correct the request body, headers, or field values |
| 401 or 403 | No, unless credentials were refreshed | Check the API key, brand key, and Sports Inventory entitlement |
| 429 | Yes | Honor Retry-After; back off with jitter |
| 5xx | Yes | Retry with bounded exponential backoff and jitter |
| Connection failure or timeout before a response | Yes, cautiously | Outcome is ambiguous; retry the identical batch and consider overwrite semantics |
Do not retry forever. After the client’s retry budget is exhausted, preserve the payload and diagnostics for controlled replay or support investigation.
Why ambiguous retries need care
There is no public idempotency-key header.
- POST is a full-row upsert. A retry can replace a row and clear optional fields omitted from the retried payload.
- PUT is a partial top-level update. Repeating the same update normally converges, but a late retry can overwrite a newer value.
- DELETE of an already absent key is normally a no-op.
- Across concurrent requests, global ordering is not guaranteed.
Use deterministic keys, send complete POST records, serialize dependent changes, and avoid concurrent writes to the same entity.
Validation error bodies
Validation errors can appear in either of these shapes, depending on where validation failed:
{
"error": {
"message": "Bad Request",
"errors": "Validation details"
}
}{
"message": "Bad Request",
"errors": "Validation details"
}Your error parser should tolerate both while treating the contents as diagnostic text, not a stable machine-readable error-code taxonomy.
Troubleshooting table
| Symptom | Likely cause | What to check |
|---|---|---|
| 400 Bad Request | Invalid JSON, missing required field, wrong enum case, invalid date-time, invalid Selection pricing state, POST/PUT batch outside 1–500, or X-Expires-After below 60 | Validate against the Sports Inventory API Reference (field types and required fields) and review Hierarchy: concepts and conventions for pricing states and enum casing; log the returned validation details |
| 401/403 | Missing, empty, invalid, or unauthorized credentials | Check x-api-key, x-brand-key, environment, and service entitlement; never print the full API key |
| 429 | Request throttling | Honor Retry-After, reduce concurrency, and apply backoff with jitter |
202 with success: false | The batch was not accepted | Retry the identical batch; retain the response |
| Timeout or connection reset | No reliable response reached the client | Retry cautiously; use deterministic keys and account for POST replacement semantics |
202 with success: true, but entity is not yet visible | Processing is asynchronous, a parent is missing, a status hides the entity, a PUT targeted a missing row, or downstream processing failed | Wait for normal asynchronous processing, verify the hierarchy and state fields, then collect diagnostics; there is no public status endpoint |
| Selection is absent from common recommendation results | Selection, Market, or Event is inactive; the Selection or a parent is NotDisplayed; price is unusable; parent join is broken | Check exact enum case, every level’s display state, parent keys, and odds fields |
| Decimal odds did not change as expected | PUT changed only fractional fields or supplied inconsistent prices | Send numPrice, denPrice, and decPrice together |
| Child records remain after a parent delete | Multiple same-type parent deletes were batched | Send each parent in a separate DELETE request |
Translations or extra keys disappeared | PUT replaced the whole nested object | Send the complete desired object, not only the nested key being changed |
| Event lifecycle timing is unexpected | Missing or invalid eventEndDateTime, timezone mismatch, or expiry/housekeeping is still processing | Use UTC for the end time and remember that an omitted or earlier end becomes start plus 24 hours |
Hierarchy diagnostics
When a Selection is not usable, verify the full chain:
Selection.marketKey
= Market.marketKey
Market.eventKey
= Event.eventKey
Event.typeKey
= Type.typeKey
Type.classKey
= Class.classKey
Class.categoryKey
= Category.categoryKeyThen verify:
selectionStatus,marketStatus, andeventStatusareActivewhere the entity should be available;displayStatusisDisplayedon the Selection and every Category, Class, Type, Event, and Market in its path where that field is supplied;eventDateTimeis correct and timezone-aware;- fractional odds have both
numPriceanddenPrice, with a denominator greater than zero, or a usabledecPriceis present; eventKeyon the Selection agrees with its Market’s Event;- keys match exactly, including case and punctuation.
Batch design
- Maximum documented POST/PUT record count is 500 per request.
- The record-count limit does not establish a separate public payload-byte limit.
- Large
extraand translation objects make a batch larger; split earlier when needed. - Avoid mixing unrelated hierarchy branches in a very large batch while validating a new integration.
- Avoid duplicate entity keys in the same batch; the first duplicate wins.
- Use parent-to-child order for POST.
- Send parent deletes individually when relying on cascade behavior.
Expiry behavior
X-Expires-After is an ingestion-time TTL, not an exact scheduling API.
- Minimum value is 60 seconds.
- One value applies to every record in the request.
- It is available on POST and PUT, not DELETE.
- POST without the header clears an old expiry as part of its full-row replacement; PUT without the header preserves an existing expiry.
- Removing an existing expiry requires a complete authoritative POST without the header.
- Records are removed asynchronously after they become eligible.
- A request that reaches processing after its TTL has already elapsed can be filtered out.
Use Event status and explicit deletes for deterministic business-state changes. Use TTL for temporary inventory where asynchronous expiry is acceptable.
Information to provide to support
If a request was accepted but the expected state is not available, provide:
- environment and base URL placeholder value used, without credentials;
- UTC request timestamp and HTTP method;
- brand key or an approved masked form;
- entity types and count;
- sanitized request body containing the relevant keys and hierarchy;
- HTTP status and full response body;
- vendor response record identifier, if requested;
- client retry history, including timestamps;
- expected state and observed state.
Never send the full x-api-key in logs, tickets, or screenshots.
Production-readiness checklist
- Credentials are stored in a secrets manager and never logged.
- Every response body is parsed, including 202 responses.
- 429, 5xx, and ambiguous transport failures use bounded backoff with jitter.
- Validation and authentication failures are not blindly retried.
- POST payloads are complete authoritative rows.
- PUT nested objects include the complete desired object.
- Odds PUTs send consistent fractional and decimal values together.
- POST batches are parent-first and contain no duplicate keys.
- Parent deletes are sent individually.
- Vendor acceptance identifiers and request metadata are retained for diagnostics.
- The integration does not depend on a public processing-status endpoint.
Updated about 2 hours ago
