{"openapi":"3.1.0","info":{"title":"Affiliate Portal API","version":"1","description":"The station API: read a network's orders, audio and documents, report affidavits and show certificates, and acknowledge new versions, with a station key. Signing a week stays a person's act in the portal. The reference, with code samples and a try-it panel on demo data, is at /docs."},"servers":[{"url":"https://affiliates.radioworkflow.com/api/v1","description":"https://affiliates.radioworkflow.com"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Stations"},{"name":"Weeks"},{"name":"Copy and audio"},{"name":"Affidavits"}],"paths":{"/affiliations":{"get":{"operationId":"list-affiliations","summary":"List affiliations","description":"Every affiliation the key can reach. An affiliation is one station's relationship with one network: a station that carries two networks has two, each with its own id, deadlines and people.\n\nStart here. Every other endpoint takes the `id` of an affiliation from this list, and each entry says what the key may do there:\n\n- `canReport` is true where the key may report times. It needs the `report` scope, and a station whose affidavits come from its own Radio Workflow log (`lane` `radioworkflow`) does not report here.\n- `readOnly` is true once the network has paused or ended the affiliation. Its history stays readable; nothing new can be reported or acknowledged.\n- `canSign` is always false for a key: a person signs a week in the portal.\n\n`vehicles` lists the network's shows and its run-of-schedule (ROS) vehicle this station carries. Show certificates and printed documents name a vehicle by its `id`.","tags":["Stations"],"security":[{"bearerAuth":[]}],"parameters":[],"responses":{"200":{"description":"The affiliations, in the order the portal lists them.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAffiliationsResponse"},"example":{"affiliations":[{"id":"srn-ksmt","networkName":"Summit Radio Network","networkShort":"SRN","stationCall":"KSUMT-FM","stationName":"KSUMT 101.5 The Summit","market":"Cedar Falls","lane":"portal","canSign":false,"canReport":true,"role":"sign","readOnly":false,"timezone":"America/Chicago","networkContact":{"name":"Marcus Bell","title":"Network Traffic Coordinator","email":"affiliates@summit-sample.example","phone":"(555) 010-2440"},"deadlineRule":"Friday after the broadcast week, 5:00 PM","audioDelivery":"Delivered by the network's spot delivery service","vehicles":[{"id":"show-wd","name":"The Summit Show","kind":"show"},{"id":"demo-ros","name":"ROS","kind":"ros"}]}]}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"list_affiliations","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}":{"get":{"operationId":"get-week","summary":"Get a week's order","description":"The whole order for one broadcast week: every ROS unit with its window, copy and affidavit, every show day with its spots and certificate, the versions the network has published, the week's creatives and the station's shows.\n\nWindows and times are minutes after midnight in the station's time zone (`affiliation.timezone`): `360` is 6:00 AM and `1139` is 6:59 PM. `now` is the station's date and minute when the week was read; a unit takes a time once its window has closed.\n\nThe report endpoints check what you read here. Send `week.epoch` and each unit's or show day's `rev` back unchanged: when the network reopens the week the epoch moves and a report built on the older read answers 409 `epoch_changed`, and an item someone else changed answers `rev_conflict` with its current value.\n\nSend `known_version` with the version you hold to learn whether the order changed: `notModified` is true when it did not. Affidavit state is always current. Reading a week records that the station has seen its latest version. A week the network has not published answers `week.published` false with empty lists.","tags":["Weeks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"known_version","in":"query","required":false,"description":"The version you already hold. `notModified` says whether the order has changed since.","schema":{"type":"integer","minimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"The station, its clock, the week, the week's creatives and the station's shows.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetWeekResponse"},"example":{"affiliation":{"id":"srn-ksmt","networkName":"Summit Radio Network","networkShort":"SRN","stationCall":"KSUMT-FM","stationName":"KSUMT 101.5 The Summit","market":"Cedar Falls","lane":"portal","canSign":false,"canReport":true,"role":"sign","readOnly":false,"timezone":"America/Chicago","networkContact":{"name":"Marcus Bell","title":"Network Traffic Coordinator","email":"affiliates@summit-sample.example","phone":"(555) 010-2440"},"deadlineRule":"Friday after the broadcast week, 5:00 PM","audioDelivery":"Delivered by the network's spot delivery service","vehicles":[{"id":"show-wd","name":"The Summit Show","kind":"show"},{"id":"demo-ros","name":"ROS","kind":"ros"}]},"now":{"date":"2026-09-24","minutes":630,"ts":1790263800,"timezone":"America/Chicago"},"week":{"start":"2026-09-21","days":["2026-09-21","2026-09-22","2026-09-23","2026-09-24","2026-09-25","2026-09-26","2026-09-27"],"version":3,"state":"in_progress","due":"Fri, Oct 2, 5:00 PM","dueAt":1790978400,"epoch":1,"rev":1,"published":true,"shows":[{"show":{"id":"show-wd","name":"The Summit Show","kind":"daily","days":[0,1,2,3,4],"hours":5,"breakCount":19,"usualStart":1140,"usualEnd":1439,"localBreaks":[{"hour":2,"breakNumber":5}]},"showNumber":"#26-39","spots":[{"id":"s-1sjkdlr","showId":"show-wd","date":"2026-09-21","hour":1,"breakNumber":1,"position":1,"creativeId":"mhcl-a","advertiser":"Meridian Health Clinics","product":"","title":"Walk-In Care","isci":"SR_MHCL0901030","length":30,"addedInVersion":1}],"removed":[],"airings":[{"key":"srn-ksmt:show-wd:2026-09-23","showId":"show-wd","date":"2026-09-23","status":"pending","rev":1}],"breaks":[{"hour":1,"breakNumber":1,"local":false},{"hour":1,"breakNumber":2,"local":false}],"excludedDays":[]}],"rosName":"Summit ROS","units":[{"id":"u-01oso1x","date":"2026-09-22","daypart":"ROS","windowStart":360,"windowEnd":1139,"creativeId":"trhd","advertiser":"Trailhead Outfitters","product":"","title":"Hunting Season Kickoff","isci":"SR_TRHD0921060","length":60,"cart":"K90388","status":"pending","addedInVersion":1,"kind":"dated","weekStart":"2026-09-21","rev":1,"copyStatus":"ready","flags":[]}],"removed":[],"revisions":[{"version":3,"publishedAt":"Thu, Sep 24, 9:12 AM","publishedAtTs":1790259120,"summary":"1 added, 1 removed","changes":[{"kind":"added","vehicle":"The Summit Show","date":"2026-09-24","where":"Hour 2, Break 6","advertiser":"Golden Grain Bakery","isci":"SR_GGBK0901030","length":30,"detail":"Added inside the show. Nothing to schedule: it is in the show audio."}]}],"acknowledgedVersion":2},"creatives":[{"id":"trhd","advertiser":"Trailhead Outfitters","title":"Hunting Season Kickoff","isci":"SR_TRHD0921060","length":60,"flightStart":"2026-09-21","flightEnd":"2026-10-25","audioReady":true,"cart":"K90388","timesThisWeek":5,"mediaStatus":"ready"}],"shows":[{"id":"show-wd","name":"The Summit Show","kind":"daily","days":[0,1,2,3,4],"hours":5,"breakCount":19,"usualStart":1140,"usualEnd":1439,"localBreaks":[{"hour":2,"breakNumber":5}]}],"notModified":false}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"get_week","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/status":{"get":{"operationId":"get-status","summary":"Get what is outstanding","description":"One call for a dashboard or a nightly check: how many units still need a time, which weeks still need a signature and when each is due, which new versions the station has not acknowledged, and how many creatives are still waiting for audio.\n\n`nextDeadline` is the earliest unsigned week's due time, when there is one. `dueAt` values are Unix seconds; `due` is the same instant written out in the station's time zone.","tags":["Stations"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"}],"responses":{"200":{"description":"The station's clock, what is outstanding and the next deadline.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStatusResponse"},"example":{"now":{"date":"2026-09-24","minutes":630,"ts":1790263800,"timezone":"America/Chicago"},"outstanding":{"to_report":7,"to_sign":[{"week":"2026-09-21","due":"Fri, Oct 2, 5:00 PM","dueAt":1790978400,"overdue":false}],"to_acknowledge":[{"week":"2026-09-21","version":3}],"audio_pending":1},"nextDeadline":{"week":"2026-09-21","due":"Fri, Oct 2, 5:00 PM","dueAt":1790978400}}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"get_status","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}/versions":{"get":{"operationId":"get-changes","summary":"Get a week's versions","description":"Every version the network has published for the week, newest first, each with a one-line summary and the changes that touch this station: units added or removed, copy that changed, spots moved inside a show.\n\n`acknowledged` says who acknowledged a version and when. Acknowledge the latest one with `acknowledge-version` once your log reflects it.","tags":["Weeks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"}],"responses":{"200":{"description":"The versions, newest first.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetChangesResponse"},"example":{"revisions":[{"version":3,"publishedAt":"Thu, Sep 24, 9:12 AM","publishedAtTs":1790259120,"summary":"1 added, 1 removed","changes":[{"kind":"added","vehicle":"The Summit Show","date":"2026-09-24","where":"Hour 2, Break 6","advertiser":"Golden Grain Bakery","isci":"SR_GGBK0901030","length":30,"detail":"Added inside the show. Nothing to schedule: it is in the show audio."}]}]}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"get_changes","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}/creatives":{"get":{"operationId":"list-creatives","summary":"List a week's creatives","description":"The creatives the week's units and show spots use: advertiser, title, ISCI, length, flight dates, the network cart number and how many times it runs this week.\n\n`mediaStatus` says where the audio is. `ready` audio comes with an `audioUrl` that works for 15 minutes (`audioUrlExpiresAt`, Unix seconds); ask again for a fresh one. `pending` audio has not arrived yet. `external` means the network delivers the audio another way, and no link is given.\n\nSend `with_urls=0` to leave the links out, for example when you only need the list.","tags":["Copy and audio"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"with_urls","in":"query","required":false,"description":"`1` includes the short-lived audio links; `0` leaves them out.","schema":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1}]}}],"responses":{"200":{"description":"The week's creatives.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCreativesResponse"},"example":{"creatives":[{"id":"trhd","advertiser":"Trailhead Outfitters","title":"Hunting Season Kickoff","isci":"SR_TRHD0921060","length":60,"flightStart":"2026-09-21","flightEnd":"2026-10-25","audioReady":true,"cart":"K90388","timesThisWeek":5,"mediaStatus":"ready","audioUrl":"https://media.example/SR_TRHD0921060.mp3?signature=0f3a9c","audioUrlExpiresAt":1790264700}]}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"list_creatives","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}/audio.zip":{"get":{"operationId":"get-week-audio","summary":"Download a week's audio","description":"Every creative of the week whose audio is ready, in one ZIP archive, each file named `{ISCI}.mp3`. Load it straight into your automation system.\n\nOnly `ready` audio goes in: a creative that is still `pending`, or whose audio the network delivers another way, is left out. With nothing ready the answer is 404 `not_found`.\n\nOver MCP the tool returns a link to this download instead of the audio itself, so no audio or signed link passes through an assistant.","tags":["Copy and audio"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"}],"responses":{"200":{"description":"A ZIP archive with one file per ready creative.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Content-Disposition":{"$ref":"#/components/headers/Content-Disposition"}},"content":{"application/zip":{"schema":{"type":"string","contentMediaType":"application/zip"}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"get_week_audio","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}/export":{"get":{"operationId":"export-order","summary":"Export a week's order","description":"The week's ROS units as a file your traffic or automation system can import. Show spots are not in it: they are in the show audio.\n\n- `csv` (the default): one row per unit with `unit_id`, `date`, `allowed_days`, `window_start`, `window_end`, `daypart`, `advertiser`, `product`, `title`, `isci`, `length` and `cart`, UTF-8 with a byte order mark.\n- `tab`: one row per distinct creative with `cart`, `length`, `isci`, `title` and `advertiser`, for a cart import.\n- `xml`: an `<order>` element with one `<unit>` per unit and the CSV columns as attributes.\n\nThe answer is an attachment with a file name. A cell that starts like a spreadsheet formula is prefixed with a single quote.\n\nOver MCP the tool returns `filename`, `content_type` and `body` as JSON instead of a file.","tags":["Copy and audio"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"format","in":"query","required":false,"description":"The file format.","schema":{"type":"string","enum":["csv","tab","xml"]}}],"responses":{"200":{"description":"The file, as an attachment. `tab` answers `text/tab-separated-values` and `xml` answers `application/xml`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Content-Disposition":{"$ref":"#/components/headers/Content-Disposition"}},"content":{"text/csv":{"schema":{"type":"string"},"example":"unit_id,date,allowed_days,window_start,window_end,daypart,advertiser,product,title,isci,length,cart\r\n\"u-01oso1x\",\"2026-09-22\",\"2026-09-22\",\"06:00\",\"18:59\",\"ROS\",\"Trailhead Outfitters\",\"\",\"Hunting Season Kickoff\",\"SR_TRHD0921060\",\"60\",\"K90388\"\r\n"},"text/tab-separated-values":{"schema":{"type":"string"}},"application/xml":{"schema":{"type":"string"}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"export_order","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/clearance":{"get":{"operationId":"get-clearance","summary":"Get the station's clearance","description":"What the station has agreed to carry for this network: each daypart it clears with its days, window and units a day, the shows it carries, the deadline rule for signing, how audio is delivered, the network contact and the station's people with their roles.","tags":["Stations"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"}],"responses":{"200":{"description":"The clearance, shows, deadline rule, contacts and people.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetClearanceResponse"},"example":{"clearance":[{"name":"Summit ROS (ROS)","days":"Mon to Fri","windowStart":360,"windowEnd":1139,"maxPerDay":2,"vehicleId":"demo-ros","unitsByWeekday":[2,2,2,2,2,0,0]}],"shows":[{"id":"show-wd","name":"The Summit Show","kind":"daily","days":[0,1,2,3,4],"hours":5,"breakCount":19,"usualStart":1140,"usualEnd":1439,"localBreaks":[{"hour":2,"breakNumber":5}]}],"deadlineRule":"Friday after the broadcast week, 5:00 PM","networkContact":{"name":"Marcus Bell","title":"Network Traffic Coordinator","email":"affiliates@summit-sample.example","phone":"(555) 010-2440"},"audioDelivery":"Delivered by the network's spot delivery service","people":[{"name":"Dana Ortiz","email":"traffic@ksmt-sample.example","role":"Traffic Director","canSign":true,"lastSeen":"Now","title":"Traffic Director","roleCode":"sign"}]}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"get_clearance","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}/versions/{version}/acknowledge":{"post":{"operationId":"acknowledge-version","summary":"Acknowledge a version","description":"Tells the network the station has the latest version of the week in its log. The network sees who acknowledged it and when.\n\nOnly the latest version can be acknowledged: an older one answers 409 `stale_version` with `latest`, the version to read and acknowledge instead. Acknowledging again changes nothing. The body is an empty JSON object.","tags":["Weeks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"version","in":"path","required":true,"description":"The version to acknowledge: the latest one, as `get-week` or `get-changes` shows it.","schema":{"type":"integer","minimum":0,"maximum":9007199254740991},"example":"3"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes a retry safe: the first request with a key does the work and a repeat answers its first result. Send a new key, such as a fresh UUID, with every new request and the same key only to retry that request. A reused key records nothing new: on the same endpoint it answers the first result even when the body changed, and after a request to another endpoint it usually fails with 502 `upstream_failed`, so send a new key. 8 to 48 letters, digits, dashes or underscores; generated when absent.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,48}$"}}],"responses":{"200":{"description":"The acknowledgement as the network sees it.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcknowledgeVersionResponse"},"example":{"acknowledged":{"version":3,"by":"Automation key","at":"Thu, Sep 24, 10:31 AM","atTs":1790263860}}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.\n\n`read_only`: The affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable.\n\n`insufficient_scope`: The key does not carry the scope this endpoint needs (`read`, `report` or `acknowledge`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"415":{"description":"`unsupported_media_type`: The body was not sent as JSON.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.\n\n`not_saved`: The change could not be saved, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"write","x-key-scope":"acknowledge","x-mcp-tool":"acknowledge_version","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"},{"bucket":"w:{prefix}","limit":120,"windowSeconds":60,"source":"database"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false},"example":{}}}}}},"/affiliations/{id}/weeks/{week}/log-uploads":{"post":{"operationId":"upload-log","summary":"Upload a log","description":"Send the as-run log your automation system saves, in whatever format it uses, and get back proposed matches between its lines and the week's units. **Nothing is saved as an affidavit**: review the matches and apply the ones you accept with `apply-log-matches`.\n\nThe file goes in the multipart field `file`: CSV, TSV, TXT, LOG, ASC, DAT, XLS, XLSX or PDF, at most 10 MB, one broadcast week per file.\n\nThe answer waits up to 60 seconds for the file to be read. A file read in time answers 200 with the matches, and a file that could not be read answers 422 `upload_failed`. A file still being read answers 202 with its id: poll `GET /api/uploads/{upload}` with the same station key every few seconds until `status` is no longer `reading`. The poll answers `{\"status\": \"ready\", \"result\": {...}}`, where `result` is the object a 200 carries in `upload`, or `{\"status\": \"failed\", \"message\": \"...\"}`. Polling uses no upload quota.\n\nEach match has a `group`: `matched` lines can be applied as they are, `check` lines need a look, and `missing` units found no line. Uploads are limited per affiliation and per key, and are not idempotent: sending the same file again is a new upload.\n\nThe key needs the `report` scope on an affiliation that takes reports. Without it, and for a read-only affiliation, the answer is 403 `forbidden` before the file is read; `read_only` comes only from an affiliation that became read-only during the upload.","tags":["Affidavits"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"}],"responses":{"200":{"description":"The file was read within 60 seconds: the proposed matches. Nothing is saved.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadReady"}}}},"202":{"description":"Still reading. Poll GET /api/uploads/{upload} with the same station key until `status` is no longer `reading`; the poll answers `UploadStatus` (#/components/schemas/UploadStatus) and uses no upload quota.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadCreated"},"example":{"upload":{"id":"u1207_20260921_4f1c2a9e8b7d4c3fa0e1d2c3b4a59687","status":"reading"}}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.\n\n`read_only`: The affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`nothing_to_match`: No unit in this week is waiting for a time, so there is nothing a log could match.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"415":{"description":"`unsupported_file`: The uploaded file is not a log export the reader accepts.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"`upload_failed`: The log could not be read within the request. `upload_id` names the upload.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.\n\n`not_saved`: The change could not be saved, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"write","x-key-scope":"report","x-mcp-tool":null,"x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"},{"bucket":"up:a:{affiliation id}","limit":20,"windowSeconds":86400,"source":"database"},{"bucket":"up:p:{prefix}","limit":10,"windowSeconds":3600,"source":"database"},{"bucket":"llm:n:{network cloud}","limit":2000000,"windowSeconds":86400,"source":"database"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","description":"The log file, at most 10 MB."}},"required":["file"]}}}}}},"/log-uploads/{upload}/apply":{"post":{"operationId":"apply-log-matches","summary":"Apply log matches","description":"Records the matches you accept from an uploaded log as affidavits. Send the lines you chose in `items`, or `all_matched: true` to apply every line in the `matched` group. Each item is checked again exactly as a typed report is, and the log is kept as evidence the network can see.\n\nAn upload is applied once. Sending the same choice again answers the first result with `replayed: true`; sending a different choice answers 409 `already_applied`, and the other times go through `report-affidavits`.\n\nAn `outside` item needs a `reason`. `epoch` is `week.epoch` from `get-week`. The key needs the `report` scope on an affiliation that takes reports: without it, and for a read-only affiliation, the answer is 403 `forbidden`.","tags":["Affidavits"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"upload","in":"path","required":true,"description":"The upload id `upload-log` returned. It names the affiliation and the week, so the path needs neither.","schema":{"type":"string","pattern":"^u(\\d{1,10})_(\\d{8})_([a-f0-9]{32})$"},"example":"u1207_20260921_4f1c2a9e8b7d4c3fa0e1d2c3b4a59687"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes a retry safe: the first request with a key does the work and a repeat answers its first result. Send a new key, such as a fresh UUID, with every new request and the same key only to retry that request. A reused key records nothing new: on the same endpoint it answers the first result even when the body changed, and after a request to another endpoint it usually fails with 502 `upstream_failed`, so send a new key. 8 to 48 letters, digits, dashes or underscores; generated when absent.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,48}$"}}],"responses":{"200":{"description":"The evidence upload id, the units saved and the items refused.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyLogMatchesResponse"},"example":{"uploadId":44,"saved":[{"id":"u-01oso1x","date":"2026-09-22","daypart":"ROS","windowStart":360,"windowEnd":1139,"creativeId":"trhd","advertiser":"Trailhead Outfitters","product":"","title":"Hunting Season Kickoff","isci":"SR_TRHD0921060","length":60,"cart":"K90388","status":"aired","addedInVersion":1,"kind":"dated","weekStart":"2026-09-21","rev":2,"copyStatus":"ready","flags":[],"airedAt":462,"airedDate":"2026-09-22","source":"api"}],"errors":[]}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.\n\n`read_only`: The affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`not_ready`: The uploaded log is still being read, or reading it failed, so there is nothing to apply yet.\n\n`stale`: The week changed after the log was read, so the matches were made against an older copy.\n\n`already_applied`: Matches from this upload were already applied, and that result does not cover the units you sent now.\n\n`nothing_to_apply`: The upload has no matched lines to apply.\n\n`week_signed`: The week is signed, so its affidavits can no longer change.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"415":{"description":"`unsupported_media_type`: The body was not sent as JSON.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"`validation_failed`: Items were refused. With `all_or_nothing` true nothing was saved; `errors` says why for each item.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.\n\n`not_saved`: The change could not be saved, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"confirm","x-key-scope":"report","x-mcp-tool":"apply_log_matches","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"},{"bucket":"w:{prefix}","limit":120,"windowSeconds":60,"source":"database"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"epoch":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"week.epoch from get_week."},"items":{"minItems":1,"maxItems":2000,"type":"array","items":{"type":"object","properties":{"unit_id":{"type":"string","minLength":1,"maxLength":20},"status":{"type":"string","enum":["aired","outside"]},"aired_date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"aired_at":{"anyOf":[{"type":"integer","minimum":0,"maximum":1439},{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"}],"description":"Minutes after midnight (0 to 1439) or HH:MM, in the station's time zone."},"reason":{"description":"Required for outside: 1 to 300 characters once trimmed, else the apply answers 422 validation_failed. Ignored for aired.","type":"string","maxLength":2000}},"required":["unit_id","status","aired_date","aired_at"],"additionalProperties":false}},"all_matched":{"description":"true applies every matched line; send it instead of items.","type":"boolean","const":true},"all_or_nothing":{"type":"boolean","const":false}},"required":["epoch"],"additionalProperties":false},"example":{"epoch":1,"items":[{"unit_id":"u-01oso1x","status":"aired","aired_date":"2026-09-22","aired_at":462}]}}}}}},"/affiliations/{id}/weeks/{week}/affidavits":{"post":{"operationId":"report-affidavits","summary":"Report affidavits","description":"Records what happened to a batch of ROS units: the time each aired, an airing outside its window, or a miss. The network's invoices list these reports under each unit as proof, and a change after an invoice is sent becomes an adjustment, so every item is checked:\n\n- `aired`: `aired_date` is the unit's day (a weekly unit: one of its allowed days whose window has closed) and `aired_at` is inside its window.\n- `outside`: aired on a day of the broadcast week but not inside the window on one of the unit's own days, with a `reason` of 1 to 300 characters.\n- `not_aired`: with a `reason`.\n- `pending`: clears an earlier report, until the week is signed.\n\nA unit takes a time only once its window has closed. Send each unit's `rev` and the week's `epoch` from `get-week` unchanged. With `all_or_nothing` false (the default) the valid items are saved and `errors` lists the others with a code each; with `true` any refusal saves nothing and answers 422. At most 2,000 items per call, each unit once.","tags":["Affidavits"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes a retry safe: the first request with a key does the work and a repeat answers its first result. Send a new key, such as a fresh UUID, with every new request and the same key only to retry that request. A reused key records nothing new: on the same endpoint it answers the first result even when the body changed, and after a request to another endpoint it usually fails with 502 `upstream_failed`, so send a new key. 8 to 48 letters, digits, dashes or underscores; generated when absent.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,48}$"}}],"responses":{"200":{"description":"The units saved, the items refused and the week's new state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportAffidavitsResponse"},"example":{"saved":[{"id":"u-01oso1x","date":"2026-09-22","daypart":"ROS","windowStart":360,"windowEnd":1139,"creativeId":"trhd","advertiser":"Trailhead Outfitters","product":"","title":"Hunting Season Kickoff","isci":"SR_TRHD0921060","length":60,"cart":"K90388","status":"aired","addedInVersion":1,"kind":"dated","weekStart":"2026-09-21","rev":2,"copyStatus":"ready","flags":[],"airedAt":462,"airedDate":"2026-09-22","source":"api"}],"errors":[],"week":{"state":"in_progress","epoch":1,"rev":2}}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.\n\n`read_only`: The affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable.\n\n`insufficient_scope`: The key does not carry the scope this endpoint needs (`read`, `report` or `acknowledge`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`epoch_changed`: The network reopened or changed the week after you read it, so the `epoch` you sent is out of date.\n\n`week_signed`: The week is signed, so its affidavits can no longer change.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"415":{"description":"`unsupported_media_type`: The body was not sent as JSON.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"`validation_failed`: Items were refused. With `all_or_nothing` true nothing was saved; `errors` says why for each item.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.\n\n`not_saved`: The change could not be saved, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"confirm","x-key-scope":"report","x-mcp-tool":"report_affidavits","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"},{"bucket":"w:{prefix}","limit":120,"windowSeconds":60,"source":"database"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"epoch":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"week.epoch from get_week."},"all_or_nothing":{"description":"true saves nothing when any item fails; the default false saves the valid items.","type":"boolean"},"items":{"minItems":1,"type":"array","items":{"type":"object","properties":{"unit_id":{"type":"string","minLength":1,"maxLength":20},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991},"status":{"type":"string","enum":["aired","outside","not_aired","pending"]},"aired_date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"aired_at":{"anyOf":[{"type":"integer","minimum":0,"maximum":1439},{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"}],"description":"Minutes after midnight (0 to 1439) or HH:MM, in the station's time zone."},"reason":{"type":"string","minLength":1,"maxLength":300}},"required":["unit_id","rev","status"],"additionalProperties":false}}},"required":["epoch","items"],"additionalProperties":false},"example":{"epoch":1,"items":[{"unit_id":"u-01oso1x","rev":1,"status":"aired","aired_date":"2026-09-22","aired_at":"07:42"}]}}}}}},"/affiliations/{id}/weeks/{week}/sign":{"post":{"operationId":"sign-week","summary":"Sign a week","description":"Signing a week is an attestation that the affidavits are true, so a person does it, signed in to the portal. **No key can sign**: this route always answers 403 `key_cannot_sign` and changes nothing.\n\nOver MCP, a person with the sign role gets `signUrl`, a link to the portal's signing panel for that week, and signs there. A week can be signed once its broadcast week has ended and nothing is pending.","tags":["Affidavits"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"}],"responses":{"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"x-effect":"confirm","x-key-scope":null,"x-mcp-tool":"sign_week","x-rate-limit":[]}},"/affiliations/{id}/weeks":{"get":{"operationId":"list-weeks","summary":"List weeks","description":"The weeks the network has published for this station, newest first, at most 26 (about six months): each week's latest version, state, deadline and how many units have been reported. There is no paging: older weeks stay readable by their Monday with `get-week`.\n\n`state` moves from `published` to `in_progress` as times come in, to `ready_to_sign` once the broadcast week has ended and nothing is pending, and to `signed`.","tags":["Weeks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"}],"responses":{"200":{"description":"Up to 26 weeks, newest first.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWeeksResponse"},"example":{"weeks":[{"start":"2026-09-21","version":3,"state":"in_progress","due":"Fri, Oct 2, 5:00 PM","dueAt":1790978400,"publishedAt":"Thu, Sep 24, 9:12 AM","publishedAtTs":1790259120,"unitsTotal":9,"unitsReported":2,"signed":false}]}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":"list_weeks","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}},"/affiliations/{id}/weeks/{week}/show-airings":{"post":{"operationId":"report-show-airings","summary":"Report show airings","description":"Certifies each show day: whether the station carried the show, the hours it aired, and whether the network's spots aired unedited.\n\n- `carried`: `start` and `end` (an `end` before `start` runs past midnight), and either `unedited: true` or `dropped`, the breaks the station did not air, each with its break number, counted straight through the show, the hour that holds it and a reason: `[{\"hour\": 5, \"breakNumber\": 18, \"reason\": \"...\"}]`. A dropped break must be a network break of that day, not a local one.\n- `not_carried`: with a `reason`.\n- `pending`: clears an earlier report, until the week is signed.\n\nA show day can be certified once the station's usual hours for the show are over, or, when the station gave none, once the day has ended in its time zone. `aired_date` is for a show aired on another day, within 7 days after its date. Send each day's `rev`, and the week's `epoch` and `version`, from `get-week` unchanged: if the show's breaks were renumbered in a newer version, the report answers 409 `stale_version` and saves nothing. A saved dropped break that `get-week` shows as `unreadable` or `unavailable` is never sent back: report the day's dropped breaks by their current numbers. The batch rules are those of `report-affidavits`.","tags":["Affidavits"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes a retry safe: the first request with a key does the work and a repeat answers its first result. Send a new key, such as a fresh UUID, with every new request and the same key only to retry that request. A reused key records nothing new: on the same endpoint it answers the first result even when the body changed, and after a request to another endpoint it usually fails with 502 `upstream_failed`, so send a new key. 8 to 48 letters, digits, dashes or underscores; generated when absent.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,48}$"}}],"responses":{"200":{"description":"The show days saved, the items refused and the week's new state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportShowAiringsResponse"},"example":{"saved":[{"key":"srn-ksmt:show-wd:2026-09-23","showId":"show-wd","date":"2026-09-23","status":"carried","start":1140,"end":1439,"unedited":true,"source":"api","airedDate":"2026-09-23","rev":2}],"errors":[],"week":{"state":"in_progress","epoch":1,"rev":2}}}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.\n\n`read_only`: The affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable.\n\n`insufficient_scope`: The key does not carry the scope this endpoint needs (`read`, `report` or `acknowledge`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`epoch_changed`: The network reopened or changed the week after you read it, so the `epoch` you sent is out of date.\n\n`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.\n\n`week_signed`: The week is signed, so its affidavits can no longer change.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"415":{"description":"`unsupported_media_type`: The body was not sent as JSON.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"`validation_failed`: Items were refused. With `all_or_nothing` true nothing was saved; `errors` says why for each item.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.\n\n`not_saved`: The change could not be saved, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"confirm","x-key-scope":"report","x-mcp-tool":"report_show_airings","x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"},{"bucket":"w:{prefix}","limit":120,"windowSeconds":60,"source":"database"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"description":"The week version whose break numbers you used. Send the version you read: if the show's breaks were renumbered since, the report answers stale_version and saves nothing.","type":"integer","minimum":1,"maximum":9007199254740991},"epoch":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"week.epoch from get_week."},"all_or_nothing":{"description":"true saves nothing when any item fails; the default false saves the valid items.","type":"boolean"},"items":{"minItems":1,"type":"array","items":{"type":"object","properties":{"vehicle_id":{"type":"string","minLength":1,"maxLength":20},"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991},"status":{"type":"string","enum":["carried","not_carried","pending"]},"start":{"anyOf":[{"type":"integer","minimum":0,"maximum":1439},{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"}],"description":"Minutes after midnight (0 to 1439) or HH:MM, in the station's time zone."},"end":{"anyOf":[{"type":"integer","minimum":0,"maximum":1439},{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"}],"description":"Minutes after midnight (0 to 1439) or HH:MM, in the station's time zone."},"aired_date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"unedited":{"type":"boolean"},"dropped":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"integer","minimum":1,"maximum":24},"breakNumber":{"type":"integer","minimum":1,"maximum":1000},"reason":{"type":"string","minLength":1,"maxLength":300}},"required":["hour","breakNumber","reason"],"additionalProperties":false}},"reason":{"type":"string","minLength":1,"maxLength":300}},"required":["vehicle_id","date","rev","status"],"additionalProperties":false}}},"required":["epoch","items"],"additionalProperties":false},"example":{"epoch":1,"version":3,"items":[{"vehicle_id":"show-wd","date":"2026-09-23","rev":1,"status":"carried","start":"19:00","end":"23:59","unedited":true}]}}}}}},"/affiliations/{id}/weeks/{week}/documents/{doc}":{"get":{"operationId":"get-document","summary":"Get a printable document","description":"One of the week's printed documents as a complete HTML page, ready to print or save as PDF: the certificate of performance, the commercial schedule or the copy playlist for one vehicle.\n\nThe page is served with a strict content policy: it loads no scripts, and images only from the network's own asset host.","tags":["Copy and audio"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The affiliation id from `GET /v1/affiliations`: one station's relationship with one network.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f]+$"},"example":"srn-ksmt"},{"name":"week","in":"path","required":true,"description":"Monday of the broadcast week, `YYYY-MM-DD`. Any other day answers 400.","schema":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"example":"2026-09-21"},{"name":"doc","in":"path","required":true,"description":"Which document.","schema":{"type":"string","enum":["certificate","commercial_schedule","copy_playlist"]},"example":"certificate"},{"name":"vehicle_id","in":"query","required":true,"description":"The vehicle the document is for, from the affiliation's `vehicles`.","schema":{"type":"string","minLength":1,"maxLength":20},"example":"show-wd"}],"responses":{"200":{"description":"The document as an HTML page.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"text/html":{"schema":{"type":"string"},"example":"<!doctype html><html><head><title>Certificate of Performance</title></head><body><h1>Certificate of Performance</h1>...</body></html>"}}},"400":{"description":"`bad_request`: The request is not valid: a path value, a query value or a body field is missing, malformed or out of range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"`invalid_principal`: The key is missing, malformed, unknown, revoked or expired; all of these answer the same way.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"`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.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited`: Too many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"`server_error`: Something went wrong on our side. The answer never carries internal detail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"`upstream_failed`: The service behind the API answered in a way the portal could not use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"`unavailable`: The service is not available right now: it could not be reached, or it is not set up yet.\n\n`demo_mode`: this copy of the portal runs on demo data; the body is exactly {\"error\":\"demo_mode\"}.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorEnvelope"},{"$ref":"#/components/schemas/DemoMode"}]}}}}},"x-effect":"read","x-key-scope":"read","x-mcp-tool":null,"x-rate-limit":[{"bucket":"portal:auth","limit":10,"windowSeconds":60,"source":"portal"},{"bucket":"portal:key","limit":60,"windowSeconds":60,"source":"portal"},{"bucket":"key:{prefix}","limit":600,"windowSeconds":600,"source":"database"}]}}},"webhooks":{"version.published":{"post":{"summary":"A week is published or revised","description":"Signed with X-Aff-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" keyed with the webhook secret>. See /docs/webhooks.","parameters":[{"name":"X-Aff-Event","in":"header","required":true,"schema":{"type":"string","const":"version.published"}},{"name":"X-Aff-Delivery","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Aff-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Any 2xx answer marks the delivery delivered; anything else is retried."}}}},"audio.arrived":{"post":{"summary":"Audio that was pending arrives","description":"Signed with X-Aff-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" keyed with the webhook secret>. See /docs/webhooks.","parameters":[{"name":"X-Aff-Event","in":"header","required":true,"schema":{"type":"string","const":"audio.arrived"}},{"name":"X-Aff-Delivery","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Aff-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Any 2xx answer marks the delivery delivered; anything else is retried."}}}},"deadline.approaching":{"post":{"summary":"Affidavits are due in two days","description":"Signed with X-Aff-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" keyed with the webhook secret>. See /docs/webhooks.","parameters":[{"name":"X-Aff-Event","in":"header","required":true,"schema":{"type":"string","const":"deadline.approaching"}},{"name":"X-Aff-Delivery","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Aff-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Any 2xx answer marks the delivery delivered; anything else is retried."}}}},"week.reopened":{"post":{"summary":"The network reopens a signed week","description":"Signed with X-Aff-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" keyed with the webhook secret>. See /docs/webhooks.","parameters":[{"name":"X-Aff-Event","in":"header","required":true,"schema":{"type":"string","const":"week.reopened"}},{"name":"X-Aff-Delivery","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Aff-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Any 2xx answer marks the delivery delivered; anything else is retried."}}}},"ping":{"post":{"summary":"A test delivery","description":"Signed with X-Aff-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" keyed with the webhook secret>. See /docs/webhooks.","parameters":[{"name":"X-Aff-Event","in":"header","required":true,"schema":{"type":"string","const":"ping"}},{"name":"X-Aff-Delivery","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Aff-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Any 2xx answer marks the delivery delivered; anything else is retried."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A station key, `rwaff_k_` followed by its id and secret, made under API and MCP in the portal."}},"headers":{"X-Request-Id":{"description":"Identifies the request. A write's Idempotency-Key when it sent one. Quote it when you ask about a request.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"The size of the key's burst bucket (60).","schema":{"type":"integer","minimum":0}},"RateLimit-Remaining":{"description":"Requests left in the key's burst bucket; 0 on every 429.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the bucket is full again; on a 429, the same as Retry-After.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":0}},"Content-Disposition":{"description":"attachment, with the file name.","schema":{"type":"string"}}},"schemas":{"Affiliation":{"type":"object","properties":{"id":{"type":"string","minLength":1},"networkName":{"type":"string"},"networkShort":{"type":"string"},"stationCall":{"type":"string"},"stationName":{"type":"string"},"market":{"type":"string"},"lane":{"type":"string","enum":["portal","radioworkflow"]},"canSign":{"type":"boolean"},"canReport":{"type":"boolean"},"role":{"type":"string","enum":["view","report","sign"]},"readOnly":{"type":"boolean"},"timezone":{"type":"string"},"networkContact":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}},"required":["name","title","email","phone"],"additionalProperties":false},"deadlineRule":{"type":"string"},"audioDelivery":{"type":"string"},"vehicles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"name":{"type":"string"},"kind":{"type":"string","enum":["show","ros"]}},"required":["id","name","kind"],"additionalProperties":false}}},"required":["id","networkName","networkShort","stationCall","stationName","market","lane","canSign","canReport","role","readOnly","timezone","networkContact","deadlineRule","audioDelivery","vehicles"],"additionalProperties":false},"WeekSummary":{"type":"object","properties":{"start":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"state":{"type":"string","enum":["signed","ready_to_sign","in_progress","published"]},"due":{"type":"string"},"dueAt":{"type":"integer","minimum":0,"maximum":9007199254740991},"publishedAt":{"type":"string"},"publishedAtTs":{"type":"integer","minimum":0,"maximum":9007199254740991},"unitsTotal":{"type":"integer","minimum":0,"maximum":9007199254740991},"unitsReported":{"type":"integer","minimum":0,"maximum":9007199254740991},"signed":{"type":"boolean"}},"required":["start","version","state","due","dueAt","publishedAt","publishedAtTs","unitsTotal","unitsReported","signed"],"additionalProperties":false},"Now":{"type":"object","properties":{"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"minutes":{"type":"integer","minimum":0,"maximum":1439},"ts":{"type":"integer","minimum":0,"maximum":9007199254740991},"timezone":{"type":"string"}},"required":["date","minutes","ts","timezone"],"additionalProperties":false},"OrderWeek":{"type":"object","properties":{"start":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"days":{"type":"array","items":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"}},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"state":{"type":"string","enum":["signed","ready_to_sign","in_progress","published"]},"due":{"type":"string"},"dueAt":{"type":"integer","minimum":0,"maximum":9007199254740991},"epoch":{"type":"integer","minimum":0,"maximum":9007199254740991},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991},"published":{"type":"boolean"},"signed":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"at":{"type":"string"},"atTs":{"type":"integer","minimum":0,"maximum":9007199254740991},"via":{"type":"string","enum":["portal","mcp"]},"late":{"type":"boolean"}},"required":["name","title","at","atTs","via","late"],"additionalProperties":false},"shows":{"type":"array","items":{"$ref":"#/components/schemas/ShowWeek"}},"rosName":{"type":"string"},"units":{"type":"array","items":{"$ref":"#/components/schemas/AffiliateUnit"}},"removed":{"type":"array","items":{"$ref":"#/components/schemas/AffiliateUnit"}},"revisions":{"type":"array","items":{"$ref":"#/components/schemas/Revision"}},"acknowledgedVersion":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["start","days","version","state","due","dueAt","epoch","rev","published","shows","units","removed","revisions","acknowledgedVersion"],"additionalProperties":false},"AffiliateUnit":{"type":"object","properties":{"id":{"type":"string","minLength":1},"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"daypart":{"type":"string"},"windowStart":{"type":"integer","minimum":0,"maximum":1439},"windowEnd":{"type":"integer","minimum":0,"maximum":1439},"creativeId":{"type":"string","minLength":1},"advertiser":{"type":"string"},"product":{"type":"string"},"title":{"type":"string"},"isci":{"type":"string"},"length":{"type":"integer","minimum":0,"maximum":9007199254740991},"cart":{"type":"string"},"status":{"type":"string","enum":["pending","aired","outside","not_aired"]},"airedAt":{"type":"integer","minimum":0,"maximum":1439},"airedDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"reason":{"type":"string"},"source":{"type":"string","enum":["portal","log_upload","rw_log","network","api","mcp"]},"addedInVersion":{"type":"integer","minimum":0,"maximum":9007199254740991},"removedInVersion":{"type":"integer","minimum":0,"maximum":9007199254740991},"kind":{"type":"string","enum":["dated","weekly"]},"weekStart":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"allowedDays":{"type":"array","items":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"}},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991},"copyStatus":{"type":"string","enum":["ready","to_follow"]},"instructions":{"type":"string"},"rwState":{"type":"string","enum":["confirmed","not_confirmed"]},"flags":{"type":"array","items":{"type":"string"}}},"required":["id","date","daypart","windowStart","windowEnd","creativeId","advertiser","product","title","isci","length","cart","status","addedInVersion","kind","weekStart","rev","copyStatus","flags"],"additionalProperties":false},"ShowWeek":{"type":"object","properties":{"show":{"$ref":"#/components/schemas/ShowVehicle"},"showNumber":{"type":"string"},"spots":{"type":"array","items":{"$ref":"#/components/schemas/ShowSpot"}},"removed":{"type":"array","items":{"$ref":"#/components/schemas/ShowSpot"}},"airings":{"type":"array","items":{"$ref":"#/components/schemas/ShowAiring"}},"breaks":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"integer","minimum":1,"maximum":24},"breakNumber":{"type":"integer","minimum":1,"maximum":1000},"local":{"type":"boolean"}},"required":["hour","breakNumber","local"],"additionalProperties":false}},"excludedDays":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"reason":{"type":"string"}},"required":["date","reason"],"additionalProperties":false}}},"required":["show","showNumber","spots","removed","airings","breaks","excludedDays"],"additionalProperties":false},"ShowSpot":{"type":"object","properties":{"id":{"type":"string","minLength":1},"showId":{"type":"string","minLength":1},"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"hour":{"type":"integer","minimum":1,"maximum":24},"breakNumber":{"type":"integer","minimum":1,"maximum":1000},"breakNote":{"type":"string","enum":["Not on the break map","Now a local break"]},"position":{"type":"integer","minimum":0,"maximum":9007199254740991},"creativeId":{"type":"string","minLength":1},"advertiser":{"type":"string"},"product":{"type":"string"},"title":{"type":"string"},"isci":{"type":"string"},"length":{"type":"integer","minimum":0,"maximum":9007199254740991},"addedInVersion":{"type":"integer","minimum":0,"maximum":9007199254740991},"removedInVersion":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","showId","date","hour","breakNumber","position","creativeId","advertiser","product","title","isci","length","addedInVersion"],"additionalProperties":false},"ShowAiring":{"type":"object","properties":{"key":{"type":"string","minLength":1},"showId":{"type":"string","minLength":1},"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"status":{"type":"string","enum":["pending","carried","not_carried"]},"start":{"type":"integer","minimum":0,"maximum":1439},"end":{"type":"integer","minimum":0,"maximum":1439},"unedited":{"type":"boolean"},"dropped":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"hour":{"type":"integer","minimum":1,"maximum":24},"breakNumber":{"type":"integer","minimum":1,"maximum":1000},"reason":{"type":"string"}},"required":["hour","breakNumber","reason"],"additionalProperties":false},{"type":"object","properties":{"unavailable":{"type":"boolean","const":true},"hour":{"type":"integer","minimum":1,"maximum":24},"reason":{"type":"string"}},"required":["unavailable","hour","reason"],"additionalProperties":false},{"type":"object","properties":{"unreadable":{"type":"boolean","const":true},"reason":{"type":"string"}},"required":["unreadable","reason"],"additionalProperties":false}]}},"reason":{"type":"string"},"source":{"type":"string","enum":["portal","log_upload","rw_log","network","api","mcp"]},"airedDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["key","showId","date","status","rev"],"additionalProperties":false},"ShowVehicle":{"type":"object","properties":{"id":{"type":"string","minLength":1},"name":{"type":"string"},"kind":{"type":"string","enum":["daily","weekend"]},"days":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"hours":{"type":"integer","minimum":0,"maximum":24},"breakCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"usualStart":{"type":"integer","minimum":0,"maximum":1439},"usualEnd":{"type":"integer","minimum":0,"maximum":1439},"localBreaks":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"integer","minimum":1,"maximum":24},"breakNumber":{"type":"integer","minimum":1,"maximum":1000}},"required":["hour","breakNumber"],"additionalProperties":false}}},"required":["id","name","kind","days","hours","breakCount","localBreaks"],"additionalProperties":false},"Creative":{"type":"object","properties":{"id":{"type":"string","minLength":1},"advertiser":{"type":"string"},"title":{"type":"string"},"isci":{"type":"string"},"length":{"type":"integer","minimum":0,"maximum":9007199254740991},"flightStart":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"flightEnd":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"audioReady":{"type":"boolean"},"replaces":{"type":"string"},"instructions":{"type":"string"},"cart":{"type":"string"},"timesThisWeek":{"type":"integer","minimum":0,"maximum":9007199254740991},"mediaStatus":{"type":"string","enum":["ready","pending","external"]},"audioUrl":{"type":"string","format":"uri"},"audioUrlExpiresAt":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","advertiser","title","isci","length","flightStart","flightEnd","audioReady","cart","timesThisWeek","mediaStatus"],"additionalProperties":false},"Revision":{"type":"object","properties":{"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"publishedAt":{"type":"string"},"publishedAtTs":{"type":"integer","minimum":0,"maximum":9007199254740991},"summary":{"type":"string"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/Change"}},"acknowledged":{"type":"object","properties":{"by":{"type":"string"},"at":{"type":"string"},"atTs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["by","at","atTs"],"additionalProperties":false}},"required":["version","publishedAt","publishedAtTs","summary","changes"],"additionalProperties":false},"Change":{"type":"object","properties":{"kind":{"type":"string","enum":["initial","added","removed","changed","copy"]},"vehicle":{"type":"string"},"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"where":{"type":"string"},"advertiser":{"type":"string"},"isci":{"type":"string"},"length":{"type":"integer","minimum":0,"maximum":9007199254740991},"detail":{"type":"string"},"unitId":{"type":"string","minLength":1}},"required":["kind","detail"],"additionalProperties":false},"DaypartClearance":{"type":"object","properties":{"name":{"type":"string"},"days":{"type":"string"},"windowStart":{"type":"integer","minimum":0,"maximum":1439},"windowEnd":{"type":"integer","minimum":0,"maximum":1439},"maxPerDay":{"type":"integer","minimum":0,"maximum":9007199254740991},"vehicleId":{"type":"string","minLength":1},"unitsByWeekday":{"minItems":7,"maxItems":7,"type":"array","items":{"type":"integer","minimum":0,"maximum":9007199254740991}}},"required":["name","days","windowStart","windowEnd","maxPerDay","vehicleId","unitsByWeekday"],"additionalProperties":false},"Person":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"},"canSign":{"type":"boolean"},"lastSeen":{"type":"string"},"title":{"type":"string"},"roleCode":{"type":"string","enum":["view","report","sign"]}},"required":["name","email","role","canSign","lastSeen","title","roleCode"],"additionalProperties":false},"ItemError":{"type":"object","properties":{"unit_id":{"type":"string","minLength":1},"vehicle_id":{"type":"string","minLength":1},"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"error":{"type":"string","enum":["rev_conflict","not_reportable","outside_window","invalid_time","invalid_date","reason_required","removed","not_found","break_not_found","local_break"]},"message":{"type":"string"},"current":{"anyOf":[{"$ref":"#/components/schemas/AffiliateUnit"},{"$ref":"#/components/schemas/ShowAiring"}]}},"required":["error","message"],"additionalProperties":false},"UploadResult":{"type":"object","properties":{"id":{"type":"string","pattern":"^u(\\d{1,10})_(\\d{8})_([a-f0-9]{32})$"},"fileName":{"type":"string"},"linesRead":{"type":"integer","minimum":0,"maximum":9007199254740991},"linesIgnored":{"type":"integer","minimum":0,"maximum":9007199254740991},"columns":{"type":"array","items":{"type":"string"}},"matches":{"type":"array","items":{"type":"object","properties":{"unitId":{"type":"string","minLength":1},"group":{"type":"string","enum":["matched","check","missing"]},"rowRef":{"type":"string","pattern":"^r[0-9]+$"},"raw":{"type":"string"},"status":{"type":"string","enum":["aired","outside"]},"airedDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"airedAt":{"type":"integer","minimum":0,"maximum":1439},"confidence":{"type":"number","minimum":0,"maximum":1},"why":{"type":"string"}},"required":["unitId","group","confidence","why"],"additionalProperties":false}}},"required":["id","fileName","linesRead","linesIgnored","columns","matches"],"additionalProperties":false},"UploadCreated":{"type":"object","properties":{"upload":{"type":"object","properties":{"id":{"type":"string","pattern":"^u(\\d{1,10})_(\\d{8})_([a-f0-9]{32})$"},"status":{"type":"string","const":"reading"}},"required":["id","status"],"additionalProperties":false}},"required":["upload"],"additionalProperties":false},"UploadReady":{"type":"object","properties":{"upload":{"$ref":"#/components/schemas/UploadResult"}},"required":["upload"],"additionalProperties":false},"UploadStatus":{"type":"object","properties":{"status":{"type":"string","enum":["reading","ready","failed"]},"result":{"$ref":"#/components/schemas/UploadResult"},"message":{"type":"string"}},"required":["status"],"additionalProperties":false},"WebhookPayload":{"type":"object","properties":{"event_id":{"type":"string","description":"evt_ and 32 hex digits, the same for every webhook of one event."},"type":{"type":"string","enum":["version.published","audio.arrived","deadline.approaching","week.reopened","ping"]},"created":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unix seconds when the event happened."},"network":{"type":"string"},"affiliation_id":{"type":"string"},"station":{"type":"string"},"week":{"type":"string","description":"Monday of the broadcast week, or empty for ping."},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["event_id","type","created","network","affiliation_id","station","week","version","data"],"additionalProperties":false},"ListAffiliationsResponse":{"type":"object","properties":{"affiliations":{"type":"array","items":{"$ref":"#/components/schemas/Affiliation"}}},"required":["affiliations"],"additionalProperties":false},"GetWeekResponse":{"type":"object","properties":{"affiliation":{"$ref":"#/components/schemas/Affiliation"},"now":{"$ref":"#/components/schemas/Now"},"week":{"$ref":"#/components/schemas/OrderWeek"},"creatives":{"type":"array","items":{"$ref":"#/components/schemas/Creative"}},"shows":{"type":"array","items":{"$ref":"#/components/schemas/ShowVehicle"}},"notModified":{"type":"boolean"}},"required":["affiliation","now","week","creatives","shows","notModified"],"additionalProperties":false},"GetStatusResponse":{"type":"object","properties":{"now":{"$ref":"#/components/schemas/Now"},"outstanding":{"type":"object","properties":{"to_report":{"type":"integer","minimum":0,"maximum":9007199254740991},"to_sign":{"type":"array","items":{"type":"object","properties":{"week":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"due":{"type":"string"},"dueAt":{"type":"integer","minimum":0,"maximum":9007199254740991},"overdue":{"type":"boolean"}},"required":["week","due","dueAt","overdue"],"additionalProperties":false}},"to_acknowledge":{"type":"array","items":{"type":"object","properties":{"week":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["week","version"],"additionalProperties":false}},"audio_pending":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["to_report","to_sign","to_acknowledge","audio_pending"],"additionalProperties":false},"nextDeadline":{"type":"object","properties":{"week":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"due":{"type":"string"},"dueAt":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["week","due","dueAt"],"additionalProperties":false}},"required":["now","outstanding"],"additionalProperties":false},"GetChangesResponse":{"type":"object","properties":{"revisions":{"type":"array","items":{"$ref":"#/components/schemas/Revision"}}},"required":["revisions"],"additionalProperties":false},"ListCreativesResponse":{"type":"object","properties":{"creatives":{"type":"array","items":{"$ref":"#/components/schemas/Creative"}}},"required":["creatives"],"additionalProperties":false},"GetClearanceResponse":{"type":"object","properties":{"clearance":{"type":"array","items":{"$ref":"#/components/schemas/DaypartClearance"}},"shows":{"type":"array","items":{"$ref":"#/components/schemas/ShowVehicle"}},"deadlineRule":{"type":"string"},"networkContact":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}},"required":["name","title","email","phone"],"additionalProperties":false},"audioDelivery":{"type":"string"},"people":{"type":"array","items":{"$ref":"#/components/schemas/Person"}}},"required":["clearance","shows","deadlineRule","networkContact","audioDelivery","people"],"additionalProperties":false},"AcknowledgeVersionResponse":{"type":"object","properties":{"acknowledged":{"type":"object","properties":{"by":{"type":"string"},"at":{"type":"string"},"atTs":{"type":"integer","minimum":0,"maximum":9007199254740991},"version":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["by","at","atTs","version"],"additionalProperties":false}},"required":["acknowledged"],"additionalProperties":false},"ApplyLogMatchesResponse":{"type":"object","properties":{"uploadId":{"type":"integer","minimum":0,"maximum":9007199254740991},"saved":{"type":"array","items":{"$ref":"#/components/schemas/AffiliateUnit"}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ItemError"}},"replayed":{"type":"boolean","const":true}},"required":["uploadId","saved","errors"],"additionalProperties":false},"ReportAffidavitsResponse":{"type":"object","properties":{"saved":{"type":"array","items":{"$ref":"#/components/schemas/AffiliateUnit"}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ItemError"}},"week":{"type":"object","properties":{"state":{"type":"string","enum":["signed","ready_to_sign","in_progress","published"]},"epoch":{"type":"integer","minimum":0,"maximum":9007199254740991},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["state","epoch","rev"],"additionalProperties":false}},"required":["saved","errors","week"],"additionalProperties":false},"ListWeeksResponse":{"type":"object","properties":{"weeks":{"maxItems":26,"type":"array","items":{"$ref":"#/components/schemas/WeekSummary"}}},"required":["weeks"],"additionalProperties":false},"ReportShowAiringsResponse":{"type":"object","properties":{"saved":{"type":"array","items":{"$ref":"#/components/schemas/ShowAiring"}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ItemError"}},"week":{"type":"object","properties":{"state":{"type":"string","enum":["signed","ready_to_sign","in_progress","published"]},"epoch":{"type":"integer","minimum":0,"maximum":9007199254740991},"rev":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["state","epoch","rev"],"additionalProperties":false}},"required":["saved","errors","week"],"additionalProperties":false},"ErrorEnvelope":{"type":"object","description":"Every refusal. `code` says what happened and `message` is one fixed sentence; the extras come with the codes that use them.","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["bad_request","invalid_principal","forbidden","read_only","key_cannot_sign","insufficient_scope","not_found","stale_version","epoch_changed","week_signed","sign_blocked","conflict","stale","already_applied","not_ready","nothing_to_match","nothing_to_apply","payload_too_large","unsupported_media_type","unsupported_file","validation_failed","upload_failed","rate_limited","not_saved","server_error","upstream_failed","unavailable"]},"message":{"type":"string"},"errors":{"type":"array","description":"With 422 validation_failed: why each item was refused.","items":{"$ref":"#/components/schemas/ItemError"}},"latest":{"type":"integer","minimum":0,"description":"With 409 stale_version: the version to read and acknowledge."},"rev":{"type":"integer","minimum":0,"description":"With some 409 answers: the current revision."},"epoch":{"type":"integer","minimum":0,"description":"With 409 epoch_changed or stale: the week's current epoch."},"missing":{"type":"object","description":"With 409 sign_blocked: what is still pending.","properties":{"units":{"type":"array","items":{"type":"string"}},"airings":{"type":"array","items":{"type":"object","properties":{"vehicle_id":{"type":"string"},"date":{"type":"string","format":"date"}},"required":["vehicle_id","date"]}}}},"retry_after":{"type":"integer","minimum":0,"description":"With 429: seconds to wait, the same as the Retry-After header."},"upload_id":{"type":"string","description":"With 422 upload_failed: the upload that could not be read."}},"required":["code","message"]}},"required":["error"]},"DemoMode":{"type":"object","description":"The whole body of every answer while the portal runs on demo data.","properties":{"error":{"const":"demo_mode"}},"required":["error"],"additionalProperties":false}}}}