Skip to content

Handle errors

Every error has the same JSON shape. Branch on error.code, show error.message, and log error.request_id.

{
"error": {
"code": "SKU_NOT_IN_SEARCH",
"message": "This product was not a result of that search. Use the search_session_uuid of the search (or the page) that returned it.",
"field": "sku_uuid",
"request_id": "e19b8889-a63b-4000-9dbe-ffd5b9157bd5"
}
}
FieldUse
codeStable. Write your logic against it
messageWritten for a person. It can change. Never match on it
fieldThe request field at fault, as a dotted path, or null
request_idQuote it to Flume support. Also in the X-Request-Id response header
detailsSometimes present, with more about a refusal
What to do when a server call failsRead the status and error.code on the left, then take the one action beside it. The same content is listed as text under the diagram.401 PARTNER_KEY_INVALIDFix the key or the base URLdo not retry401 USER_TOKEN_INVALIDDelete the stored tokenstart again without it401 CONNECT_CODE_INVALIDStore nothingthe user verifies on the next open403 USER_NOT_CONNECTEDVerify the userthen send the user access token400, 404, 413, 422and any other 403 or 409Fix the requestfield names what is wrong409 IDEMPOTENCY_IN_PROGRESSRetry with the same keyafter Retry-After429Wait for Retry-After, then retrya daily limit resets at 00:00 UTC500, 502, 503, timeoutRetry with a growing waitsame Idempotency-Key, up to 3 retries

What to do when a server call fails. Read the status and error.code on the left, then take the one action beside it.

Read this diagram as text
  1. 401 PARTNER_KEY_INVALID: fix the key, or the base URL it belongs to. Do not retry.
  2. 401 USER_TOKEN_INVALID: delete the stored token and start the session again without it. The user verifies again in the iframe.
  3. 401 CONNECT_CODE_INVALID: store nothing and do not retry. The user verifies again on the next open.
  4. 403 USER_NOT_CONNECTED: the call needs a verified user. Verify the user, then send the user access token.
  5. 400, 404, 413, 422, and any other 403 or 409: fix the request. On a 422, field names what is wrong. Do not retry unchanged.
  6. 409 IDEMPOTENCY_IN_PROGRESS: the first request with this Idempotency-Key is still running. Retry with the same key after Retry-After.
  7. 429: wait for the number of seconds in Retry-After, then retry. For a daily limit that is the time until 00:00 UTC, so stop for the day.
  8. 500, 502, 503 or a timeout: retry with a growing wait and the same Idempotency-Key, up to 3 retries.
StatusRetry?How
400, 404, 413, 422NoFix the request
401NoFix the key, or verify the user again
403NoSend the user access token, or verify the user
409 IDEMPOTENCY_IN_PROGRESSYesAfter Retry-After seconds (2 when the header is absent), same key
409 other codesNoResolve the conflict
429YesAfter Retry-After seconds
500 INTERNALYesWith backoff. Same Idempotency-Key
502 VERIFICATION_EMAIL_FAILEDYesWith backoff
503 SEARCH_UNAVAILABLEYesWith backoff

Use exponential backoff with jitter. Double the wait each time, and stop after 4 tries.

import random
import time
import requests
RETRYABLE = {"IDEMPOTENCY_IN_PROGRESS", "RATE_LIMITED", "INTERNAL", "VERIFICATION_EMAIL_FAILED", "SEARCH_UNAVAILABLE"}
def call(method, url, **kwargs):
for attempt in range(4):
response = requests.request(method, url, timeout=75, **kwargs)
if response.ok:
return response
try:
error = response.json()["error"]
except (ValueError, KeyError):
response.raise_for_status()
if error["code"] not in RETRYABLE or attempt == 3:
raise RuntimeError(f'{error["code"]} {error["request_id"]}: {error["message"]}')
wait = float(response.headers.get("Retry-After", 2**attempt))
if wait > 60:
raise RuntimeError(f'{error["code"]}: retry in {wait:.0f} seconds')
time.sleep(wait + random.random())

Keep the same Idempotency-Key in init.headers across the retries of one action.

To retry an upload such as POST /search, build the body again for each attempt. In Python, open the file again or call image.seek(0). A file that was already read uploads as empty, and the call returns 400 IMAGE_REQUIRED.

Handle responses that are not the envelope

Section titled “Handle responses that are not the envelope”
  • On live, a request from an unsupported country gets a 403 with no JSON body. See Environments.
  • On live, a network-level limit per IP address can return a 429 with no JSON body and no Retry-After.
  • A network timeout has no body. Retry a write with the same Idempotency-Key.

Always check that the body parses as JSON and has error before you read it.

  • error.message is written for the user. You can show it.
  • For 429 RATE_LIMITED, tell the user when to try again.

Log these for every failed call:

  • X-Request-Id
  • the method and path
  • error.code
  • the key prefix, never the key
  • session_uuid, for iframe sessions