Repository navigation
[Change] § 2, § 5.5, § 5.6 — One error object for rejections: code, pointer, current version, retry #130
Description
What kind of change?
Change a normative requirement (MUST / SHOULD / MAY)
Which section?
Whole document / more than one section
Current text
§ 5.6.1: A request carrying an `idempotency_key` already seen MUST return the
original result and MUST NOT create a second commitment.
§ 5.6.1, as PR #73 proposes: The rejection MUST return the current version
number, so the buyer can fetch it again. […] Reusing an `idempotency_key` with
different commitment terms MUST produce an explicit conflict […]
§ 5.5, as PR #71 proposes: A seller MUST reject it deterministically rather
than ignore it.
§ 5.6.3, as PR #70 proposes: A seller MUST reject a commitment that includes a
line item whose `requires` is unmet.Proposed text
§ 2, add a subsection:
### Errors
A party that rejects a request MUST return `errors[]`, one or more error
objects. This specification defines the object. The binding that carries the
request carries the errors. Once an AAMP wire contract defines an error
object, this subsection cites it and keeps only the codes.
A seller's decline of a resolved `commitment`, and a refused hold, are answers
to a valid request, not errors.
| Key | Required | Meaning |
| :--- | :--- | :--- |
| `code` | REQUIRED | A core code (below) or an extension code `<domain>:<code>` |
| `detail` | REQUIRED | Human-readable. A receiver SHOULD NOT parse it |
| `pointer` | OPTIONAL | JSON Pointer (RFC 6901), into the object this specification defines that the request carries, to the value that caused the error |
| `version` | REQUIRED when the request names a proposal that exists | The proposal's `version` when the error was decided |
| `retry` | REQUIRED | `same`: the same request, with the same `idempotency_key`, can succeed later; such an error is not a stored result (§ 5.6.1). `refetch`: a new attempt, composed on the current `version` with a new `idempotency_key`, can succeed. `none`: only a changed request, with a new `idempotency_key`, can succeed |
Core codes, which IAB Tech Lab maintains apart from the schema:
| `code` | Returned when |
| :--- | :--- |
| `validation` | A value breaks a rule of this specification |
| `not_found` | A named proposal or line item does not exist |
| `not_offered` | A buyer value is outside what the seller declared (§ 3.1, § 3.2) |
| `unresolved_reference` | A `catalog_ref` (§ 2.2) or a hash-pinned reference does not resolve, or its content does not match its hash |
| `not_committable` | A line item's `status`, or a `requires` that is unmet, rules out commitment |
| `stale_version` | The commitment was composed on terms that are not the current terms (§ 5.6.1) |
| `expired` | `valid_until` or `price_valid_until` has passed |
| `idempotency_conflict` | An `idempotency_key` is reused with different terms |
Anyone MAY define an extension code. Its prefix is a domain the definer
controls. A code's meaning MUST NOT change once published. A receiver that
does not recognize a code, or a key of the error object, acts on `retry`.
Informative: over HTTP, `validation`, `not_offered`, and
`unresolved_reference` fit 400 Bad Request; `not_found` 404 Not Found;
`not_committable` and `stale_version` 409 Conflict; `expired` 410 Gone;
`idempotency_conflict` 422 Unprocessable Content. An error with
`retry: same` fits 503 Service Unavailable.
§ 5.6.1 (PR #73): replace "The rejection MUST return the current version
number" with "The rejection MUST carry `stale_version`, or `expired` when
`valid_until` or `price_valid_until` has passed", and "MUST produce an
explicit conflict" with "MUST return `idempotency_conflict`".
§ 5.5 (PR #71): an exclusion type the seller did not accept returns
`not_offered`; an entry whose platform it cannot determine, `validation`.
§ 5.6.3 (PR #70): an unmet `requires` returns `not_committable`.Why
Open PRs require a seller to reject four cases, and none says how. We ask for one error object, so a buyer agent can act on a rejection without reading prose.
A stale commitment comes from @Kishore-2572's commitment version PR (#73). An unaccepted exclusion type and an undeterminable platform come from @Sirajmx's block-list PR (#71), and an unmet requires from @Sirajmx's line-item dependencies PR (#70). The draft at commit 44dfaaf never uses the word "error". #73 requires the current version in a rejection, with no field for it.
We revise our replay notes on #24, which named the behavior and not the code: an agent can't reliably act on prose.
Others asked for this. @VJAlkimi's format validation change request (#32) proposes { field, id, reason, detail }, one shape for both #32 and @Sirajmx's block-list change request (#25). @unreconciled reuses it on #5. @aleksUIX on #25 and @fgranata on #25 ask for "a structured error", and @EvgenyAndroid (#128) for a conflict error. We build on #32's shape: field becomes pointer, reason becomes code, and pointer replaces id. We add version and retry.
The shape follows the OpenDirect 2.1 error response: errors, an array of objects with a symbolic ErrorCode. OpenDirect lists no codes. detail and pointer come from RFC 9457 problem details: clients SHOULD NOT parse detail, and its validation example points with a JSON Pointer. Core codes reuse the iab-agentic-primitives ErrorCode names where meanings match. OpenRTB 3.0 no-bid reasons reserve 500 and above for exchanges, so two vendors can pick the same number. A domain prefix can't collide, as in our cost-method change request (#94).
retry borrows its idea from gRPC's status code guidance: retry the call (same), refetch and recompose (refetch), or change the request (none). The reference buyer agent retries only on a timeout or HTTP 502, 503, or 504. A stale version needs a refetch, not a wait.
OpenProposal isn't tied to HTTP, and the primitives also send errors inside a JSON-RPC result. So the binding carries the object (@dineshbhat's operations comment (#113)), and the HTTP statuses are informative.
A decline isn't an error. A seller's decline (our commitment acceptance change request (#67)) and a refused hold (our hold-states change request (#66)) answer a valid request.
Details: Error model: full rationale and examples
Knock-on effects
- PRs § 4.1, § 5.6.1 — Commitments carry proposal_id and version (#24) #73, § 5.5 — Buyer-supplied block lists (#25) #71, and § 2, § 4.1, § 5.6 — Line-item dependencies and bonus lines (#23) #70 each name a code, as proposed.
- [Change] § 5.4 — Validate
assets[].format_refagainst the resolvedformatsselection at submission, not at creative review #32's mismatch isnot_offeredwithpointerat the asset'sformat_ref; OQ-4 — The format registry has no entries foraudio/*,ctv/*ordooh/*#5's unresolved format isunresolved_reference. - A
catalog_refwhose content doesn't match the digest that @EvgenyAndroid's catalog digest feedback (#129) proposes isunresolved_reference. - @aleksUIX's inspection PR (#118) is unchanged: it reports failures through diagnostics and defines no result object.
- A mismatched
record_digest(our binding-record change request (#63)) isvalidation. - An undefined key is
validation, under our extension change request. - A
pointerinto a line item depends on its container's name (@VJAlkimi's line-item container change request (#31)). - Appendixes A and B are unchanged.
Open questions for the working group
- Which AAMP document should carry the error object, with OpenProposal keeping only its codes? Our scope comment (#59) proposes that wire contract for § 1.2, and @dineshbhat's operations comment (#113) asks for it. The primitives' error envelope covers that layer, with a closed code list.
- Should
stale_versionandexpiredtake the primitives' names,contentionandquote_expired? - Should a decline under #67 or a refused hold under #66 carry error objects as its reason? OpenDirect 2.1 gives a declined Line a free-text
statechangereason. Open question 5 in our commitment-set comment on #23 asks the same about a rejected set. - Should the core list cover cases the draft implies but doesn't state, such as a buyer outside § 5.8 Transaction eligibility?
- Should
pointerbe a JSON Pointer, as RFC 9457 uses, or a JSONPath, as the primitives'unsupported[].pathwrites it?
Would this break an implementation already built to this draft?
No — additive or editorial
Alternatives you considered and rejected
- RFC 9457 problem details as the object. Its
statusmember must match the HTTP response. Over HTTP, a binding can still send our object as problem details. - Integer codes with a vendor range (OpenRTB 3.0). Two vendors can pick the same code.
- A boolean
retryable. It can't tell a wait from a refetch. - Prose only, as PR § 4.1, § 5.6.1 — Commitments carry proposal_id and version (#24) #73 drafts it. An agent can't reliably branch on it.
You are commenting as
Other — IAB Tech Lab member (Postindustria).
Before you submit
- I have read CONTRIBUTING.md and agree my contribution may be published under this repository's licence.
- This contains no confidential or commercially sensitive information.
Revised 2026-10-27: Knock-on effects add @EvgenyAndroid's catalog digest feedback (#129): a digest mismatch is unresolved_reference.
Analyzed and generated with the help of Claude, fully reviewed by me.
Activity