Repository navigation
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, and the open PRs that add rejections:
§ 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 […] Reusing an `idempotency_key` with a different body 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
Our errors and extensions PR carries § 2.4, quoted here without its informative HTTP mapping, and the § 5.5 and § 5.6.3 codes. Our signing PR words idempotency_conflict against a Commit and orders the § 5.6.1 codes. The § 4.3.2 and § 5.8 codes land with our lifecycle and parties PRs.
### 2.4 Errors
A party that rejects a request MUST return `errors[]`, one or more
`ErrorObject`s. This specification defines the object. The message binding that
carries the request carries the errors.
A seller's decline of a resolved `commitment`, or of a hold, answers a valid
request and is not an error. What a party answers when a request fails message
authentication is § 2.8's to state.
**`ErrorObject`** keys:
| Key | Type | Required | Meaning |
| :--- | :--- | :--- | :--- |
| `code` | VocabularyTerm | REQUIRED | A core code (below) or an extension code |
| `detail` | String | REQUIRED | Human-readable. A receiver SHOULD NOT parse it |
| `pointer` | FieldPointer | OPTIONAL | Into the object this specification defines that the request carries, to the value that caused the error |
| `version` | Integer | REQUIRED when the request names a proposal the requester may read | The proposal's `version` when the error was decided |
| `retry` | Enum | REQUIRED | `same` \| `refetch` \| `none`. `same`: the same request can succeed later. `refetch`: a new attempt, composed on the current `version`, can succeed. `none`: only a changed request can succeed. On a `commit`, `same` keeps the `idempotency_key` and such an error is not a stored result (§ 5.6.1), and `refetch` and `none` take a new `idempotency_key` |
Core codes: `validation` \| `not_found` \| `not_offered` \| `unresolved_reference` \|
`not_committable` \| `stale_version` \| `expired` \| `idempotency_conflict` \|
`not_eligible` \| `record_mismatch`.
| `code` | Returned when |
| :--- | :--- |
| `validation` | A value breaks a rule of this specification and no other code applies |
| `not_found` | A named proposal or line item does not exist, or the requester may not read it (§ 5.8) |
| `not_offered` | A buyer value is outside what the seller declared (§ 3.1, § 3.2, § 4.3.2, § 5.3, § 5.5, § 5.6, § 5.6.1), or a buyer asks for a hold where `hold_terms` offers none (§ 5.6.2) |
| `unresolved_reference` | A `DocumentRef` (§ 2.2) does not resolve, or its content does not match its `digest` |
| `not_committable` | A line item's `status` rules out an ask (§ 4.3.2), a `requires` that is unmet rules out a `commit` (§ 5.6.3), a live `commitment` entry already names a line item (§ 4.1.2), or a seller rejects a `commit` before `offer_window.start` (§ 5.6.1) |
| `stale_version` | An ask is stale (§ 4.1.3) |
| `expired` | `offer_window.end` or `price_valid_until` has passed |
| `idempotency_conflict` | An `idempotency_key` is reused with a different `Commit` (§ 5.6.1) |
| `not_eligible` | The buyer or the sender fails an eligibility or authority check the seller applies (§ 5.8) |
| `record_mismatch` | A `record_digest` in the request does not equal the one the receiver recomputes, and § 5.6.1 gives no other code (§ 4.1.1) |
An `ErrorObject` is open. A receiver that does not recognize a code, or a key of
the object, acts on `retry`.§ 5.6.1: The rejection MUST carry `expired` when `offer_window.end` or
`price_valid_until` has passed, `stale_version` when the `commit` is stale,
`not_committable` when a ground of that code applies (§ 2.4), and otherwise
`record_mismatch`.
§ 5.5: A seller MUST reject it deterministically rather than ignore it, with
`not_offered`.
§ 5.6.3: An unmet `requires` returns `not_committable`.Why
Open PRs require rejections, and none says what a rejection holds. One error object lets a buyer agent act on a rejection without reading prose.
The cases come from @Kishore-2572's commitment version PR (#73), @Sirajmx's block-list PR (#71), and @Sirajmx's line-item dependencies PR (#70). The public draft never uses the word "error". #73 requires the current version in a rejection, with no field for it.
@VJAlkimi's format validation change request (#32) proposes { field, id, reason, detail } for #32 and @Sirajmx's block-list change request (#25). @unreconciled reuses it on #5. @aleksUIX and @fgranata ask on #25 for "a structured error", and @EvgenyAndroid's idempotency key feedback (#128) for a conflict code. We build on #32's shape: reason becomes code, field becomes pointer, and pointer replaces id. We add version and retry.
The object follows the OpenDirect 2.1 error response: errors, an array of objects with a symbolic code. detail and pointer come from RFC 9457 problem details. Core codes reuse the iab-agentic-primitives ErrorCode names where meanings match. code is a VocabularyTerm (our shared types PR), so an extension code carries its definer's domain.
retry takes its three values from gRPC's status code guidance: retry the call, refetch and recompose, or change the request. The reference buyer agent retries only on a timeout or HTTP 502, 503, or 504. A stale version needs a refetch, not a wait.
The binding carries the object, so the HTTP statuses are informative.
Details: Error model: full rationale and examples
Knock-on effects
- A
record_digestthat differs from the seller's recomputed value (our binding-record change request (#63)) isrecord_mismatch. - A
DocumentRefwhose content doesn't match thedigestthat @EvgenyAndroid's catalog digest feedback (#129) asks for isunresolved_reference. - In [Change] § 5.4 — Validate
assets[].format_refagainst the resolvedformatsselection at submission, not at creative review #32, an asset whose format is outside the line item's resolvedformatsselection isnot_offered, withpointerat itsformat_id.
Open questions for the working group
- Answered in part in our post-agreement PR: § 1.2 makes OpenProposal normative for what a request or response carries, such as
errors[]. It leaves the envelope and binding to the AAMP wire and lifecycle contract (provisional name). Still open: should that contract define the error object instead, with OpenProposal keeping only its codes? @dineshbhat's operations comment (#113) asks where messages live. The primitives' error envelope covers that layer, with a closed code list. - Should
stale_versionandexpiredtake the primitives' names,contentionandquote_expired? - Answered in part in our signing PR: a
declineentry MUST carry aground, aVocabularyTermwith the core termsavailability,exclusivity, andterms. A decline for lack of availability states its figures inCommitResult.availability. A declined hold under our hold-states change request (#66) carries no reason:hold.statusreadsdeclined. OpenDirect 2.1 gives a declined Line a free-textstatechangereason. Still open: must adeclinestate itsground? Our rules issue asks. - Answered in our errors and extensions PR and our parties PR. Should the core list cover cases the draft implies but doesn't state, such as a buyer outside § 5.8 Transaction eligibility? Yes:
not_eligiblecovers a buyer or sender that fails an eligibility or authority check, andrecord_mismatcharecord_digestthe seller recomputes differently. - Answered in our shared types PR. Should
pointerbe a JSON Pointer, as RFC 9457 uses, or a dotted JSON path, as the primitives'unsupported[].pathwrites it? A JSON Pointer:pointeris aFieldPointer, an RFC 6901 JSON Pointer. - Does an implementation need any of three error rules we left out of the text? One makes an ask, hold, or commit on a shared proposal an error. One makes
not_foundreplace every other code when the requester may not read what a request names. One rejects an availability read on an olderversion. Our rules issue lists them and offers a PR.
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 the object as problem details. - Integer codes with a vendor range, as in OpenRTB 3.0's no-bid reasons. 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-11-05: A decline states its ground. Rewritten to match § 2.4 in our errors and extensions PR, adding not_eligible and record_mismatch. Questions 4 and 5 are answered, 1 and 3 in part, and 6 is new.
Analyzed and generated with the help of Claude, fully reviewed by me.
Activity