Errors
Every code the error envelope carries, what causes it, how to fix it and whether to retry.
A refusal answers a 4xx or 5xx status with a JSON body: error.code says what happened and error.message is one fixed sentence. Branch on the code, not on the status or the sentence: two codes can share a status. Some codes add extras: errors (per item), latest, rev, epoch, missing and retry_after.
{
"error": {
"code": "validation_failed",
"message": "Some items could not be saved. Each error says why.",
"errors": [
{
"unit_id": "u-01oso1x",
"error": "outside_window",
"message": "That time is outside the window."
}
]
}
}Codes
| Status | Code | What happened | What to do | Retry |
|---|---|---|---|---|
| 400 | bad_request | The request is not valid: a path value, a query value or a body field is missing, malformed or out of range. | Read the message, which names up to three fields and what is wrong with each, correct the request and send it again. | No |
| 401 | invalid_principal | The key is missing, malformed, unknown, revoked or expired; all of these answer the same way. | Send the whole key as Authorization: Bearer rwaff_k_.... If it still fails, check under API and MCP in the portal that the key is active, or create a new one. | No |
| 403 | forbidden | The key is valid but cannot do this for this affiliation: the affiliation is not one of the key's, or the person who made the key lacks the role. | Call GET /v1/affiliations to see what the key can reach and with which role. A key made for other stations needs replacing. | No |
| 403 | read_only | The affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable. | Only reads work on this affiliation. Ask the network if you expected it to be live. | No |
| 403 | key_cannot_sign | A key asked to sign a week. Signing is an attestation, so only a person signed in to the portal can do it. | Send the person to the portal's signing panel (over MCP, sign_week returns the link). Keys report times; people sign. | No |
| 403 | insufficient_scope | The key does not carry the scope this endpoint needs (read, report or acknowledge). | Scopes are fixed when a key is made. Create a key with the scope named on the endpoint's page and use it instead. | No |
| 404 | not_found | Nothing was found for the values in the path: no audio is ready for the week yet, the upload id is unknown, or the record does not exist. | Check the ids against GET /v1/affiliations and GET /v1/affiliations/{id}/weeks. For audio, wait until list-creatives shows mediaStatus ready. | Yes |
| 409 | stale_version | A newer version of the week exists than the one you sent: acknowledging the older one, or reporting dropped breaks by its break numbers after the show's breaks were renumbered, would be wrong. | Read the week again, then acknowledge the latest version the error carries, or report the dropped breaks again with the new break numbers. | No |
| 409 | epoch_changed | The network reopened or changed the week after you read it, so the epoch you sent is out of date. | Read the week again, take week.epoch from it, check your times still apply and send the batch again. | No |
| 409 | week_signed | The week is signed, so its affidavits can no longer change. | Ask the network to reopen the week if something needs correcting. | No |
| 409 | sign_blocked | The week cannot be signed yet: items are still pending, or the broadcast week has not ended. missing lists what is left. | Report the units and show days listed in missing, then sign in the portal after Sunday. No API call signs. | No |
| 409 | conflict | The week changed while the request was being handled. | Read the week again, then retry with the values it now shows. | Yes |
| 409 | stale | The week changed after the log was read, so the matches were made against an older copy. | Read the week again and upload the log again, or apply with the new epoch the error carries. | No |
| 409 | already_applied | Matches from this upload were already applied, and that result does not cover the units you sent now. | Report the other times with report-affidavits. An upload is applied once; the repeat of the same choice is answered with replayed: true. | No |
| 409 | not_ready | The uploaded log is still being read, or reading it failed, so there is nothing to apply yet. | Poll GET /api/uploads/{upload} with the same station key until it answers ready, then apply. After a failure, type the times instead. | Yes |
| 409 | nothing_to_match | No unit in this week is waiting for a time, so there is nothing a log could match. | Check the week and the station. Units become reportable once their window has closed. | No |
| 409 | nothing_to_apply | The upload has no matched lines to apply. | Send the chosen items yourself, or report the times with report-affidavits. | No |
| 413 | payload_too_large | The request body is over the limit: 256 KB of JSON (512 KB when applying log matches), or 10 MB for an uploaded log. | Split a report into smaller batches (at most 2,000 items each), or upload one broadcast week per file. | No |
| 415 | unsupported_media_type | The body was not sent as JSON. | Send Content-Type: application/json with a JSON object body. | No |
| 415 | unsupported_file | The uploaded file is not a log export the reader accepts. | Upload the CSV, TSV, text, spreadsheet (xls or xlsx) or PDF your automation system saves. | No |
| 422 | validation_failed | Items were refused. With all_or_nothing true nothing was saved; errors says why for each item. | Fix each item named in errors (for example rev_conflict carries the current value) and send those items again. | No |
| 422 | upload_failed | The log could not be read within the request. upload_id names the upload. | Try a CSV export of the log, or type the times with report-affidavits. | No |
| 429 | rate_limited | Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one. | Wait for Retry-After seconds, then retry. Pace calls with RateLimit-Remaining to stay clear of it. | Yes |
| 500 | not_saved | The change could not be saved, so nothing changed. | Send the same request again with the same Idempotency-Key; a write that did save answers with its first result. | Yes |
| 500 | server_error | Something went wrong on our side. The answer never carries internal detail. | Retry once. If it keeps happening, quote the X-Request-Id header when you contact the network. | Yes |
| 502 | upstream_failed | The service behind the API answered in a way the portal could not use. | Retry after a short pause. Reads are safe to repeat; repeat a write with the same Idempotency-Key. If a write keeps failing with that key, the key may already have been used for a request to another endpoint: read the week to see what was saved, then send the write with a new key. | Yes |
| 503 | unavailable | The service is not available right now: it could not be reached, or it is not set up yet. | Retry after a short pause, backing off. Nothing about the request needs to change. | Yes |
| 503 | demo_mode | This copy of the portal runs on demo data, so the API is off. The body is exactly {"error":"demo_mode"}. | Use the "Try it" panel on each endpoint page, which runs on the demo data without a key, or call the live portal. | No |
Every endpoint can answer 401 invalid_principal, 429 rate_limited, 500 server_error, 503 unavailable; each endpoint page lists the others it can give.
Item errors
A batch report saves the valid items and lists the others in errors with a code each (with all_or_nothing true, one refusal saves nothing and answers 422). Each entry names its unit_id, or its vehicle_id and date.
| Code | Why the item was refused |
|---|---|
rev_conflict | The item changed since you read it; current carries its value now. Re-apply your change to it. |
not_reportable | The unit's window has not closed yet, or the show day has not ended: at the end of the station's usual hours when it gave them, otherwise at the end of the day in its time zone. |
outside_window | An aired time is outside the unit's window. Report it as outside with a reason. |
invalid_time | The time does not fit the status: an outside time inside the window on one of the unit's own days is aired. |
invalid_date | The date is not one the unit or show day can take. |
reason_required | outside, not_aired and not_carried need a reason of 1 to 300 characters. |
removed | The unit was removed in a later version. |
not_found | No such unit, or no such show day, in this week. |
break_not_found | A dropped break is not a break of that show day (give its break number, counted straight through the show, and the hour that holds it). |
local_break | A dropped break is a local break, which the network does not bill. |
Retrying
- Retry a
Yescode after a pause that grows each time (for example 1, 2, 4 then 8 seconds), and a 429 after itsRetry-After. - Repeat a write with the same
Idempotency-Key: if the first attempt did save, the repeat answers its first result instead of recording twice. - Do not retry a
Nocode unchanged: the same request gets the same answer. - The demo portal answers every API call with 503 and exactly
{"error":"demo_mode"}; use the Try it panels there.