Aller au contenu principal

Handling Failures & Retries

Audience

This API surface is for integration partners building on top of the Rokt network. Rokt ecommerce partners integrating placements on their own checkout should use the Rokt Ecommerce developer docs instead.

The Partnerships API is built for retry-safety. Three primitives carry the contract:

  • Idempotency-Key (request header you send) — collapses retries to a single server-side operation. Required on every write. The server caches the response for 24 hours; after that the key expires.
  • X-Operation-Id (response header server returns) — durable handle to the operation record. Use it with GET /v1/partnership/operations/{operation_id} to recover from network failures.
  • request_id (field on every response envelope; also returned in the X-Request-Id response header) — single-attempt trace ID. Pass this to support when you file a ticket.
remarque

Every response is wrapped. Both success and error responses come back as { status, error, message, request_id, data }. The examples below show the full envelope so retry logic that inspects request_id or error matches what the server actually emits.

Use them correctly and retries are free.

Failure scenario 1 — Network timeout before responseLien direct vers Failure scenario 1 — Network timeout before response

You sent the request, but the connection died before you got a response. The server may have committed the change, or it may not have. You don't know.

Resolution:

  1. If you captured X-Operation-Id from any partial response, poll the operation directly:

    curl https://accounts.rokt.com/v1/partnership/operations/$OPERATION_ID \
    -H "Authorization: Bearer $TOKEN" \
    -H "X-Platform-Parent-Account-Id: $PARENT"

    data.status is one of pending / in_progress / completed / failed. If completed, data.response_body carries the original write's response payload. If failed, data.error is the last error message captured.

  2. If you don't have an X-Operation-Id, retry the original call with the same Idempotency-Key. Within the 24-hour dedup window the server returns the cached response if the original succeeded, or re-executes if it failed.

Failure scenario 2 — 5xx from RoktLien direct vers Failure scenario 2 — 5xx from Rokt

Server-side transient error. Wait, retry, same key.

curl -X PUT https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/status \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Idempotency-Key: $SAME_KEY_AS_BEFORE" \
-H "Content-Type: application/json" \
-d '{ "status": "active" }'

If you're still getting 5xx after 3 retries with exponential backoff, capture request_id from every attempt and file a support ticket. Don't keep hammering — back off and escalate.

Failure scenario 3 — 400 validation errorLien direct vers Failure scenario 3 — 400 validation error

Your payload is wrong. The envelope's message field describes the offending parameter; request_id is the trace ID.

{
"status": 400,
"error": "BadRequest",
"message": "no partnership vertical mapping for partnerVerticalId=1500 partnerSubVerticalId=9999",
"request_id": "0e3a1b9c-1234-4abc-9def-aaaabbbbcccc",
"data": null
}

Fix the payload and resubmit with a NEW Idempotency-Key. The old key is now cached as a failed operation — reusing it within the 24-hour window returns the cached 400.

Failure scenario 4 — 401 / 403 / 422Lien direct vers Failure scenario 4 — 401 / 403 / 422

401 Unauthorized: API token is missing, malformed, or expired. Rotate the token via your token-issuance integration and retry. See Authentication.

403 Forbidden: Your API token is valid but you lack the manager-managed relationship grant on this account, or X-Platform-Parent-Account-Id doesn't match the managed account's real parent. Either:

  • The relationship was revoked (merchant offboarded from your platform on the Rokt side).
  • The account belongs to a different partner platform.
  • You sent the wrong X-Platform-Parent-Account-Id value.

Surface this to your operations team. Don't retry — 403 is structural, not transient.

422 Unprocessable: A required header is missing. Most commonly the X-Platform-Parent-Account-Id header on a write call. Add the header and retry.

curl -X PUT https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/status \
-H "Authorization: Bearer $NEW_API_TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "status": "active" }'

Failure scenario 5 — 409 conflictLien direct vers Failure scenario 5 — 409 conflict

Two distinct meanings:

On register/partnership: another partner platform already owns this store_identifier. The merchant's storefront URL was registered through another platform first.

{
"status": 409,
"error": "Conflict",
"message": "Rokt Account already exists",
"request_id": "0e3a1b9c-...",
"data": null
}

Surface this prompt to the merchant verbatim:

There is an issue with account creation that requires further review. Please email Partner Platform Support.

Do not retry the registration. The cross-partner resolution path is manual.

On write endpoints (controls, status): you reused an Idempotency-Key from a still-in-progress operation with a different request body. Either:

  • Wait briefly and retry — some paths process asynchronously.
  • Generate a fresh Idempotency-Key and retry. This starts a new operation.

Failure scenario 6 — Operation stuck in in_progressLien direct vers failure-scenario-6--operation-stuck-in-in_progress

Your write returned 202 Accepted (or completed synchronously) and gave you an X-Operation-Id. You polled GET /v1/partnership/operations/{operation_id} and data.status is still in_progress well past the latency you'd expect for that endpoint (e.g. > 60s for status / controls, > 5 min for registration). The operation has neither reached completed nor failed.

How to detect:

  • data.status === "in_progress" on every poll for an extended period.
  • No movement in data.updated_at across consecutive polls — the operation record isn't being touched.
  • data.attempts (if present) is not climbing — no async worker is picking it up.
{
"status": 200,
"error": null,
"message": "ok",
"request_id": "0e3a1b9c-...",
"data": {
"operation_id": "op_3a1b9c...",
"status": "in_progress",
"updated_at": "2026-05-20T19:14:00Z",
"response_body": null,
"error": null
}
}

Resolution — escalate, do NOT retry the original write. File a support ticket with:

  • The stuck X-Operation-Id.
  • The Idempotency-Key you sent.
  • The original request_id from the write response.
  • The endpoint and a sanitized payload.

Why retrying is dangerous. A stuck in_progress operation almost certainly has partially committed state — at least one provisioning step has executed, and the worker is either crashed, paused, or wedged on a downstream resource. Resubmitting the original write with a fresh Idempotency-Key creates a second operation that competes with the first. Depending on the endpoint that can produce:

  • Duplicate registrations (if the original commit landed but the post-commit projection didn't).
  • Conflicting marketplace-controls states.
  • A status cascade that fights with the in-flight one and leaves variants in mixed.
  • A second Stripe Connect session for the same merchant.

Resubmitting with the same Idempotency-Key won't help either — the key is already bound to the stuck operation and the server will short-circuit to "operation in progress" (409). The only safe move is escalation: support has the runbook to either resume the existing operation or mark it failed cleanly so you can retry from a known state.

Exponential backoff with jitter — 5 attempts max
  • Attempt 1: immediate
  • Attempt 2: wait 1s ± jitter
  • Attempt 3: wait 2s ± jitter
  • Attempt 4: wait 4s ± jitter
  • Attempt 5: wait 8s ± jitter
  • Give up after attempt 5, escalate to support with all request_ids
import time, random, requests

def put_with_retry(url, body, token, parent, idem_key, max_attempts=5):
for attempt in range(max_attempts):
r = requests.put(
url,
headers={
"Authorization": f"Bearer {token}",
"X-Platform-Parent-Account-Id": parent,
"Idempotency-Key": idem_key, # same key across retries
"Content-Type": "application/json",
},
json=body,
)
if r.status_code < 500 and r.status_code != 408:
return r # success, 4xx (don't retry), or done
time.sleep((2 ** attempt) + random.random())
raise RuntimeError(
f"exhausted retries; last request_id={r.json().get('request_id')}"
)
Rules of thumb
  • Always reuse Idempotency-Key across retries of the same logical operation within the 24h dedup window. Let the server collapse them.
  • Always generate a fresh Idempotency-Key after a 400. The old key is poisoned for the rest of the 24h window.
  • Always log request_id per attempt. This is the trace ID support needs.
  • Never retry 401/403. They're structural failures.
  • 422 means you forgot a required header. Add it, retry with the same key.
  • Never retry 409 on register/partnership. Surface to the merchant.
  • For timeouts, prefer polling X-Operation-Id over blind retry if you captured the header — it's cheaper and surfaces the original outcome.
Cet article vous a-t-il été utile ?