Apply log matches
confirmTurn chosen matches from an upload into affidavits.
POST
https://affiliates.radioworkflow.com/api/v1/log-uploads/{upload}/applyConfirm: this records an affidavit, the proof behind the network's invoices.
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.
An 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.
An 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.
Authentication
Send a station key as
Authorization: Bearer rwaff_k_.... A key with the report scope. Keys and scopesPath parameters
uploadstringrequiredThe upload id
upload-log returned. It names the affiliation and the week, so the path needs neither.Request body (JSON)
epochintegerat least 0requiredweek.epoch from get-week, sent back unchanged. It moves when the network reopens the week, so a report built on an older read is refused with 409 epoch_changed.itemsarray of objectsoptionalThe matches to apply, 1 to 2,000. Send
items or all_matched, not both.unit_idstring1 to 20 charactersrequiredThe unit's
id.statusenumrequiredWhat the log shows.
airedoutsideaired_datedaterequiredThe day it aired,
YYYY-MM-DD.aired_attimerequiredMinutes after midnight (0 to 1439) or
HH:MM, in the station's time zone.reasonstring0 to 2000 charactersoptionalRequired for
outside: why it aired outside its window, 1 to 300 characters once trimmed. Any other outside reason refuses the whole apply with 422 validation_failed (reason_required). Ignored for aired.all_matchedbooleanoptionaltrue applies every line in the matched group. Send it instead of items.trueall_or_nothingbooleanoptionalOnly
false is accepted: the valid items are saved and the rest listed in errors.falseIdempotency
Send a new
Idempotency-Key header with every request (8 to 48 letters, digits, dashes or underscores; a UUID is a good choice). To retry a request that timed out, send it again with the same key: if the first attempt saved, the repeat answers its first result instead of recording twice. A reused key records nothing new: here it answers its first result even when the body changed, and after a request to another endpoint it usually fails with 502 upstream_failed. Never reuse one for a new request. How it worksResponse
200
application/jsonThe evidence upload id, the units saved and the items refused.Response schema
AffiliateUnit27 fields
idstringrequireddatedaterequireddaypartstringrequiredwindowStartinteger (0 to 1439)requiredwindowEndinteger (0 to 1439)requiredcreativeIdstringrequiredadvertiserstringrequiredproductstringrequiredtitlestringrequirediscistringrequiredlengthintegerrequiredcartstringrequiredstatus"pending" | "aired" | "outside" | "not_aired"requiredairedAtinteger (0 to 1439)optionalairedDatedateoptionalreasonstringoptionalsource"portal" | "log_upload" | "rw_log" | "network" | "api" | "mcp"optionaladdedInVersionintegerrequiredremovedInVersionintegeroptionalkind"dated" | "weekly"requiredweekStartdaterequiredallowedDaysdate[]optionalrevintegerrequiredcopyStatus"ready" | "to_follow"requiredinstructionsstringoptionalrwState"confirmed" | "not_confirmed"optionalflagsstring[]requiredItemError6 fields
unit_idstringoptionalvehicle_idstringoptionaldatedateoptionalerror"rev_conflict" | "not_reportable" | "outside_window" | "invalid_time" | "invalid_date" | "reason_required" | "removed" | "not_found" | "break_not_found" | "local_break"requiredmessagestringrequiredShowAiring12 fields
keystringrequiredshowIdstringrequireddatedaterequiredstatus"pending" | "carried" | "not_carried"requiredstartinteger (0 to 1439)optionalendinteger (0 to 1439)optionaluneditedbooleanoptionaldropped(object or object or object)[]optionalreasonstringoptionalsource"portal" | "log_upload" | "rw_log" | "network" | "api" | "mcp"optionalairedDatedateoptionalrevintegerrequiredErrors
400bad_requestThe request is not valid: a path value, a query value or a body field is missing, malformed or out of range.
403forbiddenThe 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.
403read_onlyThe affiliation is read-only: the network paused or ended it, or the station outlet is inactive. Its history stays readable.
404not_foundNothing 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.
409not_readyThe uploaded log is still being read, or reading it failed, so there is nothing to apply yet.
409already_appliedMatches from this upload were already applied, and that result does not cover the units you sent now.
413payload_too_largeThe request body is over the limit: 256 KB of JSON (512 KB when applying log matches), or 10 MB for an uploaded log.
422validation_failedItems were refused. With `all_or_nothing` true nothing was saved; `errors` says why for each item.
Possible on every endpoint
401invalid_principalThe key is missing, malformed, unknown, revoked or expired; all of these answer the same way.
429rate_limitedToo many requests: the key's bucket, a daily limit or the failed sign-in brake refused this one.
503unavailableThe service is not available right now: it could not be reached, or it is not set up yet.
Rate limits
Failed sign-ins per address10 per minute
portal:authBurst per key60 per minute
portal:keyCalls per key600 per 10 minutes
key:{prefix}Writes per key120 per minute
w:{prefix}MCP
An assistant calls this as the tool
apply_log_matches, with the same input as one object. The MCP server# A new Idempotency-Key for every new request. Reuse it only to retry this same request.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST 'https://affiliates.radioworkflow.com/api/v1/log-uploads/u1207_20260921_4f1c2a9e8b7d4c3fa0e1d2c3b4a59687/apply' \
-H "Authorization: Bearer $AFFILIATE_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"epoch": 1,
"items": [
{
"unit_id": "u-01oso1x",
"status": "aired",
"aired_date": "2026-09-22",
"aired_at": 462
}
]
}'{
"epoch": 1,
"items": [
{
"unit_id": "u-01oso1x",
"status": "aired",
"aired_date": "2026-09-22",
"aired_at": 462
}
]
}{
"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": []
}