Errors and Retries

Handle validation, concurrency, rate limits, and ambiguous failures safely.

Updated 18 August 2026

The ReBattery Supplier API uses HTTP status codes plus a small JSON error envelope. Check the status code first, then use the body to decide whether to correct, reconcile, or retry the request.

Error shape

Most failures return:

JSON
{
  "error": "Short error description"
}

Validation and publication failures can add field-level guidance:

JSON
{
  "error": "Validation failed",
  "field_errors": [
    "yearManufacture must be an integer from 2000 to 2035"
  ]
}

A failed create-and-publish request may retain its safely created draft:

JSON
{
  "error": "Listing could not be published",
  "field_errors": ["Collection address"],
  "listing_id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "status": "draft"
}

When listing_id is present, do not create a replacement blindly. Read that listing, complete the missing data, and publish it with a new mutation intent.

Status reference

StatusMeaningCorrect client action
400 Bad RequestMalformed JSON, missing/invalid idempotency key, invalid pagination, or malformed required headers.Correct the request. Do not retry unchanged.
401 UnauthorizedMissing, expired, revoked, or invalid API key.Replace or rotate the credential.
403 ForbiddenThe key lacks the required scope, or the account is not an approved supplier.Use an appropriately scoped key or resolve account approval.
404 Not FoundThe listing ID is malformed, unknown, or belongs to another account.Verify the account and stored listing ID. These cases are intentionally indistinguishable.
409 ConflictAn idempotency key was reused for a different intent, a lifecycle transition is invalid, or committed stock prevents the requested change.Reconcile the business state. Use a new key only for a genuinely new intent.
412 Precondition FailedIf-Match is stale, or a competing mutation won the revision race.Read the listing again, reconcile, and submit a new intent with its current ETag.
413 Payload Too LargeThe complete HTTP request body exceeds 40 MB.Reduce or split the media payload.
415 Unsupported Media TypeA mutation body was not sent as application/json.Encode the body as JSON and set Content-Type: application/json.
422 Unprocessable ContentA field or appended image is unsupported, wrongly typed, out of bounds, internally inconsistent, or not publish-ready.Correct the fields listed in field_errors. Do not retry unchanged.
425 Too EarlyThe same idempotency key is still being processed.Wait briefly, then retry the identical request with the same key.
428 Precondition RequiredAn existing-resource mutation omitted If-Match.Read the listing and send its quoted ETag.
429 Too Many RequestsThe key exceeded 100 requests in 60 seconds.Stop requests and wait for Retry-After; then resume gradually.
500 Internal Server ErrorAn unexpected server or persistence failure occurred.Retry the identical idempotent mutation at most twice with backoff. Stop the batch and contact support if it repeats.
503 Service UnavailableA required protective service, such as the durable rate limiter, is unavailable.Respect Retry-After and retry later.

Retry decision table

SituationRetry?Inputs to use
Network timeout or lost responseYesExact method, path, body, If-Match, and Idempotency-Key.
425Yes, after a short waitExact same request.
429 or 503Yes, after Retry-AfterSame intent; apply jitter when many workers resume.
500Yes, at most twice with exponential backoffSame idempotency key and identical request; then stop the batch.
412Not unchangedRead, reconcile, then use the new ETag and a new idempotency key.
409 idempotency mismatchNot unchangedReplay the original intent exactly or create a genuinely new key and intent.
400, 401, 403, 404, 413, 415, 422, 428NoCorrect the underlying request, credential, resource, or listing data first.

Recover from a stale revision

If an edit returns 412, another mutation changed the listing after you read it.

Read the current representation and headers:

Shell
curl --include --silent --show-error \
  'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6' \
  --header "Authorization: Bearer $REBATTERY_API_KEY"

Compare your intended field changes with the current resource. If they still apply, send a new request using the returned ETag and a new idempotency key:

Shell
curl --fail-with-body --silent --show-error \
  --request PATCH 'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6' \
  --header "Authorization: Bearer $REBATTERY_API_KEY" \
  --header 'Idempotency-Key: erp-inv-2026-001-edit-10' \
  --header 'If-Match: "18"' \
  --header 'Content-Type: application/json' \
  --data '{"description":"Retested after inventory reconciliation"}'

Do not automatically replay the stale body against the new revision. The other writer may have changed a related fact.

Backoff guidance

For retryable failures without a longer Retry-After, use bounded exponential backoff with jitter. For a 500, wait approximately 1 then 2 seconds and stop the batch if the same row fails again. Rate-limit and temporary-service retries may use approximately 1, 2, 4, 8, and 16 seconds, with random variation, before operator review.

TEXT
delay = min(16 seconds, 2 ^ attempt seconds) + random jitter

Never keep retrying validation or authentication errors. Repeated invalid requests can hide mapping bugs and waste rate-limit capacity.

After a repeated 500, reconcile any returned listing ID before resuming. A credential with listings:read can also enumerate the owned collection to find the stable supplier reference. If no listing can be reconciled, record the row as an operational failure and contact support. Never change the body or idempotency key merely to bypass the failed intent.

Idempotency troubleshooting

409 after reusing a key

An idempotency key identifies one exact mutation intent. Method, canonical path, and body are part of that identity. Use the original inputs to replay the request, or create a new key if the desired mutation has changed.

425 continues briefly

The first request still owns a processing lease. Wait and resend the same request. Do not fan out parallel retries with new keys.

The client timed out after uploading images

Retry with the same key. The API reconciles committed media rows before cleanup and returns the durable outcome. Do not assume a timeout means nothing was written.

Rate-limit headers

Every 429 response includes:

HTTP
Retry-After: 12

Collection endpoint responses also include:

HTTP
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1786723456

Retry-After is the number of seconds to wait. On collection responses, X-RateLimit-Reset is a Unix timestamp. Prefer Retry-After when both are present.

An oversized or invalid image is not a 413 unless it makes the whole HTTP body exceed 40 MB. Image append validation failures return 422. During create, an ingestion failure can return 200 with image_error because the draft itself was safely created; retry that exact create request with the same idempotency key.

Support checklist

If a problem persists after safe retries, provide ReBattery support with:

  • the HTTP method and path without credentials;
  • the UTC timestamp;
  • the response status and sanitized JSON body;
  • the listing ID and supplier reference, if available;
  • the idempotency key identifier, provided it contains no confidential data.

Never send the Bearer API key, one-time setup code, raw customer exports, or base64 image data.