Errors
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.
Every error response from the Partnerships API uses the same JSON envelope — identical in shape to success responses, just with error populated and data: null. This page is the reference for what each status means, the scenarios that produce them, and how to debug an unexpected response in front of you right now.
Response envelopeDirect link to Response envelope
Every response (success and error) has this shape:
{
"status": 400,
"error": "BadRequest",
"message": "store_identifier https://acme is not a valid URL",
"request_id": "0e3a1b9c-1234-4abc-9def-aaaabbbbcccc",
"data": null
}
statusintegerMirrors the HTTP status code (200 on success, 400/401/403/404/409/422/5xx on error).
errorstring | nullStable error code keyed to the HTTP status — e.g. BadRequest (400), Unauthorized (401), Forbidden (403), NotFound (404), Conflict (409), ValidationFailed (422), RateLimitExceeded (429), InternalServerError (500). Safe to switch on. null on success.
messagestringHuman-readable description of what went wrong (or "ok" on success). Safe to surface to internal operators; do not display to end-merchants verbatim.
request_idstringServer-generated correlation ID (UUID). Also returned in the X-Request-Id response header. Pass this to Rokt support so they can look up server-side logs for the call.
dataobject | array | nullEndpoint-specific payload on success. null on error.
Always capture request_id from the response envelope. Including it in support tickets is the single biggest lever for fast resolution.
HTTP status codesDirect link to HTTP status codes
| Status | Meaning | Caller action |
|---|---|---|
| 400 Bad Request | Validation failed, payload malformed, missing required body field | Fix the payload, retry with a new Idempotency-Key |
| 401 Unauthorized | Missing, expired, or invalid API token | Rotate the API token via your token-issuance integration |
| 403 Forbidden | API token is valid, but either (a) you don't have permission to act on that account on behalf of your manager — your platform's account-to-account grant config is missing or hasn't synced — or (b) your X-Platform-Parent-Account-Id doesn't match the managed account's real parent | Surface to ops — a manager-managed grant may have been revoked, or the header value is wrong |
| 404 Not Found | Resource doesn't exist, or isn't accessible to the caller | Verify account_id and that it falls inside your manager-managed scope |
| 409 Conflict | Idempotency-Key conflict, or store_identifier already registered by another partner | See common scenarios below |
| 422 Unprocessable Entity | Missing required header (most commonly X-Platform-Parent-Account-Id on a write), or payload is semantically invalid (e.g. unknown enum value) | Add the missing header and retry with the same key |
| 429 Too Many Requests | Per-partner rate limit hit | Honor Retry-After, retry with the same Idempotency-Key. See Rate Limits for caps per endpoint |
| 500 Internal Server Error | Rokt-side error | Wait and retry with the same Idempotency-Key |
| 501 Not Implemented | You sent Prefer: respond-async but async mode isn't enabled for your platform yet | Drop the header |
| 502 / 503 / 504 | Rokt-side transient (downstream service unhealthy) | Retry with backoff, same Idempotency-Key |
For 5xx responses, always retry with the same Idempotency-Key you used originally, within the 24-hour dedup window. A fresh key on retry can cause the same logical operation to run twice if the original eventually succeeded server-side.
Common error scenariosDirect link to Common error scenarios
422 — header X-Platform-Parent-Account-Id is required
You omitted the X-Platform-Parent-Account-Id header on a write call. The server requires it on all writes.
{
"status": 422,
"error": "ValidationFailed",
"message": "header X-Platform-Parent-Account-Id is required",
"request_id": "0e3a1b9c-...",
"data": null
}
Fix. Add X-Platform-Parent-Account-Id: <your-rokt-parent-account-id> to the request. Retry with the same Idempotency-Key — within the 24-hour dedup window the server collapses retries.
400 — store_identifier is not a valid URL
store_identifier must be a fully-qualified URL between 3 and 400 chars, scheme included. Sending a bare hostname is rejected.
{
"status": 400,
"error": "BadRequest",
"message": "store_identifier acme.myshopify.com is not a valid URL",
"request_id": "0e3a1b9c-...",
"data": null
}
Fix. Send https://acme.myshopify.com (or whatever the merchant's primary storefront URL is). Retry with a fresh Idempotency-Key.
400 — No partnership vertical mapping exists for MCL translation
Your partner taxonomy contains a vertical (or sub-vertical) ID that has no corresponding row in your vertical mapping. The server cannot translate it to Rokt's internal taxonomy, so the entire PUT is rejected atomically — no partial writes.
{
"status": 400,
"error": "BadRequest",
"message": "No partnership vertical mapping exists for MCL translation",
"request_id": "0e3a1b9c-...",
"data": null
}
Fix. Email smb-partnerships@rokt.com to add the mapping row. Include the exact partnerVerticalId and partnerSubVerticalId you tried, plus the partner-side name so the row can be filled in correctly.
409 — Rokt Account already exists (on register)
A different partner platform already owns this store_identifier. The Rokt account exists, but it's attached to another integration — registration cannot transfer ownership automatically.
{
"status": 409,
"error": "Conflict",
"message": "Rokt Account already exists",
"request_id": "0e3a1b9c-...",
"data": null
}
Fix. Surface this exact message to the merchant in your onboarding UI:
There is an issue with account creation that requires further review. Please email [Partner Platform Support].
Do not retry with a modified store_identifier to work around this — the conflict is the system telling you a real ownership question needs to be resolved by humans.
400 — Missing required non-empty parameter Idempotency-Key
Every write call (POST, PUT) requires the Idempotency-Key header. It must be a non-empty UUID.
{
"status": 400,
"error": "BadRequest",
"message": "Idempotency-Key header required for partnership writes",
"request_id": "0e3a1b9c-...",
"data": null
}
Fix. Generate a UUID per logical operation client-side and pass it as Idempotency-Key: <uuid>. See Idempotency Keys for how to scope the key across retries.
409 — Operation in progress / Idempotency-Key body mismatch
You reused an Idempotency-Key either while the original call is still being processed, or with a different body than the cached one. The server won't run the same key twice concurrently, and won't silently overwrite a cached response with a new payload.
Fix. Either wait briefly and retry the same call, or — better — 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"
The operation_id is returned in the X-Operation-Id response header on every write. See Operations.
429 — Rate limit exceeded (honor Retry-After)
You exceeded the per-partner rate limit for this endpoint. The server returns the standard envelope plus a Retry-After response header carrying an integer number of seconds to wait before retrying.
{
"status": 429,
"error": "RateLimitExceeded",
"message": "rate limit exceeded for partnership writes; retry after 30s",
"request_id": "0e3a1b9c-...",
"data": null
}
Response headers (relevant subset):
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-Request-Id: 0e3a1b9c-1234-4abc-9def-aaaabbbbcccc
Fix. Sleep for Retry-After seconds, then retry with the same Idempotency-Key you used originally. Reusing the key inside the 24-hour dedup window guarantees the server collapses retries to a single logical operation — a fresh key here would risk running the write twice if the original eventually succeeded server-side.
curl backoff snippet (writes headers to a file, parses Retry-After, sleeps, retries with the same key):
KEY=$(uuidgen)
while true; do
STATUS=$(curl -s -o body.json -D headers.txt -w '%{http_code}' \
-X PUT https://accounts.rokt.com/v1/partnership/accounts/$ACCOUNT_ID/status \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Idempotency-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "active" }')
if [ "$STATUS" != "429" ]; then break; fi
WAIT=$(grep -i '^Retry-After:' headers.txt | awk '{print $2}' | tr -d '\r')
sleep "${WAIT:-30}"
done
Python equivalent (using requests):
import time, uuid, requests
key = str(uuid.uuid4())
while True:
r = requests.put(
f"https://accounts.rokt.com/v1/partnership/accounts/{account_id}/status",
headers={
"Authorization": f"Bearer {token}",
"X-Platform-Parent-Account-Id": parent,
"Idempotency-Key": key, # same key across retries
"Content-Type": "application/json",
},
json={"status": "active"},
)
if r.status_code != 429:
break
time.sleep(int(r.headers.get("Retry-After", "30")))
See Rate Limits for per-endpoint caps and Handling Failures for the broader retry contract.
400 — contact is only valid for hosted_invite payout setup
You sent experience: "embedded" together with a contact block. The two are mutually exclusive — contact is required for hosted_invite, forbidden for embedded.
Fix. Either drop the contact field (for embedded) or change experience to hosted_invite (if you want Rokt to email the merchant). See the Payout Setup section in the onboarding workflow.
Field-level validation errorsDirect link to Field-level validation errors
Common per-field rules that trip integrations during early onboarding. The full schema is in openapi.yaml; this is the short list of constraints worth knowing by heart.
POST /v1/accounts/register/partnershipDirect link to post-v1accountsregisterpartnership
| Field | Rule |
|---|---|
brand | Required, 1–200 chars |
country_code | Required, exactly 2 letters (ISO 3166-1 alpha-2). Lower-case input is normalized to upper-case server-side |
store_identifier | Required, valid URL (must include scheme like https://), 3–400 chars |
external_account_id | Required, non-empty. Idempotency key for registration — reusing it returns the same account_id |
vertical_id / sub_vertical_id | Required. Must exist in your partner taxonomy and have a row in your vertical mapping |
platform_parent_account_id | Required body field. Mirror it in the X-Platform-Parent-Account-Id header for clarity |
PUT .../marketplacecontrolslistsDirect link to put-marketplacecontrolslists
| Field | Rule |
|---|---|
name | Required, non-empty string |
blockedVerticals | Required array. Each entry must include partnerVerticalId + partnerSubVerticalId. policy defaults to Block if omitted |
blockedVerticals[].policy | One of Allow, Block |
blockedVerticals[].position1Policy | Optional. One of Allow, Block. Defaults to the entry's policy if omitted |
domains[].policy | One of Allow, Block |
PUT .../statusDirect link to put-status
| Field | Rule |
|---|---|
status | Required. One of active, paused. Note: mixed is a read-only aggregate state — you cannot send it |
POST .../payout-setupDirect link to post-payout-setup
| Field | Rule |
|---|---|
provider | Required. Currently only stripe_connect is supported |
experience | Required. One of embedded, hosted_invite. Mutually exclusive with contact — see below |
contact.email | Required for hosted_invite. Forbidden for embedded. Sending contact with experience: "embedded" returns 400 |
any nested bank / card / routing / account_number / iban / ssn / tax / tax_id / paypal / external_account* / payout_destination / payment_method / cvc fields | Forbidden anywhere in the request. Returns 400. Stripe collects this data directly from the merchant |
Debugging checklistDirect link to Debugging checklist
When you're staring at an unexpected error, walk this list top-to-bottom before reaching out for support.
- Capture
request_idfrom the response envelope (or theX-Request-Idresponse header — same value). - Confirm
Content-Type: application/jsonis set on every write call. - Confirm
X-Platform-Parent-Account-Idis set on every write —422is the canonical signal it's missing. - Decode your API token and verify it hasn't expired (
expclaim). API tokens are short-TTL — roughly 5 minutes. - Confirm you're hitting
accounts.rokt.com/v1/partnership/*oraccounts.rokt.com/v1/accounts/register/partnership. No other Rokt host is partner-callable. - If a write returns a 4xx but you suspect a server bug, retry with
?dry_run=trueto isolate whether the payload is at fault or the server is. Dry-run runs the full validation without persisting state. See Dry-Run Mode. - For 5xx responses, retry with the same
Idempotency-Keyinside the 24-hour dedup window. Do not generate a new key on retry. - If all else fails, email
smb-partnerships@rokt.comwith therequest_id, the approximate timestamp of the call (UTC), and the endpoint you hit.
Why request_id matters. Rokt threads it through every server-side log entry for your call. Pass it back in a support ticket and the team can find the exact failure without paging through traffic.