curl --request PUT \
--url https://api.interchange.io/api/v2/storefront/demand-inbox/{runId}/ledger \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"feedback": "<string>"
}
'import requests
url = "https://api.interchange.io/api/v2/storefront/demand-inbox/{runId}/ledger"
payload = { "feedback": "<string>" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({feedback: '<string>'})
};
fetch('https://api.interchange.io/api/v2/storefront/demand-inbox/{runId}/ledger', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"id": "<string>",
"brief": "<string>",
"buyer": "<string>",
"commercialIdentity": {
"advertiser": {
"value": "<string>",
"source": "request.brand"
},
"buyingOperator": {
"value": "<string>",
"source": "request.brand"
},
"account": {
"value": "<string>",
"source": "request.brand"
}
},
"responseKind": "product_offer",
"commercialLifecycle": "closed_won",
"budgetRange": {
"min": 123,
"max": 123,
"currency": "<string>"
},
"receivedAt": "<string>",
"respondedAt": "<string>",
"responseTimeMs": 1,
"ledBy": "agent",
"feedback": "<string>",
"result": "closed_won",
"grade": "A",
"wonValue": {
"amount": 123,
"currency": "<string>"
},
"provenance": "<string>",
"briefArtifactId": "<string>",
"proposalArtifactId": "<string>",
"artifactAvailable": true
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}Record demand-inbox ledger annotations
Record the seller’s grade (A–F), buyer feedback, and led-by attribution (agent | human) on one demand-inbox row. Only the fields provided are changed; grade and feedback accept null to clear them. Scoped to the caller’s storefront — a run outside it returns 404.
curl --request PUT \
--url https://api.interchange.io/api/v2/storefront/demand-inbox/{runId}/ledger \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"feedback": "<string>"
}
'import requests
url = "https://api.interchange.io/api/v2/storefront/demand-inbox/{runId}/ledger"
payload = { "feedback": "<string>" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({feedback: '<string>'})
};
fetch('https://api.interchange.io/api/v2/storefront/demand-inbox/{runId}/ledger', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"id": "<string>",
"brief": "<string>",
"buyer": "<string>",
"commercialIdentity": {
"advertiser": {
"value": "<string>",
"source": "request.brand"
},
"buyingOperator": {
"value": "<string>",
"source": "request.brand"
},
"account": {
"value": "<string>",
"source": "request.brand"
}
},
"responseKind": "product_offer",
"commercialLifecycle": "closed_won",
"budgetRange": {
"min": 123,
"max": 123,
"currency": "<string>"
},
"receivedAt": "<string>",
"respondedAt": "<string>",
"responseTimeMs": 1,
"ledBy": "agent",
"feedback": "<string>",
"result": "closed_won",
"grade": "A",
"wonValue": {
"amount": 123,
"currency": "<string>"
},
"provenance": "<string>",
"briefArtifactId": "<string>",
"proposalArtifactId": "<string>",
"artifactAvailable": true
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}{
"data": null,
"error": {
"code": "<string>",
"message": "<string>",
"field": "<string>",
"details": {}
}
}Authorizations
API key or access token
Path Parameters
Surrogate id of the chef compose run this ledger row wraps.
0 < x <= 900719925474099142
Body
Records the seller-authored ledger annotations (grade, buyer feedback, led-by attribution) on one demand-inbox row.
Seller's letter grade (A–F) for the proposal. Null clears it.
A, B, C, D, F Buyer feedback captured against the proposal. Null clears it.
2000Who led the response: the agent, or a human who took it over. Omit to leave the run's current attribution.
agent, human Response
Record demand-inbox ledger annotations
The brief text the agent answered (label for the row).
Legacy ambiguous buyer label retained for compatibility. New readers must use commercialIdentity instead.
Advertiser = BrandRef × Operator (AI-5080), projected from the persisted brief request first and the compose-run identity snapshot only as a historical fallback. CRM evidence and seller-native mappings are deliberately not joined into this identity.
Show child attributes
Show child attributes
What the persisted seller response actually proves: 'proposal' requires a non-empty proposal_id; products without that evidence are a product_offer, never a Proposal.
product_offer, proposal, no_offer, unavailable The most specific commercial lifecycle supported by the persisted response and attributed outcome. This supplements result so an undecided exchange is never presented as an ambiguous Pending state.
closed_won, closed_lost, awaiting_advertiser_decision, awaiting_seller_approval, forwarding_failed, no_offer_returned, response_not_recorded, response_unavailable The buyer's stated budget range, exactly as sent in the brief's filters.budget_range and captured in the brief artifact. Null when the buyer did not state one.
Show child attributes
Show child attributes
When the agent answered. Null while the brief is unanswered.
Persisted compose duration in milliseconds. Null when duration was not recorded; never derived as zero from equal timestamps.
x >= 0agent, human Commercial result derived from this run’s own attributed outcome: closed_won (booked/delivered), closed_lost (rejected), else pending. A booking attributed to a later refine pass is not rolled up here (see the win-rate scope note).
closed_won, closed_lost, pending A, B, C, D, F Money attributed to this run's won outcome: delivered spend when reporting exists, else the booked media-buy budget. Null unless the outcome persisted both an amount and its currency.
Show child attributes
Show child attributes
How this brief reached the ledger: 'live' = an AdCP get_products call from a buyer agent, 'uploaded' = a seller-uploaded RFP/brief confirmed in chat (AI-4728). Reflects this run's own intake origin (AI-5025), falling back to the linked brief artifact's source column only for historical runs recorded before that origin was captured. Null when neither is available (e.g. a practice pitch or a historical pre-capture run). Uploaded briefs never masquerade as live buyer demand.
Canonical brief artifact id (AI-4693) for the build-3 drill-in. Null = artifact unavailable (historical pre-capture run).
Historical storage-table name for the latest canonical seller-response artifact. Its presence does not prove a Proposal; responseKind is authoritative and 'proposal' requires proposal_id evidence.
False when the brief artifact was not captured; readers must say "artifact unavailable" rather than reconstruct it.