This page defines the normative algorithm for extracting AdCP response data from A2A Task objects and TaskStatusUpdateEvents. For the canonical response structure that sellers must produce, see A2A Response Format. For error-specific extraction, see Transport Error Mapping.Documentation Index
Fetch the complete documentation index at: https://agenticadvertisingorg-changeset-release-main.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
AdCP Conventions on Top of A2A
The rules on this page layer AdCP-specific semantics onto A2A. Non-AdCP A2A agents do not enforce them and should not be expected to produce conforming output.- Single-artifact invariant. AdCP tasks produce one artifact containing all output parts. Clients read from
artifacts[0]. If a seller needs multiple distinct deliverables, they should be modeled as separate tasks β not multiple artifacts. - Last-DataPart authority. When multiple DataParts appear in one artifact (typical during streaming), the last one is authoritative. Earlier DataParts are superseded progress snapshots.
- First-DataPart for interim. When multiple DataParts appear in
status.message.parts, the first is used β interim updates are single-event snapshots, not accumulated. - Wrapper rejection. A DataPart whose
.datais{ response: {...} }(single key namedresponse) is treated as a framework-wrapper bug, not a valid payload.
Wire-Format Compatibility
This algorithm handles both A2A 1.0 and v0.3 responses. Extraction must not assume one wire format β the same AdCP client may talk to both during the v0.3 compatibility period. State values. Thestatus.state field arrives as either the ProtoJSON form ("TASK_STATE_COMPLETED", "TASK_STATE_WORKING", β¦) in 1.0 or the lowercase form ("completed", "working", β¦) in v0.3. Clients normalize before comparison.
Part shape. A 1.0 DataPart has a non-null data field and no kind. A v0.3 DataPart has kind: "data" and a data field. Both satisfy βthe data field is a non-null object.β The same holds for TextParts (text field present) and FileParts (url/raw in 1.0, or kind: "file" in v0.3). Per A2A 1.0 Β§4.1.6, a Part is a strict oneof β exactly one of text, raw, url, or data is set. Clients receiving a Part with multiple content fields SHOULD treat it as malformed.
Streaming envelope. A2A 1.0 wraps streaming responses and push-notification payloads in a StreamResponse oneof with exactly one of the keys task, message, statusUpdate, or artifactUpdate (A2A 1.0 Β§3.2.3, Β§4.3.3). Non-streaming responses (e.g., tasks/get, or v0.3 over HTTP) deliver the bare object. Extraction unwraps a single-key envelope before applying the algorithm below.
Status-Based Extraction
The extraction location depends on the taskβs status. State names in this table are shown in normalized lowercase form β match against the normalized state, not the raw wire value.| Status | Type | Data Location | DataPart Selection |
|---|---|---|---|
completed | Final | .artifacts[0].parts[] (fallback: status.message.parts[]) | Last DataPart |
failed | Final | .artifacts[0].parts[] (fallback: status.message.parts[]) | Last DataPart |
canceled | Final | .artifacts[0].parts[] | Last DataPart (typically none) |
rejected | Final (1.0) | .artifacts[0].parts[] | Last DataPart (carries adcp_error for policy/validation rejections) |
working | Interim | status.message.parts[] | First DataPart |
submitted | Interim | status.message.parts[] | First DataPart |
input-required | Interim | status.message.parts[] | First DataPart |
auth-required | Interim (1.0) | status.message.parts[] | First DataPart (carries auth challenge data β scheme, URL, scopes) |
status.message.parts[] when .artifacts is absent or empty β this covers servers that put a final payload in the status message rather than a separate artifact.
Canceled tasks rarely carry data β extraction returns null when no DataPart is present, which is the expected case. Rejected tasks are expected to carry an adcp_error DataPart describing why the request was rejected (tier/policy/validation).
Extraction Algorithm
Clients MUST extract AdCP data from A2A responses using these steps:-
Unwrap stream envelopes. If the input is an object with exactly one top-level key named
task,message,statusUpdate, orartifactUpdateand that keyβs value is a non-null, non-array object, replace the input with that value (A2A 1.0StreamResponseoneof). BareTask/TaskStatusUpdateEventobjects β non-streaming responses or v0.3 β pass through unchanged. AnartifactUpdatecarries no task status; once unwrapped itsstatus.stateis absent and step 1 returns null. Unwrap exactly once. Clients MUST NOT recurse. If the unwrapped inner object itself has the single-key envelope shape ({ task: { task: {...} } }or any combination), treat as malformed and return null β this is a nested-envelope smuggling attempt. An envelope whose inner valueβs top-level keys include any oftask/message/statusUpdate/artifactUpdateMUST be rejected. Bare{ message }envelopes (out-of-band agent messages) MUST be ignored by task-oriented extractors β step 1 returns null when the unwrapped object has nostatus.state. Webhook/SSE handlers MUST NOT return a200 OKacknowledgment for unrecognized{ message }envelopes; return400 Bad Requestor silently discard at the transport layer to avoid acting as a presence oracle for attackers probing endpoints. -
Read
status.state. If absent, return null. Normalize to lowercase form (TASK_STATE_COMPLETEDβcompleted) before comparing. After normalization, the state MUST match one of the known final/interim tokens by exact ASCII string equality. Clients MUST NOT collapse repeated separators, trim whitespace, or apply Unicode case-folding beyond ASCII lowercase. Any other value β including novelTASK_STATE_*inputs the client does not recognize β is βunknownβ and extraction returns null (step 4). -
Final states (
completed,failed,canceled,rejected): a. Look inartifacts[0].parts[]for DataParts (a Part whosedatafield is a non-null object β regardless of whetherkindis present). b. Use the last DataPart as authoritative (see Last-DataPart Authority). c. Reject wrappers: If the DataPartβs.datahas a single keyresponsecontaining an object, this is a framework wrapper bug. Throw or log an error. d. Return.data. e. Fallback: If no artifacts or no DataPart in artifacts, checkstatus.message.parts[]using step 3. -
Interim states (
working,submitted,input-required,auth-required): a. Look instatus.message.parts[]for DataParts. b. Use the first DataPart. c. Return.data, or null if no DataPart found. - Unknown states: Return null. Forward-compatible clients SHOULD NOT throw on unrecognized status values.
TASK_STATE_ prefix, lowercase, replace underscores with hyphens. That maps both A2A 1.0 ("TASK_STATE_INPUT_REQUIRED") and v0.3 ("input-required") onto the same value.
DataPart detection uses field presence β a 1.0 Part { "data": {...} } and a v0.3 Part { "kind": "data", "data": {...} } both satisfy the βnon-null object data fieldβ test.
Last-DataPart Authority
For final states, the last DataPart inartifacts[0].parts[] is authoritative. During streaming, intermediate DataParts may contain stale progress data that gets superseded by the final result:
{"products": [...], "total": 12}, not {"progress": 25}.
For interim states, the first DataPart is used because interim updates are single-event snapshots, not accumulated.
Wrapper Rejection
Clients MUST reject DataParts where.data is wrapped in a framework-specific object:
.data has exactly one key named response whose value is an object, it is a wrapper. This is a server-side bug β clients should throw or log an error, not silently unwrap.
Wrapper detection applies to final states only (artifacts). Interim status messages are lightweight progress snapshots β wrapper detection is not required for status.message.parts.
Exception: A .data object that has response alongside other keys is NOT a wrapper:
Relationship to Error Extraction
This algorithm extracts any AdCP data from A2A responses, including error payloads (adcp_error). Error-specific extraction (Transport Error Mapping) is a specialization that checks for the adcp_error key in the extracted data.
The transport-errors spec provides its own extractAdcpErrorFromA2A function that scans all artifacts for adcp_error. That function is optimized for error detection (scanning all parts for the error key). This function is the general-purpose extractor (last DataPart from first artifact). For failed tasks with a single adcp_error DataPart, both produce equivalent results.
Typical client flow:
Security Considerations
Seller-Controlled Data
All data in.artifacts[].parts[].data and status.message.parts[].data is seller-controlled. The prompt injection, data boundary, and size limit requirements from Transport Error Mapping apply.
Prototype Pollution
Clients MUST NOT merge extracted DataPart payloads into application state viaObject.assign or spread without filtering keys. Validate against the expected task response schema before merging.
FilePart URI Validation
A2A responses may include FileParts. In 1.0 these are Parts carrying aurl field (file by reference) or a raw field (base64 bytes); in v0.3 they carry kind: "file" with a uri field. Clients MUST validate that the URL uses the https scheme, contains no userinfo component, and matches an expected domain allowlist. Reject javascript:, data:, file:, and http: URIs. For raw parts, enforce a max decoded size before accepting.
Auth Challenge URL Validation
When handlingauth-required, the seller sends an auth challenge in status.message.parts β typically a DataPart with fields like auth_scheme, challenge_url, and scopes. A seller-controlled URL that the client opens or fetches is an OAuth-phishing and SSRF vector. Before initiating any user-facing or programmatic auth flow, clients MUST validate challenge_url:
- Scheme MUST be
https. Rejecthttp:,javascript:,data:,file:. - URL MUST NOT contain a userinfo component (
user:pass@hostform). - Host MUST match the authenticated sellerβs registered auth origin for this agent card. Clients SHOULD maintain a per-agent allowlist seeded from the Agent Cardβs
supportedInterfaces[].urlorigin or a declaredauthOriginextension field β not derived from the task payload. - Any
redirect_uri,return_url, or similar query parameter MUST be dropped or overwritten by the client before navigation. Never forward a seller-supplied redirect. scopesMUST be treated as a request, not a grant. Show scopes to the user and obtain fresh consent for each challenge.
Seller-Controlled String Hygiene
Alladcp_error.message, adcp_error.details.*, and status TextPart content is seller-controlled. Clients rendering these in UI MUST escape for the target context (HTML, Slack, CLI). Clients logging them MUST strip CRLF to prevent log-injection. This applies to all states carrying adcp_error (failed, rejected, system-initiated canceled) and to free-text status.message.
Size Limits
Clients SHOULD enforce a maximum DataPart size (e.g., 1MB) before schema validation. Unlike error payloads (capped at 4096 bytes), success payloads can be larger but still need bounds.Intermediary Injection
The last-DataPart convention assumes the artifact is received intact from a single trusted sender. In multi-hop scenarios (buyer β orchestrator β seller), an intermediary could inject additional parts. Clients operating through intermediaries SHOULD validate that the artifact part count matches expectations.Client Library Requirements
Client libraries that implement this spec MUST:- Unwrap A2A 1.0 stream envelopes. A single-key object with key
task,message,statusUpdate, orartifactUpdateis aStreamResponsewrapper β unwrap to the inner object before applying the rest of the algorithm. Bare objects pass through unchanged. - Accept both A2A 1.0 and v0.3 wire shapes. Normalize
status.statebefore comparison (stripTASK_STATE_prefix, lowercase, underscores to hyphens). Detect DataParts by field presence (datais a non-null object), not bykind. - Branch on normalized state. Final states (
completed,failed,canceled,rejected) use artifacts; interim states (working,submitted,input-required,auth-required) usestatus.message.parts. - Use last DataPart for final states. Skip DataParts with null, non-object, or array
.data. - Use first DataPart for interim states.
- Detect and reject wrappers. Single-key
{response: {...}}payloads are bugs. - Fall back gracefully. If artifacts are empty for a final state, check
status.message.parts. - Handle unknown states. Return null, do not throw.
Test Vectors
Machine-readable test vectors are available at/static/test-vectors/a2a-response-extraction.json. Each vector contains:
status: the A2A task statuspath: extraction path (artifact,status_message, ornone)response: the A2A Task or TaskStatusUpdateEventexpected_data: the AdCP data that should be extracted (ornull)expected_error_type: if present, the extraction should throw (e.g.,wrapper_detected)
See Also
- A2A Response Format β canonical response structure for sellers
- Transport Error Mapping β error extraction from MCP and A2A
- MCP Response Extraction β equivalent spec for MCP
- A2A Guide β A2A transport integration