# Auto Agent Protocol — Full Specification (v1.3) Canonical URL: https://autoagentprotocol.org Version: 1.3.0 | Extension URI: https://autoagentprotocol.org/extensions/aap/v1.3 Transport: A2A `SendMessage` over JSON-RPC 2.0 — sole binding Discovery: GET https://{dealer-domain}/.well-known/agent-card.json Built on: A2A v1.0 (Agent2Agent, https://a2a-protocol.org) License: Specification & schemas Apache-2.0; documentation prose CC-BY-4.0 Generated from the v1.3 documentation — do not edit by hand. The machine-readable artifacts linked at the end are authoritative over the prose below. # Introduction **The Auto Agent Protocol (AAP) lets AI assistants shop at car and motorcycle dealerships.** People increasingly ask an AI assistant to find their next car or motorcycle. AAP is the free, open standard that lets any of those assistants find a dealership, browse its real inventory, and — with the customer's clear permission — send the dealership a sales lead. Inventory listings carry an optional `vehicle_type` (`car`, `motorcycle`, `trailer`, `rv`, `other`; absent = `car`) so a single vocabulary covers both automotive and powersports retail, including electric models via a generic electric-powertrain field group. For a dealership, joining in means publishing **one small file on your own website** and answering a few well-defined questions; no app store, no middleman, no per-partner integration work. In technical terms: AAP is a strict [A2A v1.0](https://a2a-protocol.org) (Agent2Agent) profile that defines the typed automotive data shapes AI agents and dealer agents exchange when they discover, browse, and submit leads. AAP does not invent a new wire protocol — every AAP message travels inside an A2A `Message.parts[].data` value as a typed `DataPart`. JSON-RPC 2.0 is the sole binding: a JSON-RPC 2.0 interface is REQUIRED on every AAP agent card. The HTTP+JSON (REST) binding was removed in v1.1.0, and gRPC is out of scope. The extension is identified by a single URI: ``` https://autoagentprotocol.org/extensions/aap/v1.3 ``` A dealer agent declares itself AAP-compliant by listing this URI in `capabilities.extensions[]` of its A2A agent card and by implementing **one or more** of the five standard AAP automotive skills. Agents pick the subset they support; AAP RECOMMENDS at least `inventory.search` + `lead.submit` for an end-to-end shopping flow, but neither is mandatory. ## What AAP standardizes AAP v1.3.0 defines a **vocabulary** of five standard skill IDs that cover the read-and-lead lifecycle of automotive retail. A dealer agent picks whichever subset matches its capabilities — none of the five is individually mandatory. | Skill | Purpose | |---|---| | `dealer.information` | Dealership profile, rooftops, hours, contact channels, capabilities | | `inventory.facets` | Aggregated counts and ranges over the dealer's inventory | | `inventory.search` | Filtered, paginated inventory queries (cars and motorcycles) | | `inventory.vehicle` | Detail view of one specific vehicle or motorcycle (by VIN, stock, or vehicle_id) | | `lead.submit` | Unified consented lead carrying customer info plus optional vehicle of interest, trade-in, and appointment | It does NOT define authentication (v1.3.0 agents are public by default; auth is left to A2A), payments, financing approval, RFQ/quote workflows, trade-in valuations, or reservations. Future versions MAY extend this surface; v1.3.0 is intentionally minimal. ## Layered architecture AAP sits as a profile on top of A2A, which itself sits on top of HTTP. AAP never touches the wire format directly — it defines the shape of typed `DataParts` that A2A bindings carry. AAP uses exactly **one** A2A operation: `SendMessage` — a request `Message` goes in, a response `Message` comes out. The optional A2A surface (`SendStreamingMessage`, the `tasks` operations Get/List/Cancel/Subscribe, push notification configs, `GetExtendedAgentCard`) is out of scope for AAP: dealer agents do not need to implement it, and buyer agents MUST NOT require it. AAP only specifies the typed payloads inside `DataPart.data`. ## Quick start A buyer agent talks to a compliant dealer agent in three steps. ### 1. Discover the agent Fetch the A2A agent card at the dealer's well-known URL: ```bash curl https://demo-toyota.example.com/.well-known/agent-card.json ``` Confirm the card lists the AAP extension URI under `capabilities.extensions[].uri` and includes a `supportedInterfaces[]` entry whose `protocolBinding` is `JSONRPC` (REQUIRED on every AAP agent card; it is the sole AAP binding). ### 2. The binding Every AAP agent exposes the JSON-RPC 2.0 binding — AAP's sole transport. gRPC is out of scope, and the HTTP+JSON (REST) binding was removed in v1.1.0. | Binding | Status | A2A spec | AAP page | |---|---|---|---| | JSON-RPC 2.0 | REQUIRED (sole binding) | A2A Section 9 | JSON-RPC binding | ### 3. Send a typed AAP message Wrap an AAP request inside an A2A `Message` and send it with `SendMessage` — the single A2A operation AAP uses. Below is the simplest call — `dealer.information` over the JSON-RPC binding, using the A2A v1.0 ProtoJSON wire format (`ROLE_USER`/`ROLE_AGENT` enum names, no `kind` discriminators) that A2A v1.0 clients send and parse: ```bash curl -X POST https://demo-toyota.example.com/a2a \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "01HZ9G5N8D1Y4M6SP9C4XKVW3Q", "role": "ROLE_USER", "parts": [ { "data": { "type": "dealer.information.request" }, "mediaType": "application/vnd.autoagent.dealer-information-request+json" } ] }, "configuration": { "acceptedOutputModes": ["application/vnd.autoagent.dealer-information-response+json"] } } }' ``` The dealer agent replies with a `SendMessageResponse` in the JSON-RPC `result` — `{ "message": }` — where the `message` is an A2A `Message` whose first `DataPart.data` is an AAP response: ```json { "jsonrpc": "2.0", "id": 1, "result": { "message": { "messageId": "01HZ9G5P2KA8RT9WMS3B4C5D6E", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "dealer.information.response", "data": { "name": "Demo Auto Group", "welcome_message": "Welcome to Demo Auto Group.", "rooftops": [ { "name": "Demo Toyota San Francisco", "legal_name": "Demo Toyota of San Francisco, LLC", "website": "https://demo-toyota.example.com", "phones": [ { "name": "Sales", "value": "+14155550100" } ], "address": { "country": "US", "state": "CA", "city": "San Francisco", "address_line_1": "100 Market St", "zip": "94105" }, "timezone": "America/Los_Angeles", "capabilities": [ "sales", "service", "financing" ] } ] } }, "mediaType": "application/vnd.autoagent.dealer-information-response+json" } ] } } } ``` ## Verified interoperability All five skills have been exercised live through the official A2A v1.0 SDKs (`@a2a-js/sdk` and `a2a-sdk` for Python): inventory search, facets, vehicle detail, dealer information, and a consented `lead.submit` — with no AAP-specific client code. ## Where to read next - Why automotive needs AAP — the gap AAP fills against A2A, ACP, MCP, and ADF. - A2A profile — how AAP slots into A2A's three-layer architecture. - Discovery — full agent card example. - Pricing and fee disclosure — authoritative price semantics, optional vehicle fee itemization, provider mapping, and current FTC context. - Skills reference — one page per skill with full request/response examples. # Why automotive needs AAP Automotive retail has unusual constraints that no general-purpose agent protocol addresses end-to-end: - Inventory is **mixed** (new + used + certified + in-transit) and **mutates daily**. A car listed at 9am can be sold by 11am. - Pricing is **regulated**. Current FTC enforcement warns that advertised vehicle prices must include mandatory dealer fees and required add-ons and must not rely on rebates unavailable to all consumers. - Customer contact data is **regulated**. TCPA, CAN-SPAM, and state laws require explicit, scoped consent before a dealer can call, text, or email. - Lead handoff is **legacy-bound**. Dealer CRMs ingest [ADF/XML](https://en.wikipedia.org/wiki/Auto-lead_Data_Format) (Auto-lead Data Format) leads that have been the de-facto standard for two decades. A protocol for AI agents talking to dealerships has to handle all four. AAP does. Generic agent protocols do not. ## How AAP relates to neighboring protocols | Protocol | What it standardizes | What it does NOT cover for automotive | |---|---|---| | [A2A](https://a2a-protocol.org) (Agent2Agent) | Generic agent discovery, message envelope, JSON-RPC + HTTP+JSON bindings, task model, push notifications | Automotive vocabulary (vehicles, VIN, pricing semantics, ADF compatibility, dealership consent rules) | | [ACP](https://www.agenticcommerce.dev) (Agentic Commerce Protocol) | E-commerce checkout (cart, payment, fulfillment) between agents and merchants | Pre-purchase research, leads, appointments, dealership-specific data — vehicles are rarely bought through agentic checkout | | [MCP](https://modelcontextprotocol.io) (Model Context Protocol) | Tool layer between an LLM client and one host application (filesystem, DB, API) | A peer-to-peer protocol between agents; MCP is host-to-tool, not agent-to-business | | [ADF/XML](https://en.wikipedia.org/wiki/Auto-lead_Data_Format) | Legacy lead format dealer CRMs ingest today | A read API (no inventory queries), no agent discovery, no consent records, no appointment booking | AAP does not replace any of these. It complements them. - **AAP IS an A2A profile.** Every AAP message is an A2A `DataPart`. A buyer agent that already speaks A2A can call an AAP dealer agent without learning a new transport. AAP keeps the A2A surface minimal: AAP v1.3.0 uses a single transport — JSON-RPC 2.0 — so a JSONRPC interface is required on every AAP agent card (the HTTP+JSON (REST) binding was removed in v1.1.0, and gRPC is out of scope), and AAP uses exactly one A2A operation — `SendMessage`. The optional A2A surface (streaming, tasks, push notifications) is out of scope: dealer agents do not need to implement it, and buyer agents must not require it. - **AAP COMPLEMENTS ACP.** ACP is built around payment + checkout. Vehicles are typically not transacted that way — the dealer's lead system, financing, F&I, and trade-in conversation happen out of band. AAP covers the lead step that precedes (or replaces) checkout. - **AAP COMPLEMENTS MCP.** A buyer agent's host LLM can expose AAP skills as MCP tools. The MCP compatibility page shows the one-to-one mapping. - **AAP MAPS TO ADF.** Every `lead.submit` request can be losslessly converted to an ADF/XML payload so a dealer's existing CRM accepts it without changes. See the ADF mapping page. ## What AAP adds that A2A alone does not A2A standardizes how agents exchange messages, not what is in them. Two A2A-compliant dealer agents could each invent their own `inventory_search` skill with different field names, different filter semantics, different pricing fields, and a buyer agent would have to special-case each one. AAP fixes the field names, types, and required behavior: - **Five canonical skill IDs** form the AAP v1.3.0 vocabulary; dealer agents implement whichever subset matches their capabilities. - **Strict typed `DataParts`** (`..request`, `..response`) so a buyer agent can validate before sending. - **Explicit pricing and fee fields** (`msrp`, `list_price`, `price`, `fees`) where `price` includes every mandatory dealer charge, optional `fees` can expose the complete itemization, and buyer-specific government charges remain excluded — see Pricing and fee disclosure. - **`ConsentGrant`** structure required when a lead carries customer contact info, with explicit `allowed_channels` and `scope`. - **A controlled vehicle `status` enum** (`available`, `intransit`, `pending`) — these are the only statuses that appear in an inventory feed; a vehicle in any other state (sold, reserved, in service, etc.) is out of stock and is omitted by the dealer and ignored by the buyer — see behavior rules. ## First automotive-specific A2A profile AAP is the first published A2A profile written specifically for the automotive retail vertical, riding on A2A v1.0; v1.3.0 is the current release. Its goal is narrow: a buyer agent should be able to talk to any compliant dealer agent — Toyota, Honda, an independent used-car lot, a motorcycle or powersports store, a CDK/Reynolds-backed group — through identical typed messages, with consent, pricing, and ADF compatibility built in from day one. The unified `Vehicle` shape carries an optional `vehicle_type` (`car`, `motorcycle`, `trailer`, `rv`, `other`; absent = `car`) plus a class-agnostic `body`/segment field and a free-form `other_attributes` map for niche specs, so the same five skills serve both car and motorcycle dealers. A generic electric-powertrain group (range, battery kWh, motor hp, 0-60, charge time, DC fast charge) covers electric cars and electric motorcycles alike. # AAP as an A2A profile The Auto Agent Protocol is a strict profile of [A2A v1.0](https://a2a-protocol.org). It does not redefine discovery, message envelopes, the task model, or transport. It only constrains the shape of one specific A2A construct: typed `DataParts` carried inside `Message.parts[]`. AAP v1.3.0 is compliant with the A2A **v1.0.x** line, **including A2A v1.0.1** — a non-breaking patch that changed no AgentCard, AgentSkill, or message field. Per A2A §3.6 the agent card advertises the `Major.Minor` version only, so AAP cards keep `protocolVersion: "1.0"` (patch numbers are never put on the wire). A2A v1.0.1's one transport nudge — preferring `application/a2a+json` on the HTTP+JSON binding — does not apply to AAP, which uses JSON-RPC 2.0 exclusively. AAP publishes each skill's request/response JSON Schema URLs in `capabilities.extensions[].params` (a free-form A2A `Struct`) rather than on the `AgentSkill` object, because A2A's `AgentSkill` has no schema field in v1.0 or v1.0.1 and strict A2A card parsers reject unknown skill fields. ## The three layers of A2A A2A is structured in three layers. AAP sits as a profile that constrains layer 1 (data model). A2A defines several bindings; AAP rides on exactly one — JSON-RPC 2.0 — with the same data model on every call. ## Where AAP fits AAP is a layer 1 profile. It defines: 1. **Standard skill vocabulary.** Five canonical `skills[].id` values an AAP-compliant agent card draws from: `dealer.information`, `inventory.facets`, `inventory.search`, `inventory.vehicle`, `lead.submit`. An agent declares the subset it actually implements (one or more); none is individually mandatory. AAP RECOMMENDS at least `inventory.search` + `lead.submit` for an end-to-end shopping flow. 2. **Typed `DataPart` payloads.** For each skill, an exact request and response JSON Schema. Each payload includes a `type` field whose value is `..request` or `..response` (e.g. `inventory.search.request`). The AAP version is announced once via the agent-card extension URI; it is not repeated on the wire. 3. **An extension URI.** `https://autoagentprotocol.org/extensions/aap/v1.3`, declared in `capabilities.extensions[]` of the agent card. AAP does NOT redefine layer 2 (abstract operations) or layer 3 (protocol bindings) — it deliberately uses a minimal slice of each. AAP uses exactly **one** A2A operation: `SendMessage` (the message-only pattern — request `Message` in, response `Message` out). The optional A2A surface (`SendStreamingMessage`, the tasks Get/List/Cancel/Subscribe operations, push notification configs, `GetExtendedAgentCard`) is out of scope for AAP — dealer agents do not need to implement it, and buyer agents MUST NOT require it. On bindings: JSON-RPC 2.0 is the **sole** binding AAP defines — a JSON-RPC interface is REQUIRED on every AAP agent card. The HTTP+JSON (REST) binding was removed in v1.1.0, and gRPC is out of scope. ## The typed `DataPart` pattern A2A messages are composed of one or more `parts`. Each part identifies its kind by the member it carries — a part with a `text` member is a `TextPart`, with a `file` member is a `FilePart`, with a `data` member is a `DataPart`. AAP only uses `DataParts` — it never relies on free-text natural-language parsing for protocol semantics. A `DataPart` looks like this: ```json { "data": { "type": "inventory.search.request", "filters": { "make": ["Honda"] }, "pagination": { "skip": 0, "limit": 20 } }, "mediaType": "application/vnd.autoagent.inventory-search-request+json" } ``` The `type` field is the AAP-typed identifier (e.g. `inventory.search.request`). Every AAP request and response carries a `type` matching the regex `^[a-z_]+(\.[a-z_]+){1,2}$`. This lets a buyer agent or middleware validate the payload against the right schema without inspecting the surrounding A2A envelope. The `mediaType` field on the part advertises the AAP media type so generic A2A middleware can route or filter parts without parsing the inner `data`. **Note: A2A v1.0 wire format — the single canonical ProtoJSON form** AAP rides on **A2A v1.0**, whose single canonical wire format is the ProtoJSON form: the method is `SendMessage`, `Role` is the enum name `"ROLE_USER"` (buyer agent) / `"ROLE_AGENT"` (dealer response), and a `Part` has no `kind` discriminator (it is typed by the member it carries — AAP uses the `data` member). The `Message` has no `kind` discriminator either. **A compliant AAP agent MUST emit and accept this form** so any A2A v1.0 client and the published A2A SDKs (`a2a-js`, `a2a-python`) can parse its replies. Every `Message` carries a unique `messageId`. ### Concrete example: `inventory.search` A full A2A `Message` carrying an AAP request: ```json { "messageId": "01HZ9Q2V5L8F1U3ABV6K1ETBDEX", "role": "ROLE_USER", "parts": [ { "data": { "type": "inventory.search.request", "filters": { "make": ["Honda"], "condition": ["used", "cpo"], "year_min": 2020, "price_max": 30000 }, "pagination": { "skip": 0, "limit": 20 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } }, "mediaType": "application/vnd.autoagent.inventory-search-request+json" } ] } ``` The dealer agent replies with an A2A `Message` containing the AAP response: ```json { "messageId": "01HZ9Q2W9SH5ZB6DUA0J1K2L3M", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "inventory.search.response", "data": { "total": 1, "skip": 0, "limit": 20, "vehicles": [ { "dealer_id": "dealer_demo_toyota", "vin": "1HGCY2F57RA000001", "stock": "T12345", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "condition": "cpo", "status": "available", "list_price": 24990, "price": 26780, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1290 } ], "inventory_date": "2026-04-12", "updated_at": "2026-04-30T10:15:00Z" } ] } }, "mediaType": "application/vnd.autoagent.inventory-search-response+json" } ] } ``` The outer envelope around this `Message` is the JSON-RPC 2.0 binding — AAP's sole transport. See JSON-RPC binding for full envelope examples. ## Why typed `DataParts` instead of natural-language parts An AI agent could in principle stuff a search query into a `TextPart` and let the dealer's LLM parse it. AAP does not allow that for protocol calls because: - Buyer agents need deterministic schemas to plan and validate. - Dealer CRMs need structured data to write to ADF/XML and downstream lead pipes. - Pricing and consent are regulated; ambiguous natural language increases compliance risk. - Validation tooling (Ajv, ajv-formats, OpenAPI clients) only works on structured payloads. Protocol calls use typed JSON `DataParts` ONLY; dealer agents MUST ignore any `TextParts`. AAP defines no protocol semantics for free-text parts — only the typed `DataPart` is normative, and a dealer agent MUST NOT derive any protocol meaning from a `TextPart`. ## Discovery and bindings: the rest of the profile AAP layers one more piece on top of the base A2A surface: | Piece | Where it lives | Purpose | |---|---|---| | Agent card with AAP extension | `/.well-known/agent-card.json` | A2A discovery; declares the AAP extension URI and lists the subset of AAP skills the agent implements. | | Binding section | A2A Section 9 | How AAP DataParts ride inside JSON-RPC 2.0 envelopes — the sole AAP binding, REQUIRED on every AAP agent card. | See: - Discovery for the agent card. - JSON-RPC binding for the wire format. # Discovery Every AAP-compliant dealer agent publishes an A2A v1.0 agent card at the well-known URL on its own domain: ``` GET https://{dealer-domain}/.well-known/agent-card.json ``` The card MUST declare the AAP extension and list the AAP skills the agent implements (one or more from the vocabulary of five). The buyer agent uses the card to confirm AAP compliance and discover which skills are actually available before calling any skill. AAP v1.3.0 uses a single transport — JSON-RPC 2.0. ## Required AAP additions to the A2A agent card The AgentCard structure itself is defined by [A2A](https://a2a-protocol.org/latest/specification/) — AAP does not redefine it. AAP only narrows it: an AAP-compliant agent card MUST satisfy all of: 1. `capabilities.extensions[]` contains an entry whose `uri` equals: ``` https://autoagentprotocol.org/extensions/aap/v1.3 ``` 2. `skills[]` contains one entry per AAP skill the agent implements (one or more). Buyer agents discover capability from `skills[]`, not from the AAP extension URI alone. AAP RECOMMENDS that an agent expose at least `inventory.search` + `lead.submit` for a meaningful shopping experience, but no single skill is individually required. 3. `supportedInterfaces[]` includes an entry whose `protocolBinding` is `JSONRPC` (REQUIRED on every AAP agent card). JSON-RPC 2.0 is the sole AAP binding; the HTTP+JSON (REST) binding was removed in v1.1.0, and gRPC is out of scope for AAP v1.3.0. A buyer agent that does not find a matching extension URI MUST treat the agent as a generic A2A agent, not as an AAP dealer agent. ## Authentication AAP v1.3.0 agents are **public by default** — the simplest setup needs no authentication. AAP defines no auth of its own. A dealer that wants to protect its endpoint uses A2A's native `securitySchemes` / `securityRequirements` on the agent card (e.g. HTTP bearer), and buyer agents obtain credentials out of band, exactly as A2A specifies. Auth is therefore an A2A/transport concern, out of scope for the v1.3.0 profile beyond what A2A already provides. ## Full example agent card This is the **smallest** card that satisfies the three requirements above — a public dealer agent on the JSON-RPC binding. Copy it, change the `name`, the `supportedInterfaces[].url`, and `params.id`, and keep only the skills you actually implement — pruning both `skills[]` and `params.skills` to match. A copy-pasteable copy is published at [`/v1.3/examples/agent-card.example.json`](https://autoagentprotocol.org/v1.3/examples/agent-card.example.json). ```json { "name": "Demo Toyota", "description": "Auto Agent Protocol dealer agent for Demo Toyota — browse inventory and submit consented leads over A2A.", "version": "1.0.0", "provider": { "organization": "Lumika AI", "url": "https://lumika.ai" }, "supportedInterfaces": [ { "url": "https://demo-toyota.example.com/a2a", "protocolBinding": "JSONRPC", "protocolVersion": "1.0" } ], "capabilities": { "extensions": [ { "uri": "https://autoagentprotocol.org/extensions/aap/v1.3", "description": "Auto Agent Protocol v1.3.0 — A2A Automotive Retail Profile.", "required": true, "params": { "id": "0192f3c0-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "version": "1.3.0", "schema_base_url": "https://autoagentprotocol.org/v1.3/schemas/", "skills": { "dealer.information": { "request_schema": "https://autoagentprotocol.org/v1.3/schemas/dealer-information-request.schema.json", "response_schema": "https://autoagentprotocol.org/v1.3/schemas/dealer-information-response.schema.json" }, "inventory.facets": { "request_schema": "https://autoagentprotocol.org/v1.3/schemas/inventory-facets-request.schema.json", "response_schema": "https://autoagentprotocol.org/v1.3/schemas/inventory-facets-response.schema.json" }, "inventory.search": { "request_schema": "https://autoagentprotocol.org/v1.3/schemas/inventory-search-request.schema.json", "response_schema": "https://autoagentprotocol.org/v1.3/schemas/inventory-search-response.schema.json" }, "inventory.vehicle": { "request_schema": "https://autoagentprotocol.org/v1.3/schemas/vehicle-detail-request.schema.json", "response_schema": "https://autoagentprotocol.org/v1.3/schemas/vehicle-detail-response.schema.json" }, "lead.submit": { "request_schema": "https://autoagentprotocol.org/v1.3/schemas/lead-submit-request.schema.json", "response_schema": "https://autoagentprotocol.org/v1.3/schemas/lead-submit-response.schema.json" } } } } ] }, "defaultInputModes": ["application/json"], "defaultOutputModes": ["application/json"], "skills": [ { "id": "dealer.information", "name": "Dealer Information", "description": "Return the dealership profile: group name, welcome message, and rooftops with address, hours, contacts, and capabilities.", "tags": ["dealer", "dealership", "profile", "hours", "contact", "locations", "automotive"] }, { "id": "inventory.facets", "name": "Inventory Facets", "description": "Return aggregated facets (makes, models, years, conditions, price/mileage ranges, statuses) over the dealer's inventory.", "tags": ["inventory", "facets", "aggregation", "filters", "automotive"] }, { "id": "inventory.search", "name": "Inventory Search", "description": "Search the dealer's vehicle inventory by make, model, year, condition, price, mileage, body, fuel, drivetrain, VIN, or stock.", "tags": ["inventory", "vehicles", "search", "cars", "automotive"] }, { "id": "inventory.vehicle", "name": "Vehicle Detail", "description": "Return full detail for a specific vehicle by VIN, stock number, or vehicle_id.", "tags": ["inventory", "vehicle", "vin", "stock", "detail", "automotive"] }, { "id": "lead.submit", "name": "Submit Lead", "description": "Submit a consented lead with optional vehicle of interest, trade-in, and appointment.", "tags": ["lead", "contact", "consent", "sales", "appointment", "automotive"] } ] } ``` `provider` names who operates the agent. The AAP extension's `params.id` is a unique identifier (UUID v7 recommended) the dealer regenerates whenever the card changes — onboarding tools cache it to cheaply detect changes. The published per-skill request/response JSON Schemas also live inside the extension `params` — under `capabilities.extensions[].params.skills[""].request_schema` / `response_schema` — not as fields on the A2A `skills[]` entries (strict A2A proto parsers reject unknown skill fields). Both `params` and any AAP-specific data live inside the extension entry, which is the only A2A-sanctioned place for it. Each skill carries the A2A-required `tags` (keywords clients/LLMs use to categorize and rank skills). Everything else is **optional** A2A surface a dealer MAY add to the same card — `documentationUrl`, per-skill `inputModes`, or `securitySchemes` + `securityRequirements` for auth. Note that the optional A2A surface beyond `SendMessage` (streaming, tasks, push notification configs, extended agent card) is out of scope for AAP v1.3.0 — dealer agents do not need to implement it and buyer agents MUST NOT require it. The AgentCard shape is A2A's; see the [A2A spec](https://a2a-protocol.org/latest/specification/). ## What a buyer agent does next Once the card is fetched and validated: 1. Read `skills[]` from the card to learn which AAP skills the agent implements. The request/response JSON Schema for each skill is defined by the AAP spec itself (this docs site), version-pinned by the extension URI, and the card publishes the schemas inline under `capabilities.extensions[].params.skills[""].request_schema` / `response_schema`. 2. Invoke skills via standard A2A `SendMessage` over the JSON-RPC binding — JSON-RPC 2.0 is the only AAP transport (the REST binding was removed in v1.1.0). `SendMessage` is the only A2A operation AAP uses: request `Message` in, response `Message` out. If the card declares A2A `securitySchemes`, obtain credentials out of band first. # Pricing and fee disclosure AAP separates a vehicle's base pricing context from the price a dealer actually advertises, and makes mandatory dealer charges explicit. > **`price` is the authoritative advertised vehicle price.** It includes every mandatory, non-government dealer charge and dealer-required add-on. An optional `fees` array can itemize charges already included in `price`; never add them to `price` again. `price` is deliberately **not** called an out-the-door price. Sales tax, title, registration, and other required government charges vary by buyer and jurisdiction and are excluded. A true out-the-door quote requires buyer-specific context. This page defines protocol behavior, not legal advice. Dealers remain responsible for federal, state, and local requirements. ## Current FTC context In March 2026, the FTC warned 97 auto dealership groups that advertised prices must include all mandatory fees. The agency also identified advertisements that use rebates unavailable to all consumers, require an extra down payment, condition price on dealer financing, or omit required add-ons as potentially illegal pricing practices under Section 5 of the FTC Act. - [FTC announcement about deceptive auto pricing (March 2026)](https://www.ftc.gov/news-events/news/press-releases/2026/03/ftc-warns-97-auto-dealership-groups-about-deceptive-pricing) - [FTC sample warning letter](https://www.ftc.gov/system/files/ftc_gov/pdf/warning-letter-to-best-price-dealer.pdf) The FTC's separate CARS Rule is **not in force**. The Fifth Circuit [vacated it on January 27, 2025](https://www.ca5.uscourts.gov/opinions/pub/24/24-60013-CV0.pdf), and the FTC later [withdrew the vacated rule](https://public-inspection.federalregister.gov/2026-02866.pdf). AAP therefore does not claim that the CARS Rule is the source of these contract requirements. The contract instead adopts a transparent pricing model that aligns with the FTC's current enforcement statements and makes safer comparison possible. The FTC's current automotive materials require the truthful total, but do not establish a general federal requirement to itemize every mandatory dealer fee in the advertisement. AAP therefore permits an authoritative `price` without `fees`. The optional breakdown supports transparency, state-specific implementations, and systems that already carry itemized charges without making federal compliance depend on it. ## Pricing fields Every amount is an integer in whole US dollars. | Field | Required? | Meaning | Buyer-agent treatment | |---|---|---|---| | `msrp` | optional | Manufacturer's Suggested Retail Price. | Context only. | | `list_price` | optional | Dealer's base list price before discounts, rebates, mandatory dealer charges, and required add-ons. | Context only; never present it as the payable price. | | `price` | optional | Authoritative advertised vehicle price after universally available discounts and including all mandatory dealer charges and required add-ons. Excludes government charges. | Use for price filters, sorting, and comparisons. It may stand alone. | | `fees` | optional | When present, the complete effective itemization of mandatory, non-government dealer charges and required add-ons for the vehicle. | With `price`, display as an included breakdown; do not add the amounts again. Without `price`, treat as informational only. | `price` may reflect only discounts or rebates available to every consumer. A discount that depends on military service, recent graduation, loyalty, conquest, financing choice, trade-in, or another eligibility condition must not reduce `price`. AAP does not add a second `final_price` field. `price` is the one authoritative advertised total; two competing totals would invite ambiguity and stale data. A buyer-specific out-the-door quote is a later calculation outside this inventory contract. ## Fee states `fees` has three intentionally different states: | Representation | Meaning | |---|---| | field omitted | An itemized breakdown was not provided. An otherwise authoritative `price` remains valid. | | `"fees": []` | The publisher affirmatively reports no mandatory dealer charges or required add-ons for this vehicle. | | non-empty `fees` | The array is the complete effective fee snapshot for this vehicle. | Each fee is `{ "name": string, "amount": integer }`. It covers mandatory, non-government dealer charges and dealer-required add-ons. It excludes optional products and government charges such as tax, title, and registration. ```json { "name": "Documentation fee", "amount": 500 } ``` ## Rooftop defaults versus vehicle snapshots A rooftop MAY publish `fees` in `dealer.information` as its complete default schedule. This avoids repeating configuration inside a dealer's own source system, but it does not create a consumer-side join. The resolution rule is simple: 1. The inventory publisher MAY start with the rooftop defaults. 2. It resolves any vehicle-specific differences before returning inventory. 3. When the publisher supplies vehicle `fees`, it emits the complete effective itemization rather than a delta. 4. Buyer agents use only `Vehicle.fees` for itemized vehicle charges. They MUST NOT fetch `dealer.information` to complete a breakdown, join by the mutable rooftop name, or merge rooftop and vehicle fee arrays. When vehicle fees differ from rooftop defaults, `Vehicle.fees` replaces the rooftop array in full. It is never a delta. This keeps inventory responses self-contained, deterministic, and safe when dealer information is missing or cached at a different time. ## Arithmetic and discounts The protocol does not require `list_price + sum(fees) == price`. For example, a universally available discount can make the values differ: ```json { "vehicle_id": "vehicle_demo_civic", "year": 2022, "make": "Honda", "model": "Civic", "condition": "cpo", "msrp": 26500, "list_price": 24990, "price": 26280, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1000 } ], "status": "available", "updated_at": "2026-04-30T10:15:00Z" } ``` Here, the dealer applied a $210 discount available to every buyer: `$24,990 - $210 + $1,500 = $26,280`. `price` remains authoritative. Buyer agents display `$26,280` and the two included fees; they do not derive a replacement price from `list_price`. If the relationship is not explained or cannot be reconstructed, consumers still use `price` and SHOULD treat any `fees` as its included breakdown. They SHOULD suppress or de-emphasize `list_price` rather than inventing a discount. Future protocol work may add a structured incentives model; `fees` must not be overloaded for discounts. When `list_price` and `fees` are supplied without `price`, both are informational. A buyer agent may label them separately as a base/list amount and known mandatory fee itemization, but it MUST NOT present either value—or their sum—as the purchasable price. If the publisher can certify that their sum is the complete advertised amount, it publishes that amount as `price`. ## Provider mapping | Provider data | AAP mapping | |---|---| | Complete advertised price, fee breakdown unavailable | Publish `price`; omit `fees`. | | Complete advertised price plus complete fee itemization | Publish `price` and `fees` (`[]` when there are affirmatively no mandatory dealer charges). | | Base or list amount only | Publish `list_price`; omit `price`. | | Base/list amount plus known mandatory fees, authoritative total unavailable | Publish `list_price` and `fees`; omit `price`. Consumer agents treat both as informational and do not derive a price. | | Amount reduced by a conditional rebate | Do not publish it as `price`. Publish the non-conditional advertised amount, or omit `price` if that amount is unavailable. | | Rooftop defaults plus vehicle exceptions | If publishing vehicle fees, resolve the defaults inside the publisher and emit one complete `Vehicle.fees` array. | ## Normative behavior - Publishers MUST include all mandatory, non-government dealer charges and required add-ons in `price`. - Publishers MAY omit `fees` when an itemized breakdown is unavailable; omission does not invalidate an otherwise authoritative `price`. - When publishers include `fees`, the array MUST be the complete effective vehicle itemization, and every amount MUST already be included in `price` when `price` is present. - Publishers MUST NOT reduce `price` with a rebate or discount unavailable to every consumer. - Publishers MUST NOT reduce `price` by an additional required down payment or condition it on dealer financing. - Publishers MUST NOT include optional products or government charges in `fees`. - Buyer agents MUST NOT add `fees[].amount` to `price`. - Buyer agents MUST NOT derive an advertised price from `list_price`, `fees`, or their sum when `price` is absent. - Buyer agents MUST use `price`, not `list_price`, for `price_min`, `price_max`, `sort.field: "price"`, and cross-dealer comparisons. - Buyer agents MUST NOT infer missing fees from a rooftop or another vehicle. `inventory.facets.price_range` likewise aggregates available `price` values. Vehicles without `price` remain valid inventory results, but they cannot safely participate in price-based comparisons without provider-specific behavior that the protocol does not standardize. # JSON-RPC 2.0 binding A2A defines a JSON-RPC 2.0 binding in [Section 9](https://a2a-protocol.org/specification#section-9) of its specification. AAP rides on top of **A2A v1.0** without modification, and uses JSON-RPC 2.0 as its **sole** transport: every skill is invoked via the `SendMessage` JSON-RPC method, with the AAP request packaged as a typed `DataPart` inside `params.message.parts[]`. **Note: JSON-RPC is the SOLE binding** A JSON-RPC interface is **REQUIRED** on every AAP agent card: `supportedInterfaces[]` MUST include at least one entry with `protocolBinding: "JSONRPC"`. JSON-RPC 2.0 is the **only** transport AAP defines — the HTTP+JSON (REST) binding was removed in v1.1.0, and gRPC is out of scope. **Info: A2A v1.0 wire format — the ProtoJSON form** AAP rides on A2A v1.0, whose single canonical wire format is the ProtoJSON form: the method is `SendMessage`, `Role` is the enum name `"ROLE_USER"` / `"ROLE_AGENT"`, and a `Part` has no `kind` discriminator (it is typed by the member it carries — AAP uses the `data` member). A compliant AAP agent **MUST** emit and accept this form so any A2A v1.0 client can parse its replies. | Aspect | A2A v1.0 (ProtoJSON) | |---|---| | Method name | `SendMessage` | | Role | `"ROLE_USER"` / `"ROLE_AGENT"` | | Part discriminator | member-name (no `kind`) | | Message discriminator | (none — no `kind`) | | `result` (JSON-RPC) | the `SendMessageResponse`, i.e. `{ "message": }` | | `messageId` | required on every `Message` | | `mediaType` on DataPart | `application/vnd.autoagent.-request+json` | ## Endpoint and method A dealer agent advertises one or more JSON-RPC endpoints under `supportedInterfaces[]` of its agent card. Each entry has `protocolBinding: "JSONRPC"` and a `url`. ``` POST {jsonrpc-url} Content-Type: application/json ``` All AAP skills use a single JSON-RPC method: ``` "method": "SendMessage" ``` `SendMessage` is the **only** A2A operation AAP uses (message-only pattern: request `Message` in, response `Message` out). The optional A2A surface — `SendStreamingMessage`, the `tasks` operations (Get/List/Cancel/Subscribe), push notification configs, and `GetExtendedAgentCard` — is out of scope for AAP: dealer agents do not need to implement it, and buyer agents MUST NOT require it. The `id` field is the standard JSON-RPC request id; AAP does not constrain it. The `params.message` is an A2A `Message` whose first `parts[]` entry is the typed AAP `DataPart`. A buyer agent MUST also include `params.configuration.acceptedOutputModes` listing the AAP response media type it expects. ## Generic envelope Every AAP request looks like this on the wire: ```json { "jsonrpc": "2.0", "id": "req-1", "method": "SendMessage", "params": { "message": { "messageId": "01HZ9F4M7C0X3K5RN8B3WJTW2P", "role": "ROLE_USER", "parts": [ { "data": { "type": "..request", "...": "skill-specific fields" }, "mediaType": "application/vnd.autoagent.-request+json" } ] }, "configuration": { "acceptedOutputModes": [ "application/vnd.autoagent.-response+json" ] } } } ``` The JSON-RPC `result` is the `SendMessageResponse`, which ProtoJSON serializes as `{ "message": }` — so the agent `Message` is wrapped under `result.message`: ```json { "jsonrpc": "2.0", "id": "req-1", "result": { "message": { "messageId": "01HZ9F4N1JZ7QS8VKR2A3B4C5D", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "..response", "data": { "...": "skill-specific response data" } }, "mediaType": "application/vnd.autoagent.-response+json" } ] } } } ``` The `messageId` on the response is generated by the dealer agent; it MUST differ from the `messageId` the buyer agent sent on the request. The remainder of this page shows the full envelope for each of the five skills. ## `dealer.information` ### Request ```json { "jsonrpc": "2.0", "id": "req-1", "method": "SendMessage", "params": { "message": { "messageId": "01HZ9G5N8D1Y4M6SP9C4XKVW3Q", "role": "ROLE_USER", "parts": [ { "data": { "type": "dealer.information.request" }, "mediaType": "application/vnd.autoagent.dealer-information-request+json" } ] }, "configuration": { "acceptedOutputModes": [ "application/vnd.autoagent.dealer-information-response+json" ] } } } ``` ### Response ```json { "jsonrpc": "2.0", "id": "req-1", "result": { "message": { "messageId": "01HZ9G5P2KA8RT9WMS3B4C5D6E", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "dealer.information.response", "data": { "name": "Demo Auto Group", "rooftops": [ { "name": "Demo Toyota San Francisco", "legal_name": "Demo Toyota of San Francisco, LLC", "website": "https://demo-toyota.example.com", "geo": { "latitude": 37.77, "longitude": -122.41 }, "emails": [ { "name": "Sales", "value": "sales@demo-toyota.example.com" } ], "phones": [ { "name": "Sales", "value": "+14155550100" } ], "address": { "country": "US", "state": "CA", "city": "San Francisco", "address_line_1": "100 Market St", "zip": "94105" }, "timezone": "America/Los_Angeles", "capabilities": [ "sales", "service", "financing", "trade_in" ] } ] } }, "mediaType": "application/vnd.autoagent.dealer-information-response+json" } ] } } } ``` ## `inventory.facets` ### Request ```json { "jsonrpc": "2.0", "id": "req-2", "method": "SendMessage", "params": { "message": { "messageId": "01HZ9H6P9E2Z5N7TQ0D5YMWX4R", "role": "ROLE_USER", "parts": [ { "data": { "type": "inventory.facets.request", "filters": { "condition": [ "used" ] } }, "mediaType": "application/vnd.autoagent.inventory-facets-request+json" } ] }, "configuration": { "acceptedOutputModes": [ "application/vnd.autoagent.inventory-facets-response+json" ] } } } ``` ### Response ```json { "jsonrpc": "2.0", "id": "req-2", "result": { "message": { "messageId": "01HZ9H6Q3KB9SV0XNT4C5D6E7F", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "inventory.facets.response", "data": { "makes": [ { "value": "Honda", "count": 12 }, { "value": "Toyota", "count": 27 } ], "conditions": [ { "value": "used", "count": 39 } ], "year_range": { "min": 2015, "max": 2024 }, "price_range": { "min": 9990, "max": 38990 } } }, "mediaType": "application/vnd.autoagent.inventory-facets-response+json" } ] } } } ``` ## `inventory.search` ### Request ```json { "jsonrpc": "2.0", "id": "req-3", "method": "SendMessage", "params": { "message": { "messageId": "01HZ9F4M7C0X3K5RN8B3WJTW2P", "role": "ROLE_USER", "parts": [ { "data": { "type": "inventory.search.request", "filters": { "make": [ "Honda" ], "condition": [ "used", "cpo" ], "year_min": 2020, "price_max": 30000 }, "pagination": { "skip": 0, "limit": 20 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } }, "mediaType": "application/vnd.autoagent.inventory-search-request+json" } ] }, "configuration": { "acceptedOutputModes": [ "application/vnd.autoagent.inventory-search-response+json" ] } } } ``` ### Response ```json { "jsonrpc": "2.0", "id": "req-3", "result": { "message": { "messageId": "01HZ9F4N1JZ7QS8VKR2A3B4C5D", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "inventory.search.response", "data": { "total": 1, "skip": 0, "limit": 20, "vehicles": [ { "dealer_id": "dealer_demo_toyota", "vin": "1HGCY2F57RA000001", "stock": "T12345", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "condition": "cpo", "list_price": 24990, "price": 26780, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1290 } ], "status": "available", "rooftop": "Demo Toyota San Francisco", "inventory_date": "2026-04-12", "updated_at": "2026-04-30T10:15:00Z" } ] } }, "mediaType": "application/vnd.autoagent.inventory-search-response+json" } ] } } } ``` ## `inventory.vehicle` ### Request ```json { "jsonrpc": "2.0", "id": "req-4", "method": "SendMessage", "params": { "message": { "messageId": "01HZ9J7Q0F3A6P8VR1E6ZNXY5S", "role": "ROLE_USER", "parts": [ { "data": { "type": "inventory.vehicle.request", "vin": "1HGCY2F57RA000001" }, "mediaType": "application/vnd.autoagent.vehicle-detail-request+json" } ] }, "configuration": { "acceptedOutputModes": [ "application/vnd.autoagent.vehicle-detail-response+json" ] } } } ``` ### Response ```json { "jsonrpc": "2.0", "id": "req-4", "result": { "message": { "messageId": "01HZ9J7R4MC0TW1YPV5D6E7F8G", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "inventory.vehicle.response", "data": { "dealer_id": "dealer_demo_toyota", "vin": "1HGCY2F57RA000001", "stock": "T12345", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "condition": "cpo", "msrp": 26500, "list_price": 24990, "price": 26780, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1290 } ], "status": "available", "rooftop": "Demo Toyota San Francisco", "city_mpg": 31, "highway_mpg": 40, "features": [ "Adaptive Cruise Control", "Apple CarPlay", "Lane Keep Assist" ], "vdp_url": "https://demo-toyota.example.com/inventory/T12345", "inventory_date": "2026-04-12", "updated_at": "2026-04-30T10:15:00Z" } }, "mediaType": "application/vnd.autoagent.vehicle-detail-response+json" } ] } } } ``` ## `lead.submit` The unified lead carries customer info plus any combination of `vehicle_of_interest`, `trade_in`, and `appointment`. Below: a single test-drive lead that also queues the buyer's trade-in for in-person appraisal. ### Request ```json { "jsonrpc": "2.0", "id": "req-5", "method": "SendMessage", "params": { "message": { "messageId": "01HZ9K8R1G4B7Q9WS2F7APYZ6T", "role": "ROLE_USER", "parts": [ { "data": { "type": "lead.submit.request", "customer": { "first_name": "Anna", "last_name": "Lee", "email": "anna@example.com", "phone": "+14155550123", "preferred_contact": "phone", "address": { "address_line_1": "200 Folsom St", "city": "San Francisco", "state": "CA", "zip": "94105" } }, "consent": { "granted_at": "2026-04-30T10:16:00Z", "allowed_channels": [ "email", "phone" ], "consent_text": "I agree to share my contact info with Demo Toyota about VIN 1HGCY2F57RA000001, my Saturday test drive, and the trade-in of my 2014 Toyota Corolla.", "scope": [ "lead_submission" ] }, "vehicle_of_interest": { "vin": "1HGCY2F57RA000001", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "condition": "cpo" }, "trade_in": { "year": 2014, "make": "Toyota", "model": "Corolla", "condition": "good", "mileage": 96000 }, "appointment": { "appointment_type": "test_drive", "appointment_at": "2026-05-02T17:00:00Z", "duration_minutes": 60 }, "message": "Interested in this Civic; is it still available? Please appraise my Corolla at the same visit.", "source_agent": { "name": "chatgpt-shopping", "url": "https://chatgpt.com", "agent_card_url": "https://chatgpt.com/.well-known/agent-card.json" }, "submitted_at": "2026-04-30T10:16:05Z" }, "mediaType": "application/vnd.autoagent.lead-submit-request+json" } ] }, "configuration": { "acceptedOutputModes": [ "application/vnd.autoagent.lead-submit-response+json" ] } } } ``` ### Response ```json { "jsonrpc": "2.0", "id": "req-5", "result": { "message": { "messageId": "01HZ9K8S5ND1VX2ZQW6E7F8G9H", "role": "ROLE_AGENT", "parts": [ { "data": { "type": "lead.submit.response", "data": { "lead_id": "lead_2026_04_30_anna_001", "status": "received", "appointment": { "appointment_id": "appt_2026_04_30_anna_001", "status": "confirmed", "confirmed_at": "2026-05-02T17:00:00Z" }, "dealer": { "name": "Demo Toyota", "phone": "+14155550100" } }, "message": "Confirmed for Saturday at 10am Pacific. Please bring a valid driver's license. Your Corolla is queued for in-person appraisal at the same visit." }, "mediaType": "application/vnd.autoagent.lead-submit-response+json" } ] } } } ``` ## Error mapping (A2A Section 9.5) When a skill cannot be fulfilled, the dealer agent MUST return a JSON-RPC error envelope. AAP defines a typed error payload (`aap.error`) carried in `error.data`. The mapping uses the standard JSON-RPC `code` for transport errors and AAP's own `code` for business errors. Validation errors (`SCHEMA_VALIDATION_FAILED`, `MISSING_REQUIRED_FIELD`, `INVALID_CONDITION`) MUST list **every** failing field at once in `details.errors[]` — each entry carrying `instanceLocation`, `keyword`, and `error` — so the buyer agent can fix the whole payload in a single retry instead of one round-trip per error. See Errors for the full shape. ```json { "jsonrpc": "2.0", "id": "req-3", "error": { "code": -32602, "message": "Invalid params: 2 validation errors", "data": { "type": "aap.error", "error_id": "err_01HZ9EXAMPLE", "code": "SCHEMA_VALIDATION_FAILED", "message": "Request failed validation with 2 errors; see details.errors[].", "retryable": false, "details": { "errors": [ { "instanceLocation": "/filters/year_min", "keyword": "type", "error": "must be integer" }, { "instanceLocation": "/filters/condition/0", "keyword": "enum", "error": "must be one of: new, used, cpo" } ] }, "created_at": "2026-04-30T10:15:30Z" } } } ``` Recommended JSON-RPC code mapping: | AAP `code` | JSON-RPC `code` | Notes | |---|---|---| | `SCHEMA_VALIDATION_FAILED` | -32602 | JSON-RPC "Invalid params". | | `MISSING_REQUIRED_FIELD` | -32602 | "Invalid params". | | `INVALID_CONDITION` | -32602 | "Invalid params" — a `condition` value is in the wrong vocabulary. | | `UNSUPPORTED_SKILL` | -32601 | JSON-RPC "Method not found" — the dealer does not implement this skill (rare for AAP-compliant agents but allowed for forward compat). | | `VEHICLE_NOT_FOUND` | -32000 | Application error. | | `VEHICLE_UNAVAILABLE` | -32000 | Application error. | | `CONTACT_CONSENT_REQUIRED` | -32000 | Application error. | | `INVALID_CONSENT` | -32000 | Application error. | | `APPOINTMENT_TIME_UNAVAILABLE` | -32000 | Application error. | | `IDEMPOTENCY_CONFLICT` | -32000 | Application error — an `idempotency_key` was reused with a different payload. | | `RATE_LIMITED` | -32002 | Reserved server-error range; AAP-specific. | | `INTERNAL_ERROR` | -32603 | JSON-RPC "Internal error". | See Errors for the full vocabulary and per-code semantics. # HTTP+JSON (REST) binding — removed in v1.1.0 **Danger: Removed in v1.1.0 — AAP is JSON-RPC only** The HTTP+JSON (REST) binding is **no longer part of AAP** as of v1.1.0. AAP rides on a single transport: the **JSON-RPC 2.0 binding** (A2A Section 9). There is no `POST /message:send` REST surface in AAP; `SendMessage` is invoked exclusively over JSON-RPC. This page is kept only so existing links do not 404. For the current, normative transport, see the **JSON-RPC 2.0 binding**. In AAP v1.0.0, a dealer agent MAY have additionally exposed an optional HTTP+JSON interface alongside its required JSON-RPC interface. AAP v1.1.0 drops that option: every AAP agent speaks JSON-RPC 2.0, and only JSON-RPC 2.0. Buyer agents and dealer agents MUST use the JSON-RPC binding. # `dealer.information` **Info: A2A invocation** This skill is invoked through A2A's `SendMessage` operation — the `SendMessage` JSON-RPC method on AAP's sole transport, the JSON-RPC binding — not a dedicated REST URL. (The HTTP+JSON binding was removed in v1.1.0.) AAP only defines what goes inside `Message.parts[].data`. The `dealer.information` skill returns a dealership's static profile. It is the simplest AAP call: the request carries no parameters, the response carries a [`DealerInformation`](https://autoagentprotocol.org/v1.3/schemas/dealer-information.schema.json) object describing the dealer group and its rooftops — each with identity, address, contact channels, business hours, default dealer fees, and service capabilities. | Property | Value | |---|---| | Skill id | `dealer.information` | | Request type | `dealer.information.request` | | Response type | `dealer.information.response` | | Anonymous allowed | yes | | Consent required | no | | ADF compatible | no | ## Request shape The request has a single field — the AAP type identifier. There are no parameters. ```json { "type": "dealer.information.request" } ``` | Field | Type | Required | Description | |---|---|---|---| | `type` | string const | yes | Always `dealer.information.request`. | `additionalProperties: false`. The request is invalid if any other property is present. ## Response shape The response wraps a `DealerInformation` object inside the standard AAP response envelope. A `DealerInformation` is a dealer group (`name` + optional `welcome_message`) plus one or more `rooftops`, where each rooftop is an individual dealership location: ```json { "type": "dealer.information.response", "data": { "name": "string", "welcome_message": "string", "rooftops": [ { "name": "string", "legal_name": "string", "website": "https://...", "geo": { "latitude": 0, "longitude": 0 }, "emails": [{ "name": "string", "value": "string" }], "phones": [{ "name": "string", "value": "+1XXXXXXXXXX" }], "address": { "country": "US", "state": "CA", "city": "string", "address_line_1": "string", "address_line_2": "string", "zip": "94103" }, "schedules": [ { "name": "sales", "value": { "monday": { "open": "HH:MM", "close": "HH:MM" }, "sunday": null } } ], "timezone": "America/Los_Angeles", "notes": "string", "fees": [ { "name": "Documentation fee", "amount": 500 } ], "capabilities": ["sales", "service", "parts", "financing", "trade_in", "delivery"] } ] }, "message": "Optional contextual note from the dealer." } ``` `data` carries the full `DealerInformation` object. The only required top-level fields are `name` and `rooftops` (at least one). Within each rooftop, only `name` is required; everything else is optional. | Field | Type | Required | Notes | |---|---|---|---| | `data.name` | string | yes | Dealer group name. | | `data.welcome_message` | string | no | Optional greeting surfaced to the buyer. | | `data.rooftops[]` | `Rooftop[]` | yes | One entry per dealership location; at least one. | | `rooftop.name` | string | yes | Public-facing name of the location (also used by a vehicle's `rooftop` field). | | `rooftop.legal_name` | string | no | Legal/registered business name. | | `rooftop.website` | URI | no | Public website for this location. | | `rooftop.geo.latitude` / `geo.longitude` | number | no | Coordinates of the location. | | `rooftop.emails[]` | `{ name, value }[]` | no | `value` holds the email address; `name` is an optional label (e.g. "Sales"). | | `rooftop.phones[]` | `{ name, value }[]` | no | `value` holds the phone number (e.g. `+14155550100`); `name` is an optional label. | | `rooftop.address` | `Address` | no | Physical address. `country` is optional and defaults to `US`. | | `rooftop.schedules[]` | object[] | no | Named weekly hours; each entry is `{ name, value }` where `value` maps each weekday to `{ open, close }` (24h `HH:MM`) or `null` when closed. | | `rooftop.timezone` | string | no | IANA timezone identifier (e.g. `America/Los_Angeles`). | | `rooftop.notes` | string | no | Free-text notes (e.g. "closed major holidays"). | | `rooftop.fees[]` | `{ name, amount }[]` | no | Complete default schedule of mandatory, non-government dealer charges and required add-ons. Omitted = unknown; `[]` = affirmatively none. Publisher/discovery metadata only; consumers do not join it onto vehicles. | | `rooftop.capabilities[]` | string[] | no | Service capabilities, e.g. `sales`, `service`, `parts`, `financing`, `trade_in`, `delivery`. Rooftops MAY also advertise which vehicle types they sell with tags such as `motorcycle_sales` or `powersports`, so buyer agents know a rooftop's `vehicle_type` mix before searching. | ### How rooftop fees are used Rooftop `fees` are defaults for the inventory publisher, not a consumer-side inheritance mechanism. A publisher MAY start from this schedule, apply any vehicle-specific replacement, and then place the complete effective fee snapshot on the returned `Vehicle`. Vehicle-level `fees` remains optional even when `price` is present. When supplied, it is a complete itemization rather than a delta. Buyer agents MUST NOT fetch this skill to complete a vehicle breakdown or merge rooftop and vehicle arrays. ## Full example A complete response from a dealer group with two rooftops: ```json { "type": "dealer.information.response", "data": { "name": "Demo Auto Group", "welcome_message": "Welcome — happy to help by phone or video call.", "rooftops": [ { "name": "Demo Toyota San Francisco", "legal_name": "Demo Toyota of San Francisco, LLC", "website": "https://sf.demo-toyota.example.com", "geo": { "latitude": 37.7935, "longitude": -122.3946 }, "emails": [ { "name": "Sales", "value": "sales@sf.demo-toyota.example.com" } ], "phones": [ { "name": "Sales", "value": "+14155550100" }, { "name": "Service", "value": "+14155550101" } ], "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1000 } ], "address": { "country": "US", "state": "CA", "city": "San Francisco", "address_line_1": "1450 Howard Street", "zip": "94103" }, "schedules": [ { "name": "sales", "value": { "monday": { "open": "09:00", "close": "20:00" }, "tuesday": { "open": "09:00", "close": "20:00" }, "wednesday": { "open": "09:00", "close": "20:00" }, "thursday": { "open": "09:00", "close": "20:00" }, "friday": { "open": "09:00", "close": "20:00" }, "saturday": { "open": "09:00", "close": "18:00" }, "sunday": null } }, { "name": "service", "value": { "monday": { "open": "07:00", "close": "18:00" }, "tuesday": { "open": "07:00", "close": "18:00" }, "wednesday": { "open": "07:00", "close": "18:00" }, "thursday": { "open": "07:00", "close": "18:00" }, "friday": { "open": "07:00", "close": "18:00" }, "saturday": { "open": "08:00", "close": "14:00" }, "sunday": null } } ], "timezone": "America/Los_Angeles", "notes": "Closed major US holidays.", "capabilities": ["sales", "service", "parts", "financing", "trade_in", "delivery"] }, { "name": "Demo Toyota Oakland", "legal_name": "Demo Toyota of Oakland, LLC", "website": "https://oak.demo-toyota.example.com", "geo": { "latitude": 37.8044, "longitude": -122.2712 }, "emails": [ { "name": "Sales", "value": "sales@oak.demo-toyota.example.com" } ], "phones": [ { "name": "Sales", "value": "+15105550100" } ], "address": { "country": "US", "state": "CA", "city": "Oakland", "address_line_1": "200 Broadway", "zip": "94607" }, "schedules": [ { "name": "sales", "value": { "monday": { "open": "09:00", "close": "19:00" }, "tuesday": { "open": "09:00", "close": "19:00" }, "wednesday": { "open": "09:00", "close": "19:00" }, "thursday": { "open": "09:00", "close": "19:00" }, "friday": { "open": "09:00", "close": "19:00" }, "saturday": { "open": "10:00", "close": "17:00" }, "sunday": null } } ], "timezone": "America/Los_Angeles", "capabilities": ["sales", "trade_in"] } ] }, "message": "Welcome — happy to help by phone or video call." } ``` ## When to use it - The buyer agent needs a rooftop's name, address, or hours to surface to the user. - The buyer agent needs to confirm a rooftop's `capabilities` (e.g. `service` or `trade_in`) before routing the user to that location. - The buyer agent needs the sales phone or email to display alongside a confirmed lead response. `dealer.information` is anonymous and consent-free; LLM-driven buyer agents are encouraged to call it eagerly and cache the result. # `inventory.facets` **Info: A2A invocation** This skill is invoked through A2A's `SendMessage` operation — the single A2A operation AAP v1.3.0 uses — not a dedicated REST URL. It travels as the `SendMessage` JSON-RPC method on AAP's sole transport, the JSON-RPC binding. (The HTTP+JSON binding was removed in v1.1.0.) AAP only defines what goes inside `Message.parts[].data`. The `inventory.facets` skill returns aggregated facet counts and ranges over a dealer's inventory. A buyer agent uses it to discover what a dealer actually carries before composing a search — for example, to learn the set of available makes and models, the year range, or the price ceiling. | Property | Value | |---|---| | Skill id | `inventory.facets` | | Request type | `inventory.facets.request` | | Response type | `inventory.facets.response` | | Anonymous allowed | yes | | Consent required | no | | ADF compatible | no | ## Request shape ```json { "type": "inventory.facets.request", "filters": { "...": "(same shape as inventory.search filters; all optional)" } } ``` | Field | Type | Required | Description | |---|---|---|---| | `type` | const | yes | `inventory.facets.request`. | | `filters` | object | no | Optional scoping filters. Same shape as the `inventory.search` filter block; all fields are optional. | When `filters` is absent, facets are aggregated over the dealer's entire inventory. When it is present, facets are aggregated over only the matching subset (e.g. all `condition: ["used"]` listings). ## Response shape ```json { "type": "inventory.facets.response", "data": { "makes": [{ "value": "string", "count": 0 }], "models": [{ "value": "string", "count": 0 }], "trims": [{ "value": "string", "count": 0 }], "years": [{ "value": 0, "count": 0 }], "conditions": [{ "value": "string", "count": 0 }], "transmissions": [{ "value": "string", "count": 0 }], "fuels": [{ "value": "string", "count": 0 }], "dc_fast_charge": [{ "value": "true | false", "count": 0 }], "charge_ports": [{ "value": "string", "count": 0 }], "drivelines": [{ "value": "string", "count": 0 }], "bodies": [{ "value": "string", "count": 0 }], "vehicle_types": [{ "value": "car | motorcycle | trailer | rv | other", "count": 0 }], "exterior_colors": [{ "value": "string", "count": 0 }], "interior_colors": [{ "value": "string", "count": 0 }], "rooftops": [{ "value": "string", "count": 0 }], "statuses": [{ "value": "available | intransit | pending", "count": 0 }], "price_range": { "min": 0, "max": 0 }, "mileage_range": { "min": 0, "max": 0 }, "year_range": { "min": 0, "max": 0 }, "displacement_cc_range": { "min": 0, "max": 0 }, "electric_range_mi_range":{ "min": 0, "max": 0 } } } ``` Each facet array entry is `{ value, count }` where `value` is the facet term (string or integer) and `count` is the number of matching listings. The `statuses` facet's `value` is drawn from the controlled vehicle status enum — `available`, `intransit`, or `pending` — since those are the only statuses that appear in inventory feeds. The `*_range` fields are `{ min, max }` numeric ranges (`price_range` in whole US dollars, `displacement_cc_range` in cc, `electric_range_mi_range` in miles). Powersports dealers additionally return `vehicle_types` (`car` | `motorcycle` | `trailer` | `rv` | `other`), `bodies` (which for motorcycles carry segments like `cruiser`/`touring`), and `displacement_cc_range`, matching the filters on `inventory.search`. Electric inventory adds `electric_range_mi_range`, `dc_fast_charge`, and `charge_ports`. A dealer omits any facet key for which it has no inventory. `price_range` aggregates available authoritative `price` values, which include mandatory dealer charges and exclude government charges (see Pricing and fee disclosure). Vehicles without `price` do not contribute; omit `price_range` when no matching vehicle has `price`. ## Scoped facets example Used-only facets: ### Request ```json { "type": "inventory.facets.request", "filters": { "condition": ["used"] } } ``` ### Response ```json { "type": "inventory.facets.response", "data": { "makes": [ { "value": "Honda", "count": 12 }, { "value": "Toyota", "count": 27 } ], "models": [ { "value": "Civic", "count": 5 }, { "value": "Accord", "count": 4 }, { "value": "Camry", "count": 9 }, { "value": "Corolla", "count": 8 } ], "conditions": [ { "value": "used", "count": 39 } ], "statuses": [ { "value": "available", "count": 34 }, { "value": "intransit", "count": 3 }, { "value": "pending", "count": 2 } ], "year_range": { "min": 2015, "max": 2024 }, "price_range": { "min": 9990, "max": 38990 }, "mileage_range": { "min": 8400, "max": 142000 } } } ``` ## When to use it - The buyer agent wants to enumerate the dealer's makes/models before constructing a search. - The buyer agent needs to surface a price slider or year filter to the user. - The buyer agent wants a count of the dealer's used inventory before recommending a deeper conversation. `inventory.facets` is anonymous and consent-free. The dealer agent SHOULD return the same set of facet keys regardless of filter, omitting only those for which it has no inventory. # `inventory.search` **Info: A2A invocation** This skill is invoked through A2A's `SendMessage` operation — the single A2A operation AAP v1.3.0 uses — not a dedicated REST URL. It travels as the `SendMessage` JSON-RPC method on AAP's sole transport, the JSON-RPC binding. (The HTTP+JSON binding was removed in v1.1.0.) AAP only defines what goes inside `Message.parts[].data`. The `inventory.search` skill is the primary inventory discovery surface. A buyer agent submits a flat filter block, optional pagination, optional sort, and optional privacy hints; the dealer agent returns matching `Vehicle` listings together with a total count and OPTIONAL aggregated facets. | Property | Value | |---|---| | Skill id | `inventory.search` | | Request type | `inventory.search.request` | | Response type | `inventory.search.response` | | Anonymous allowed | yes | | Consent required | no | | ADF compatible | no | ## Filter design AAP keeps filters flat: there is no nested `make → model → trim` tree. Multi-value filters are arrays. Range filters use `*_min` / `*_max` pairs. All fields are optional; absence means "no constraint." | Filter | Type | Multi-value? | Range? | Description | |---|---|---|---|---| | `make` | string[] | yes | — | Vehicle makes (e.g. `["Honda", "BMW"]`). | | `model` | string[] | yes | — | Vehicle models. | | `trim` | string[] | yes | — | Trim levels. | | `condition` | enum[] | yes | — | Subset of `["new", "used", "cpo"]`. | | `vehicle_type` | enum[] | yes | — | Subset of `["car", "motorcycle", "trailer", "rv", "other"]`. Omit to search all types; a listing without `vehicle_type` is treated as `car`. | | `transmission` | string[] | yes | — | Free-text transmission types. | | `fuel` | string[] | yes | — | Free-text fuel types (e.g. `gas`, `hybrid`, `bev`). | | `electric_range_mi_min` / `electric_range_mi_max` | integer | — | yes | Inclusive electric-range range in miles, applied against `electric_range_mi`. Generic electric filter (EV cars and electric motorcycles). | | `dc_fast_charge` | boolean | — | — | When true, include only units supporting DC fast charging. Generic electric filter. | | `charge_port` | string[] | yes | — | EV charge/plug connectors to include (e.g. `['nacs','ccs']`), matching `Vehicle.charge_port`. Generic electric filter. | | `driveline` | string[] | yes | — | Drivetrain layouts (car context). | | `body` | string[] | yes | — | Body styles / segments, applicable to any `vehicle_type` (e.g. `sedan`, `suv` for cars; `cruiser`, `touring` for motorcycles). | | `displacement_cc_min` / `displacement_cc_max` | integer | — | yes | Inclusive engine-displacement range in cc, applied against `displacement_cc`. | | `exterior_color` | string[] | yes | — | Free-text colors. | | `interior_color` | string[] | yes | — | Free-text colors (car context). | | `rooftops` | string[] | yes | — | Rooftop names to include, matching `Vehicle.rooftop` and the rooftop `name` from `dealer.information`. For multi-rooftop dealerships; omit to search across all rooftops. | | `year_min` / `year_max` | integer | — | yes | Inclusive year range. | | `price_min` / `price_max` | integer | — | yes | Inclusive range applied to the authoritative advertised `price`, including mandatory dealer charges and excluding government charges. Vehicles without `price` do not match. See Pricing and fee disclosure. | | `mileage_max` | integer | — | — | Maximum odometer reading. | | `vin` | string | — | — | Exact VIN match (17 chars, ISO 3779). | | `stock` | string | — | — | Exact dealer stock number. | | `query` | string | — | — | Optional free-text query (max 200 chars). | `additionalProperties: false` on `filters`. A request with an unknown filter key is invalid. ## Pagination, sort, privacy ```json { "pagination": { "skip": 0, "limit": 20 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } } ``` - `pagination.skip` and `pagination.limit` are integers. AAP recommends defaults of `skip=0`, `limit=20`, and a hard cap of `100`. - `sort.field` accepts: `price`, `list_price`, `msrp`, `mileage`, `year`, `make`, `model`, `stock`, `updated_at`. `sort.order` is `asc` or `desc`. Sorting by `price` uses the authoritative advertised price; vehicles without it sort after vehicles with a price. - `privacy.anonymous: true` indicates the buyer agent is not attaching user identity to this search. AAP RECOMMENDS anonymous searches by default; user identity is attached only when a lead is submitted. ## Request shape ```json { "type": "inventory.search.request", "filters": { "...": "see filter table" }, "pagination": { "skip": 0, "limit": 20 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } } ``` | Field | Type | Required | |---|---|---| | `type` | const | yes | | `filters` | object | no | | `pagination` | object | no | | `sort` | object | no | | `privacy` | object | no | ## Response shape ```json { "type": "inventory.search.response", "data": { "total": 0, "skip": 0, "limit": 0, "vehicles": [{ "...Vehicle..." }], "facets": { "...Facets... (optional)" } }, "message": "Optional contextual note." } ``` | Field | Type | Required | Notes | |---|---|---|---| | `data.total` | integer | yes | Total number of matching vehicles across all pages. | | `data.skip` | integer | no | Echo of request `pagination.skip`. | | `data.limit` | integer | no | Echo of effective `pagination.limit`. | | `data.vehicles[]` | `Vehicle[]` | yes | Listings in the requested order. | | `data.facets` | `Facets` | no | OPTIONAL embedded aggregation over the matching set. | Each `Vehicle` MAY include `dealer_id`, `vehicle_id`, `vin`, `stock`, `year`, `make`, `model`, `trim`, `condition` (`new` | `used` | `cpo` for inventory contexts), `vehicle_type` (`car` | `motorcycle` | `trailer` | `rv` | `other`, defaulting to `car` when absent), `rooftop`, `body`, `transmission`, `mileage`, `msrp`, `list_price`, `price`, `fees`, and `status`. `price` is the authoritative all-in advertised amount and may stand alone. When `fees` is present with `price`, it is the complete effective fee itemization already included in that amount; buyer agents never add it again or join fees from the rooftop. `list_price` and `fees` without `price` are informational components and MUST NOT be treated as a computed offer. The unified Vehicle schema declares the remaining fields as optional and `additionalProperties: true`, so inventory responses MAY also include rich fields like `photos`, `vdp_url`, `driveline`, `engine`, `fuel`, `city_mpg`, `highway_mpg`, `electric_range_mi`, `exterior_color`, `interior_color`, `features`, `description`, `notes`, `inventory_date`, and `updated_at`. Motorcycle listings (`vehicle_type: "motorcycle"`) carry the class-agnostic `body`/segment and `displacement_cc`, with niche specs (`final_drive`, `engine_stroke`, `wheel_count`, `abs`) in the free-form `other_attributes` map, in place of the car-oriented `driveline`, `interior_color`, and MPG fields. `updated_at` MUST be present whenever the dealer is making availability claims — see Behavior rules. Vehicle `status` is **REQUIRED** on inventory listings and is a controlled enum: exactly `available` | `intransit` | `pending`. Only these three statuses appear in inventory feeds; a vehicle in any other state is OUT OF STOCK and MUST be omitted by the dealer (and ignored by the buyer if encountered). ## Full example A buyer agent searches for certified or used Hondas from 2020 onward, under $30,000 final price, sorted ascending by price. ### Request ```json { "type": "inventory.search.request", "filters": { "make": ["Honda"], "condition": ["used", "cpo"], "year_min": 2020, "price_max": 30000 }, "pagination": { "skip": 0, "limit": 20 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } } ``` ### Response ```json { "type": "inventory.search.response", "data": { "total": 2, "skip": 0, "limit": 20, "vehicles": [ { "dealer_id": "dealer_demo_toyota", "vin": "1HGCV1F30KA000001", "stock": "T12345", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "condition": "cpo", "rooftop": "Demo Toyota San Francisco", "transmission": "automatic", "fuel": "gas", "driveline": "fwd", "body": "sedan", "exterior_color": "Crystal Black Pearl", "mileage": 22150, "list_price": 24990, "price": 26780, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1290 } ], "photos": [ "https://demo-toyota.example.com/photos/T12345-1.jpg" ], "vdp_url": "https://demo-toyota.example.com/inventory/T12345", "status": "available", "inventory_date": "2026-04-12", "updated_at": "2026-04-30T10:15:00Z" }, { "dealer_id": "dealer_demo_toyota", "stock": "T12399", "vehicle_id": "veh_inbound_civic_2024_001", "year": 2024, "make": "Honda", "model": "Civic", "trim": "Touring", "condition": "new", "rooftop": "Demo Toyota San Francisco", "transmission": "automatic", "fuel": "hybrid", "driveline": "fwd", "body": "sedan", "list_price": 27990, "price": 29990, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Dealer preparation fee", "amount": 1500 } ], "status": "intransit", "inventory_date": "2026-04-28", "updated_at": "2026-04-30T08:00:00Z" } ] } } ``` Note that vehicle 2 has only `stock` and `vehicle_id` (no VIN yet, since it is in transit), and its `status` is the enum value `intransit`. Both listings include `updated_at`. ## Motorcycle search Searching a powersports dealer uses the same skill with `vehicle_type: ["motorcycle"]` plus the shared filters (`body` for the motorcycle segment, `displacement_cc_min` / `displacement_cc_max`). ```json { "type": "inventory.search.request", "filters": { "vehicle_type": ["motorcycle"], "make": ["Acme Moto"], "condition": ["new"], "body": ["cruiser", "touring"], "displacement_cc_min": 1200, "price_max": 30000 }, "pagination": { "skip": 0, "limit": 25 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } } ``` Each returned `Vehicle` carries `vehicle_type: "motorcycle"` and the class-agnostic `body`/segment and `displacement_cc` (with niche specs such as `final_drive`, `wheel_count`, or `abs` in `other_attributes`) in place of the car-oriented `driveline`, `interior_color`, and MPG fields. ### Electric motorcycles Electric motorcycles have no displacement, so shoppers compare them on range, battery, and charging instead. Combine `vehicle_type` and `fuel` with the generic electric filters `electric_range_mi_min` / `electric_range_mi_max`, `dc_fast_charge`, and `charge_port`: ```json { "type": "inventory.search.request", "filters": { "vehicle_type": ["motorcycle"], "make": ["Acme Moto"], "fuel": ["bev"], "electric_range_mi_min": 100, "charge_port": ["nacs"], "price_max": 15000 }, "pagination": { "skip": 0, "limit": 25 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } } ``` Returned electric units carry the generic electric-powertrain group (`electric_range_mi`, `battery_kwh`, `motor_power_hp`, `dc_fast_charge`, `charge_port`) and omit combustion fields like `displacement_cc`. These same electric fields apply to electric cars. ## Sort considerations - Sorting by `price` (default for price-comparison flows) sorts on the authoritative advertised vehicle price, including mandatory dealer charges and excluding government charges. This is what buyer agents SHOULD use to compare offers. - Sorting by `list_price` or `msrp` is allowed for users who want a different perspective; neither field should be presented as the payable advertised price. - Sorting by `updated_at desc` is the recommended freshness ordering when buyers care about which listings the dealer has most recently re-confirmed. ## Anonymous search by default AAP RECOMMENDS that buyer agents send `privacy.anonymous: true` on every `inventory.search` call. User identity is reserved for the moment a lead is actually submitted (see `lead.submit`). Dealers MUST support anonymous `inventory.search` unless their agent card explicitly states otherwise. # `inventory.vehicle` **Info: A2A invocation** This skill is invoked through A2A's `SendMessage` operation — the single A2A operation AAP v1.3.0 uses — not a dedicated REST URL. It travels as the `SendMessage` JSON-RPC method on AAP's sole transport, the JSON-RPC binding. (The HTTP+JSON binding was removed in v1.1.0.) AAP only defines what goes inside `Message.parts[].data`. The `inventory.vehicle` skill returns the full detail of a single vehicle. The buyer agent identifies the vehicle by VIN, stock number, or dealer-internal `vehicle_id`. | Property | Value | |---|---| | Skill id | `inventory.vehicle` | | Request type | `inventory.vehicle.request` | | Response type | `inventory.vehicle.response` | | Anonymous allowed | yes | | Consent required | no | | ADF compatible | no | ## Request shape ```json { "type": "inventory.vehicle.request", "vin": "string (17 chars, ISO 3779)", "stock": "string", "vehicle_id": "string" } ``` | Field | Type | Required | Description | |---|---|---|---| | `type` | const | yes | `inventory.vehicle.request`. | | `vin` | string | conditional | 17-char VIN. Preferred when known. | | `stock` | string | conditional | Dealer's stock number. Used when VIN is not yet assigned. | | `vehicle_id` | string | conditional | Dealer-internal identifier (e.g. for in-transit units). | The request MUST include **at least one** of `vin`, `stock`, or `vehicle_id` (`anyOf`). Sending more than one is allowed; the dealer agent uses the most specific match. ## Response shape The response wraps a `Vehicle` object — a `Vehicle` plus arbitrary additional dealer-specific properties (`additionalProperties: true`). ```json { "type": "inventory.vehicle.response", "data": { "...Vehicle (all fields)": "...", "...optional extra dealer-specific fields": "e.g. carfax_url, warranty, title_status" }, "message": "Optional contextual note." } ``` `data` SHOULD include `vin` or `stock` (recommended for any availability claim) and the identification fields `year`, `make`, `model` to be useful. `condition` (when present) MUST be one of `new` | `used` | `cpo`. `data` MUST include `updated_at` whenever the agent is making availability claims about this listing — see Behavior rules. The optional `vehicle_type` field (`car` | `motorcycle` | `trailer` | `rv` | `other`) scopes the type-specific detail fields; a listing without `vehicle_type` is treated as `car`. Motorcycle detail responses (`vehicle_type: "motorcycle"`) carry the class-agnostic `body`/segment and `displacement_cc`, with niche specs (`final_drive`, `engine_stroke`, `wheel_count`, `abs`) in `other_attributes`, instead of the car-oriented `driveline`, `interior_color`, `city_mpg`, and `highway_mpg`. Electric units (BEV/PHEV, any vehicle type) carry a generic electric-powertrain group: `electric_range_mi`, `battery_kwh`, `motor_power_hp`, `dc_fast_charge`, and `charge_port`. These are the same fields whether the unit is an electric car or an electric motorcycle, so combustion-only fields like `displacement_cc` are simply omitted. Pricing fields: | Field | Always present? | Notes | |---|---|---| | `msrp` | optional | Sticker price set by the OEM. | | `list_price` | optional | Base advertised price before incentives and fees. | | `price` | RECOMMENDED when complete | Authoritative advertised vehicle price including mandatory dealer charges and excluding government charges. | | `fees` | optional | Complete effective itemization. With `price`, every amount is already included; without `price`, the itemization is informational and must not be used to derive a payable price. `[]` affirmatively means no mandatory dealer charges. See Pricing and fee disclosure. | ## Full example ### Request ```json { "type": "inventory.vehicle.request", "vin": "1HGCV1F30KA000001" } ``` ### Response ```json { "type": "inventory.vehicle.response", "data": { "dealer_id": "dealer_demo_toyota", "vin": "1HGCV1F30KA000001", "stock": "T12345", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "transmission": "automatic", "exterior_color": "Crystal Black Pearl", "interior_color": "Black", "condition": "cpo", "description": "One-owner CPO Civic EX with Honda Sensing.", "driveline": "fwd", "engine": "2.0L I4", "fuel": "gas", "city_mpg": 32, "highway_mpg": 42, "msrp": 26500, "list_price": 24990, "price": 26780, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1290 } ], "mileage": 22150, "rooftop": "Demo Toyota San Francisco", "photos": [ "https://demo-toyota.example.com/photos/T12345-1.jpg", "https://demo-toyota.example.com/photos/T12345-2.jpg" ], "vdp_url": "https://demo-toyota.example.com/inventory/T12345", "status": "available", "notes": "Honda CPO eligible.", "inventory_date": "2026-04-12", "updated_at": "2026-04-30T10:15:00Z", "features": [ "Adaptive Cruise Control", "Lane Keeping Assist", "Apple CarPlay", "Heated Front Seats" ], "carfax_url": "https://demo-toyota.example.com/carfax/T12345", "warranty": "Honda True Certified+, 60 months remaining" } } ``` `features` is a declared field on the unified `Vehicle`. The response above also includes extra dealer-specific properties (`carfax_url`, `warranty`) that are not part of the base Vehicle schema; AAP allows them via the Vehicle's `additionalProperties: true` (other common extras include `title_status`). ## Errors - `VEHICLE_NOT_FOUND` — none of the supplied identifiers match a listing. - `VEHICLE_UNAVAILABLE` — the listing exists but is no longer available (e.g. its status is no longer one of `available` | `intransit` | `pending`). See Errors for full semantics. # `lead.submit` **Info: A2A invocation** This skill is invoked through A2A's `SendMessage` operation — the only A2A operation AAP uses — not a dedicated REST URL. Every AAP agent card MUST expose a JSON-RPC interface (`SendMessage` method); JSON-RPC 2.0 is AAP's sole binding — see JSON-RPC binding. AAP only defines what goes inside `Message.parts[].data`, carried as a typed JSON `DataPart`. The `lead.submit` skill is the **single, unified** lead-capture entry point in AAP v1.3.0. A buyer agent submits one request containing the consented `customer` plus any combination of `vehicle_of_interest`, `trade_in`, and `appointment`. This matches how dealerships actually take leads: a shopper test-driving a new car often wants their old car appraised in the same visit. `lead.submit` replaced the early-draft trio of `lead.general`, `lead.vehicle`, and `lead.appointment` with a single contract, which AAP v1.3.0 carries forward unchanged. | Property | Value | |---|---| | Skill id | `lead.submit` | | Request type | `lead.submit.request` | | Response type | `lead.submit.response` | | Anonymous allowed | no | | Consent required | yes | | ADF compatible | **yes** | For the field-by-field ADF/XML mapping, see ADF mapping. ## Request shape ```json { "type": "lead.submit.request", "customer": { "...Customer..." }, "consent": { "...ConsentGrant... (scope MUST be ['lead_submission'])" }, "vehicle_of_interest": { "...Vehicle (condition: new|used|cpo)..." }, "trade_in": { "...Vehicle (condition: excellent|good|fair|poor)..." }, "appointment": { "...Appointment..." }, "message": "Free-text message from the user (max 4000 chars).", "source_agent": { "name": "chatgpt-shopping", "url": "https://chatgpt.com", "agent_card_url": "https://chatgpt.com/.well-known/agent-card.json" }, "submitted_at": "ISO-8601" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `type` | const | yes | `lead.submit.request`. | | `customer` | `Customer` | **yes** | Buyer contact info. Always required. | | `consent` | `ConsentGrant` | **yes** | Always required. `scope` MUST be `["lead_submission"]`. As of v1.1.0, `consent` no longer carries `source_agent`; the buyer agent is identified once by the top-level `source_agent` object below. | | `vehicle_of_interest` | `Vehicle` | no | Optional. When present, `condition` (if set) MUST be `new` \| `used` \| `cpo`; vehicle MUST be identifiable via `vin`, `stock`, or `year`+`make`+`model`. | | `trade_in` | `Vehicle` | no | Optional. When present, `condition` (if set) MUST be `excellent` \| `good` \| `fair` \| `poor`; MUST carry at least `year`+`make`+`model`. `mileage` is strongly recommended. | | `appointment` | `Appointment` | no | Optional. `appointment_type` is one of `sales` \| `service` \| `test_drive` \| `trade_in`; `appointment_at` is the requested start time (ISO 8601). The vehicle is implicit: `vehicle_of_interest` for a test drive, `trade_in` for a trade-in appraisal. | | `message` | string | no | Free-text user note (max 4000 chars). | | `source_agent` | object | yes | The single buyer-agent identity for this lead. `name` is REQUIRED (e.g. `chatgpt-shopping`, `gemini-assistant`); `url` and `agent_card_url` are OPTIONAL. This is the one and only `source_agent` in v1.1.0 — it was removed from `consent`. | | `source_agent.name` | string | **yes** | Buyer agent identifier. | | `source_agent.url` | string (uri) | no | Buyer agent's site or product URL. | | `source_agent.agent_card_url` | string (uri) | no | URL of the buyer agent's A2A agent card. | | `submitted_at` | date-time | no | Buyer-agent timestamp at submission. | The unified `Vehicle` interface is the same shape used by `inventory.search` results — see the [Vehicle schema source](https://autoagentprotocol.org/v1.3/schemas/vehicle.schema.json). Both `vehicle_of_interest` and `trade_in` use this shape; only the valid `condition` enum subset differs. Either may be a motorcycle: set `vehicle_type: "motorcycle"` and include the powersports fields (`body`/segment, `displacement_cc`, plus niche specs such as `final_drive` or `wheel_count` in `other_attributes`). For a motorcycle, an `appointment_type` of `test_drive` denotes a demo ride. **Note: Self-discovery** A buyer agent does not have to hard-code or guess this shape. The dealer's agent card publishes each skill's request/response JSON Schema URLs in the **AAP extension params** — `capabilities.extensions[].params.skills["lead.submit"].request_schema` points at the canonical schema (e.g. `https://autoagentprotocol.org/v1.3/schemas/lead-submit-request.schema.json`). The URLs live in the extension params, not as fields on the A2A `skill` object, so strict A2A AgentCard parsers still accept the card. Buyer agents SHOULD fetch that schema and validate against it, rather than relying on the prose here. The top-level `source_agent` object — and the removal of `consent.source_agent` in v1.1.0 — are both reflected in that published schema. ## Response shape ```json { "type": "lead.submit.response", "data": { "lead_id": "string", "status": "received | duplicate | rejected", "appointment": { "appointment_id": "string", "status": "requested | proposed | confirmed | rejected", "confirmed_at": "ISO-8601", "proposed_times": ["ISO-8601"] }, "dealer": { "name": "string", "phone": "+1XXXXXXXXXX" } }, "message": "Optional contextual note." } ``` | Field | Type | Required | Notes | |---|---|---|---| | `data.lead_id` | string | yes | Dealer-assigned lead identifier. | | `data.status` | enum | yes | `received`, `duplicate`, or `rejected`. `duplicate` indicates the dealer recognized the same buyer/vehicle from a recent submission. | | `data.appointment` | object | conditional | Present iff request had `appointment` AND the dealer is acknowledging it. | | `data.appointment.status` | enum | (when present) | `requested` (manual review), `proposed` (alternatives in `proposed_times`), `confirmed` (booked at `confirmed_at`), or `rejected`. | | `data.dealer` | object | no | Convenience contact summary the buyer agent can show the user. | | `message` | string | no | Optional dealer note. | ## Full example: vehicle + trade-in + test-drive in one lead A user wants to test-drive a 2024 Honda CR-V, trade in their 2020 Passat, and book Sunday afternoon. One request. ### Request ```json { "type": "lead.submit.request", "customer": { "first_name": "Piotr", "last_name": "Nowak", "email": "piotr.nowak@example.com", "phone": "+14155555678", "preferred_contact": "phone", "address": { "address_line_1": "320 Brannan Street", "address_line_2": "Apt 412", "city": "San Francisco", "state": "CA", "zip": "94107" } }, "consent": { "granted_at": "2026-04-30T11:05:00Z", "allowed_channels": ["email", "phone"], "consent_text": "I authorize Demo Toyota to contact me by phone or email about VIN 1HGCY2F57RA000001, my requested test drive on May 3, and a possible trade-in of my 2020 Volkswagen Passat. I understand I can withdraw consent at any time.", "scope": ["lead_submission"] }, "vehicle_of_interest": { "vin": "1HGCY2F57RA000001", "year": 2024, "make": "Honda", "model": "CR-V", "trim": "EX-L", "condition": "used", "stock": "DT-2611", "body": "suv", "transmission": "automatic", "mileage": 14820, "price": 32995, "fees": [ { "name": "Documentation fee", "amount": 500 }, { "name": "Pre-installed theft protection", "amount": 1000 } ] }, "trade_in": { "year": 2020, "make": "Volkswagen", "model": "Passat", "trim": "SE", "condition": "good", "mileage": 62000, "body": "sedan", "transmission": "automatic" }, "appointment": { "appointment_type": "test_drive", "appointment_at": "2026-05-03T18:00:00Z", "duration_minutes": 60, "notes": "I'd like to test the CR-V and have my Passat appraised in the same visit." }, "message": "I'm interested in the 2024 Honda CR-V EX-L (VIN 1HGCY2F57RA000001). I plan to pay cash and would like to trade in my 2020 Volkswagen Passat with 62,000 miles.", "source_agent": { "name": "gemini-assistant", "url": "https://gemini.google.com", "agent_card_url": "https://gemini.google.com/.well-known/agent-card.json" }, "submitted_at": "2026-04-30T11:05:08Z" } ``` ### Response (lead received, appointment confirmed) ```json { "type": "lead.submit.response", "data": { "lead_id": "lead_2026_04_30_00842", "status": "received", "appointment": { "appointment_id": "appt_2026_04_30_00128", "status": "confirmed", "confirmed_at": "2026-05-03T18:00:00Z" }, "dealer": { "name": "Demo Toyota", "phone": "+14155550100" } }, "message": "Thanks, Piotr. Test drive confirmed for Sunday May 3 at 11:00 AM Pacific. We've queued your 2020 Passat for an in-person appraisal at the same visit." } ``` ## Variant: customer-only general inquiry (no vehicle, no appointment) ```json { "type": "lead.submit.request", "customer": { "first_name": "Anna", "last_name": "Kowalska", "email": "anna.kowalska@example.com", "phone": "+14155551234", "preferred_contact": "email" }, "consent": { "granted_at": "2026-04-30T10:42:00Z", "allowed_channels": ["email"], "consent_text": "I agree to share my name, email, and phone number with Demo Toyota so a sales representative can answer my financing question by email.", "scope": ["lead_submission"] }, "message": "What APR is Demo Toyota offering this month for buyers with 740+ credit?", "source_agent": { "name": "chatgpt-shopping-agent", "url": "https://chatgpt.com", "agent_card_url": "https://chatgpt.com/.well-known/agent-card.json" }, "submitted_at": "2026-04-30T10:42:05Z" } ``` ## Variant: appointment with proposed alternatives When the dealer cannot honor the requested time: ```json { "type": "lead.submit.response", "data": { "lead_id": "lead_2026_04_30_00843", "status": "received", "appointment": { "appointment_id": "appt_2026_04_30_00129", "status": "proposed", "proposed_times": [ "2026-05-03T20:00:00Z", "2026-05-04T16:00:00Z" ] }, "dealer": { "name": "Demo Toyota", "phone": "+14155550100" } }, "message": "We're booked solid Sunday afternoon. The times above are open Sunday evening and Monday afternoon (Pacific). Reply with the time you prefer or call to confirm." } ``` The buyer agent SHOULD present the alternatives to the user and re-submit a fresh `lead.submit.request` with one of the proposed times in `appointment.appointment_at`. ## Why one skill instead of three? Real shopping flows naturally bundle the inquiry, the trade-in, and the appointment. Forcing buyer agents to make 3 separate calls (with 3 separate consent records) creates: - **Brittle correlation** — dealer CRMs have to re-stitch what was always one customer intent. - **Consent friction** — users sign off 3 disclosures for one decision. - **Race conditions** — the appointment may be booked before the lead arrives, or vice-versa. A single `lead.submit` lets the dealer transactionally accept the lead, queue the trade-in for appraisal, and confirm or propose the appointment in one round trip. v1.3.0 keeps the contract tight by NOT supporting multi-vehicle leads (one `vehicle_of_interest` per submission); send N requests for N vehicles. ## Consent and channel rules - `consent.allowed_channels[]` lists the channels (`email`, `phone`, `sms`) the user authorized for THIS submission. - `consent.scope[]` MUST be `["lead_submission"]`. - The dealer MUST reject the lead with `CONTACT_CONSENT_REQUIRED` if it intends to follow up via a channel not in `allowed_channels`. - The buyer agent MUST NOT include phone or email without explicit user authorization. See Behavior rules. ## Errors - `CONTACT_CONSENT_REQUIRED` — `consent` missing, `scope` not `['lead_submission']`, or follow-up channel not in `allowed_channels`. - `VEHICLE_NOT_FOUND` / `VEHICLE_UNAVAILABLE` — `vehicle_of_interest` reference cannot be located, or no longer available, when a `test_drive` appointment is requested. - `APPOINTMENT_TIME_UNAVAILABLE` — the requested `appointment_at` cannot be honored AND the dealer has no proposals to make. The lead may still be `received` even when the appointment portion fails. - `INVALID_CONDITION` — `vehicle_of_interest.condition` is in the trade-in vocabulary, or `trade_in.condition` is in the sale-condition vocabulary. See Errors for the full vocabulary. ## What `lead.submit` does NOT guarantee A successful `lead.submit.response` does not guarantee booking unless `data.appointment.status` is `confirmed`. `requested` and `proposed` mean the customer has expressed interest but has not been booked. Buyer agents MUST communicate this to the user clearly. See Behavior rules. # ADF/XML mapping The Auto-lead Data Format (ADF/XML) has been the de-facto standard for delivering leads to dealer CRMs for over two decades. AAP's `lead.submit` request is designed to translate losslessly to ADF/XML so a dealer's existing pipeline accepts the lead unchanged. This page documents the field-by-field translation. The dealer agent (or the dealer's CRM adapter) generates the XML; the buyer agent only sends AAP JSON. ## Mapping table | AAP field (`lead.submit.request`) | ADF element / attribute | Notes | |---|---|---| | `customer.first_name` + `customer.last_name` | `{first} {last}` | ADF expects a full-name node; AAP keeps first/last separate. The mapper concatenates with a single space. | | `customer.email` | `...` | RFC 5322 email. | | `customer.phone` | `...` | E.164 phone (e.g. `+14155550123`). ADF allows free-form phones; AAP normalizes to E.164. | | `customer.address.address_line_1` / `_2` | `
......
` | Optional. | | `customer.address.city` | `
...
` | Optional. | | `customer.address.state` | `
...
` | Optional. ADF uses ``; AAP uses `state`. | | `customer.address.zip` | `
...
` | Optional. ADF uses ``; AAP uses `zip`. The ADF mapper writes `` from `customer.address.country` when provided, defaulting to `US` otherwise. | | `vehicle_of_interest.year` / `make` / `model` | `.........` | The required ADF trio. Provided either directly on `vehicle_of_interest` or by VIN-decoding the dealer-side listing. | | `vehicle_of_interest.trim` | `...` | Optional. | | `vehicle_of_interest.vin` | `...` | Optional but recommended. | | `vehicle_of_interest.stock` | `...` | Optional. | | `vehicle_of_interest.condition` | `` (one of `new`, `used`) | ADF accepts only `new` or `used`. AAP `cpo` MAPS TO `status="used"` AND a free-text `certified pre-owned` on the vehicle. | | `vehicle_of_interest.body` | `...` | Optional. Applies to any `vehicle_type`: a car body style (e.g. `sedan`, `suv`) or a motorcycle segment (e.g. `cruiser`, `touring`) — both go into ADF's `` since ADF has no motorcycle-specific element. | | `vehicle_of_interest.displacement_cc` | `Displacement: {cc}cc` | Optional. ADF has no displacement element, so the mapper folds it into vehicle ``. Any niche specs carried in `other_attributes` (e.g. `wheel_count`, `final_drive`, `engine_stroke`) are handled the same way — folded into ``. | | `vehicle_of_interest.transmission` | `...` | Optional. ADF expects `A` or `M`; the mapper folds `automatic`/`manual` (and variants like `8-speed automatic`) accordingly. | | `vehicle_of_interest.mileage` | `...` | Optional; typical for used. | | `vehicle_of_interest.price` | `...` | Mapped from the AAP integer dollar amount (whole US dollars). | | `appointment.appointment_type` | Implicit on the parent vehicle's `interest`: `test_drive` → ``; `trade_in` → the `` block; `sales` / `service` → the appointment lives in `` of the customer block. | ADF predates structured appointments. | | `appointment.appointment_at` | `Requested appointment: 2026-05-03 11:00 PT` | Free-text in ``; CRMs route to the appointment desk. | | `submitted_at` | `...` | ISO 8601 (e.g. `2026-04-30T10:15:10Z`). | | `message` | `...` | Free-text user message. | | `source_agent` | `{source_agent}` | Identifies the originating buyer agent (e.g. `chatgpt-shopping`). **Direct integration:** put `source_agent` in ``. **Lead-network delivery:** when an intermediary (e.g. the AAP platform) delivers the ADF, it MAY instead set `` to the network brand, carry `source_agent` in ``, and add `` for the network — `` always stays the selling dealership either way. | | Dealer name (from the `dealer.information` rooftop `name`) | `...` | Dealer agent fills this from its own profile, not from the buyer agent. | | `trade_in.year` / `make` / `model` / `trim` | A second `...` block with ``, ``, ``, `` | Trade-in vehicle goes in its own ADF `` block. | | `trade_in.mileage` | `...` | Strongly recommended; ADF ``. | | `trade_in.condition` | `...` | `excellent`, `good`, `fair`, `poor` map directly to ADF's `` enum. | | `trade_in.vin` | `...` | Optional; recommended for accurate appraisal. | Nothing in `consent` is part of ADF (the format predates structured agent consent). AAP's `ConsentGrant` is preserved alongside the lead in the dealer CRM as an audit record. AAP-specific fields without an ADF equivalent (`customer.preferred_contact`, `vehicle_of_interest.vehicle_id`, `vehicle_of_interest.msrp`/`list_price`) MAY be persisted as CRM extension fields or in ``. > **One `` per ``.** ADF allows only a single `` element inside each `` block. Several rows above independently fold into vehicle `` — the `cpo` certified note, `displacement_cc`, and any `other_attributes` (e.g. `wheel_count`, `final_drive`, `engine_stroke`). The mapper MUST **merge** all of these into ONE `` element per vehicle (e.g. `certified pre-owned; Displacement: 1868cc; final_drive: belt`). A literal row-by-row reading that emits multiple `` per vehicle produces invalid ADF — e.g. for a CPO motorcycle that has both a certified note and displacement/`other_attributes`. ## Concrete worked example Given a `lead.submit` request bundling a vehicle of interest, a trade-in, and a test-drive appointment: ```json { "type": "lead.submit.request", "customer": { "first_name": "Anna", "last_name": "Lee", "email": "anna@example.com", "phone": "+14155550123", "preferred_contact": "email", "address": { "address_line_1": "200 Folsom St", "city": "San Francisco", "state": "CA", "zip": "94105" } }, "consent": { "granted_at": "2026-04-30T10:15:00Z", "allowed_channels": ["email", "phone"], "consent_text": "I agree to share my contact info with Demo Toyota about this 2022 Honda Civic, my Sunday test drive, and my Toyota Corolla trade-in.", "source_agent": "chatgpt-shopping", "scope": ["lead_submission"] }, "vehicle_of_interest": { "vin": "1HGCV1F30KA000001", "year": 2022, "make": "Honda", "model": "Civic", "trim": "EX", "condition": "cpo", "body": "sedan", "transmission": "automatic" }, "trade_in": { "year": 2014, "make": "Toyota", "model": "Corolla", "condition": "good", "mileage": 96000 }, "appointment": { "appointment_type": "test_drive", "appointment_at": "2026-05-03T18:00:00Z" }, "message": "Interested in this Civic; can you confirm availability and best price with my trade?", "source_agent": "chatgpt-shopping", "submitted_at": "2026-04-30T10:15:10Z" } ``` The dealer-side ADF/XML payload is: ```xml 2026-04-30T10:15:10Z 2022 Honda Civic EX 1HGCV1F30KA000001 sedan A certified pre-owned 2014 Toyota Corolla good 96000 Anna Lee anna@example.com +14155550123
200 Folsom St San Francisco CA 94105 US
Interested in this Civic; can you confirm availability and best price with my trade? Requested test-drive: 2026-05-03 11:00 PT.
chatgpt-shopping Demo Toyota
``` Note: - `condition: "cpo"` becomes `` PLUS `certified pre-owned`. Plain ADF has no certified value. - `appointment.appointment_type: "test_drive"` becomes `interest="test-drive"` on the `vehicle_of_interest` block. AAP `trade_in` maps to a second ``. - `appointment.appointment_at` is folded into the customer `` since ADF has no native appointment element. - The dealer name `Demo Toyota` is filled from the dealer agent's own profile, not from the buyer agent's request. - The user `message` and the appointment time land in customer ``. - This example shows **direct** delivery, so `source_agent` (`chatgpt-shopping`) is the ``. Under **lead-network** delivery the network brand is the `` and `chatgpt-shopping` rides in `` (see the `source_agent` row above); `` is the selling dealership in both cases. ## What does NOT map to ADF The ADF format predates structured agent consent. AAP's `consent` (`ConsentGrant`) is preserved alongside the lead in the dealer CRM as an audit record but does not have an ADF equivalent. Dealers typically: - Persist the full `ConsentGrant` JSON in a separate consent table or audit log. - Surface `consent.allowed_channels` to the dealer's BDC or CRM rules engine to decide which channels to actually use for follow-up (and to refuse unauthorized channels). `consent` MUST NOT be silently dropped on the dealer side; it is a regulatory requirement that the buyer agent passes through. ## ADF compatibility flag AAP defines `lead.submit` as ADF-compatible. When the request includes `vehicle_of_interest`, the lead carries enough context to populate ADF's required `` trio cleanly. When the request is customer-only (no `vehicle_of_interest`), it is forwarded into the dealer CRM as an ADF lead with a synthetic placeholder vehicle (e.g. omitted or left as a generic inquiry record), per the dealer's CRM conventions. For implementation guidance on the receiving side, see your CRM vendor's ADF documentation; AAP does not specify wire-level ADF transport (FTP, SMTP, HTTP POST), only the field-level translation. # MCP compatibility [Model Context Protocol](https://modelcontextprotocol.io) (MCP) is a tool layer between an LLM client and a host application. AAP exposes its five skills as MCP tools so any MCP-compatible LLM client (Claude Desktop, an MCP-aware IDE, or a custom orchestrator) can call a dealer agent without learning the A2A wire format directly. The MCP server acts as a thin adapter: it accepts an MCP `tools/call` request whose `arguments` is exactly an AAP request payload, wraps it as a typed `DataPart` inside an A2A `Message`, and forwards it to the dealer agent's A2A endpoint with a single `SendMessage` call — the only A2A operation AAP uses (request `Message` in, response `Message` out; streaming, tasks, and push notifications are out of scope for AAP v1.3.0). The MCP tool's `inputSchema` is the AAP request schema by URL — no extra wrapping, no field renaming. ## Tool naming Each AAP skill maps to one MCP tool. The tool name pattern is: ``` aap_ ``` Dots become underscores. The mapping is fixed for v1.3.0: | AAP skill id | MCP tool name | |---|---| | `dealer.information` | `aap_dealer_information` | | `inventory.facets` | `aap_inventory_facets` | | `inventory.search` | `aap_inventory_search` | | `inventory.vehicle` | `aap_inventory_vehicle` | | `lead.submit` | `aap_lead_submit` | ## Tool input is the AAP request payload The MCP tool's `inputSchema` is the AAP request schema (referenced by URL). The MCP server passes `arguments` directly through as the AAP request — no envelope, no extra wrapping. The MCP server is responsible for: 1. Validating `arguments` against the request schema (best practice but optional). 2. Wrapping `arguments` as `Message.parts[].data` (a Part carrying the `data` member). 3. Sending it to the dealer's A2A endpoint as a `SendMessage` call over AAP's single transport, JSON-RPC 2.0, which every AAP agent exposes. (The HTTP+JSON (REST) binding was removed in v1.1.0.) 4. Unwrapping the dealer's A2A `Message` response and returning the AAP `data` payload as the MCP tool result. The MCP tool result is the AAP response payload (the same thing the dealer returned in `parts[0].data`). ## MCP manifest structure A complete MCP server descriptor that exposes all five AAP skills as tools: ```json { "name": "auto-agent-protocol", "version": "1.3.0", "description": "MCP server descriptor that exposes Auto Agent Protocol automotive skills as MCP tools. Each tool's input matches the corresponding AAP request schema; the wrapper invokes the dealer's A2A endpoint with the same payload as a typed DataPart.", "protocolVersion": "2025-06-18", "tools": [ { "name": "aap_dealer_information", "description": "Return the dealership profile — group name, welcome message, and one or more rooftops (locations) with their address, geo, contacts, business hours, timezone, and service capabilities.", "inputSchema": { "$ref": "https://autoagentprotocol.org/v1.3/schemas/dealer-information-request.schema.json" }, "annotations": { "aap_skill_id": "dealer.information", "aap_request_type": "dealer.information.request", "aap_response_type": "dealer.information.response", "aap_response_schema": "https://autoagentprotocol.org/v1.3/schemas/dealer-information-response.schema.json" } }, { "name": "aap_inventory_facets", "description": "Return searchable inventory facets such as makes, models, years, conditions, body styles, price ranges, mileage ranges, drivetrain, fuel type, and statuses.", "inputSchema": { "$ref": "https://autoagentprotocol.org/v1.3/schemas/inventory-facets-request.schema.json" }, "annotations": { "aap_skill_id": "inventory.facets", "aap_request_type": "inventory.facets.request", "aap_response_type": "inventory.facets.response", "aap_response_schema": "https://autoagentprotocol.org/v1.3/schemas/inventory-facets-response.schema.json" } }, { "name": "aap_inventory_search", "description": "Search vehicle inventory by query, make, model, trim, year, condition, price, mileage, body style, VIN, stock, features, and availability.", "inputSchema": { "$ref": "https://autoagentprotocol.org/v1.3/schemas/inventory-search-request.schema.json" }, "annotations": { "aap_skill_id": "inventory.search", "aap_request_type": "inventory.search.request", "aap_response_type": "inventory.search.response", "aap_response_schema": "https://autoagentprotocol.org/v1.3/schemas/inventory-search-response.schema.json" } }, { "name": "aap_inventory_vehicle", "description": "Return details for a specific vehicle by VIN, stock number, or vehicle_id, including status, pricing disclosure, photos, mileage, trim, features, fuel economy, and dealer page URL.", "inputSchema": { "$ref": "https://autoagentprotocol.org/v1.3/schemas/vehicle-detail-request.schema.json" }, "annotations": { "aap_skill_id": "inventory.vehicle", "aap_request_type": "inventory.vehicle.request", "aap_response_type": "inventory.vehicle.response", "aap_response_schema": "https://autoagentprotocol.org/v1.3/schemas/vehicle-detail-response.schema.json" } }, { "name": "aap_lead_submit", "description": "Submit a consented lead carrying customer info plus any combination of vehicle of interest, trade-in, and appointment request — a single unified contract that matches how dealerships actually take leads (e.g. test-drive a new car while getting a trade-in appraised in the same visit).", "inputSchema": { "$ref": "https://autoagentprotocol.org/v1.3/schemas/lead-submit-request.schema.json" }, "annotations": { "aap_skill_id": "lead.submit", "aap_request_type": "lead.submit.request", "aap_response_type": "lead.submit.response", "aap_response_schema": "https://autoagentprotocol.org/v1.3/schemas/lead-submit-response.schema.json" } } ] } ``` During development, a reference manifest is generated from `spec/latest/skills.yaml` into `generated/latest/mcp.json`. A release stores the reviewed manifest under `releases/v{major}.{minor}/artifacts/mcp.json`; production publishes that immutable snapshot rather than regenerating it. ## Calling a tool An MCP client invokes `tools/call` with the AAP request as `arguments`: ```json { "jsonrpc": "2.0", "id": "mcp-1", "method": "tools/call", "params": { "name": "aap_inventory_search", "arguments": { "type": "inventory.search.request", "filters": { "make": ["Honda"], "condition": ["used", "cpo"], "year_min": 2020, "price_max": 30000 }, "pagination": { "skip": 0, "limit": 20 }, "sort": { "field": "price", "order": "asc" }, "privacy": { "anonymous": true } } } } ``` The MCP server forwards `arguments` as the AAP `DataPart.data` to the dealer's A2A endpoint and returns the dealer's AAP response payload (the contents of the response `data` block) as the MCP tool result. ## Why this matters - LLM clients that already speak MCP gain instant access to every AAP-compliant dealer agent through a one-line server registration. - The MCP `inputSchema` `$ref` points at the AAP schema by URL, so an LLM with schema-following tool use can plan calls against the same source of truth as a hand-written A2A client. - The MCP server is stateless adapter glue; all business logic — auth, consent enforcement, inventory accuracy — stays in the dealer agent behind A2A. For more on MCP itself, see the [MCP specification](https://modelcontextprotocol.io). For the single A2A transport the MCP server forwards into, see the JSON-RPC binding (the sole AAP v1.3.0 transport, exposed by every AAP agent). The REST binding was removed in v1.1.0. # Behavior rules This page collects the normative MUST and SHOULD requirements that an AAP-compliant agent must follow. These rules are the bare minimum for interoperability and regulatory compliance; they are referenced from the per-skill pages and applied by the dealer-side test suite. The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, MAY, RECOMMENDED, and OPTIONAL are interpreted as in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119.html). ## Inventory rules ### Inventory MUSTs - **Inventory feeds MUST contain only in-stock statuses.** `Vehicle.status` is a controlled enum: `available` | `intransit` | `pending`. Dealer agents MUST only return vehicles whose `status` is one of these three in `inventory.search` results. A vehicle in any other state is OUT OF STOCK: the dealer MUST omit it, and a buyer agent that somehow observes any other value MUST ignore the vehicle and treat it as unavailable. `status` is REQUIRED on every inventory listing. - **`updated_at` is MANDATORY for availability and pricing claims.** Every `Vehicle` returned by `inventory.search` and `inventory.vehicle` MUST include `updated_at` whenever the agent is making availability or pricing claims about the listing. The field is an RFC 3339 timestamp indicating when the dealer last updated the listing's availability, price, fees, or status. - **`vehicle.vin` or `vehicle.stock` SHOULD be present on detail responses.** `inventory.vehicle` responses SHOULD include `vin` or `stock`. When neither is present (e.g. a deeply pre-allocated unit), the response MUST include `vehicle_id` and SHOULD include free-text `notes` explaining the unit's identification. - **When implemented, `inventory.search` MUST support anonymous calls.** A dealer agent is not required to expose `inventory.search`, but if it does, it MUST accept calls without `customer` info and without `consent`. AAP RECOMMENDS dealer agents publish their search surface anonymously by default when they expose one. ### Inventory SHOULDs - Dealer agents SHOULD update `updated_at` no less frequently than once per business day for each in-stock listing. - Dealer agents SHOULD echo the request's `pagination.skip` and `pagination.limit` in the response `data.skip` and `data.limit` so buyer agents can paginate without ambiguity. - Buyer agents SHOULD attach `privacy.anonymous: true` to every `inventory.search` call by default and only attach customer identity when actually submitting a lead. ## Lead rules ### Lead MUSTs - **`customer` is REQUIRED on every `lead.submit` request.** The unified lead is never anonymous. Every `lead.submit.request` MUST include a `customer` block; the schema enforces this in `required`. - **`consent` is MANDATORY whenever `customer` is present (which is always for `lead.submit`).** Every `lead.submit.request` MUST include a `consent` (`ConsentGrant`) block. A request that violates this MUST be rejected with `CONTACT_CONSENT_REQUIRED`. - **Channel must be permitted.** Dealer agents MUST reject the lead with `CONTACT_CONSENT_REQUIRED` if the requested follow-up channel (or the channel implied by `customer.preferred_contact`) is not in `consent.allowed_channels[]`. The dealer MUST NOT use a channel the user did not authorize. - **Buyer agents MUST NOT include phone or email without explicit user authorization.** A buyer agent MUST capture an explicit consent action from the user — verbatim text shown, channels selected, scope confirmed — before populating `customer.email`, `customer.phone`, or `customer.address`. The verbatim text MUST be reproduced in `consent.consent_text`. - **Scope is fixed.** `consent.scope[]` MUST be `["lead_submission"]`. A `ConsentGrant` whose `scope` is anything else MUST cause the dealer to reject with `INVALID_CONSENT`. - **Condition vocabularies MUST NOT be mixed.** `vehicle_of_interest.condition` (when set) MUST be one of `new` | `used` | `cpo`. `trade_in.condition` (when set) MUST be one of `excellent` | `good` | `fair` | `poor`. Mixing these MUST be rejected with `SCHEMA_VALIDATION_FAILED` (the conditional schema enforces it) or `INVALID_CONDITION`. ### Lead SHOULDs - Dealer agents SHOULD return `status: "duplicate"` (instead of `received`) when the same `customer` submits an equivalent lead within a short window (dealer-defined; commonly 24 hours). - Dealer agents SHOULD include a brief `message` on every successful lead response explaining the next step (e.g. "A sales rep will email Anna within 1 business day."). - Dealer agents SHOULD persist the full `ConsentGrant` JSON as an audit record alongside the lead. ADF mapping does not specify a consent element; the `ConsentGrant` is the AAP-side record. - Buyer agents SHOULD bundle `vehicle_of_interest`, `trade_in`, and `appointment` into a SINGLE `lead.submit.request` when they belong to the same shopping intent, rather than making multiple consecutive submissions. This keeps the dealer CRM transactional and avoids re-stitching one customer intent. ## Appointment rules ### Appointment MUSTs - **Booking is not implied.** A successful `lead.submit` response does NOT guarantee an appointment booking unless the `data.appointment.status` is `confirmed`. `requested` and `proposed` mean the customer has expressed interest but has not been booked. - **Non-confirmable requests SHOULD return `requested` or `proposed`.** When the dealer agent cannot auto-confirm a request (manual review required, the requested time does not fit, etc.), it SHOULD respond with `data.appointment.status: "requested"` or `data.appointment.status: "proposed"` rather than rejecting the appointment. - **`appointment_at` MAY be omitted.** When the buyer does not specify a time, the dealer SHOULD return `requested` and follow up to schedule. When present, `appointment_at` MUST be an ISO 8601 / RFC 3339 timestamp with a timezone offset. - **`vehicle_of_interest` SHOULD be present for `test_drive`.** When `appointment.appointment_type` is `test_drive`, the lead SHOULD also include `vehicle_of_interest` so the dealer knows which unit. A submission without `vehicle_of_interest` MAY be rejected with `MISSING_REQUIRED_FIELD` per dealer policy. ### Appointment SHOULDs - The dealer agent SHOULD include the dealer's primary phone (`data.dealer.phone`, E.164) on every lead-with-appointment response so the buyer agent can surface it to the user. - The dealer agent SHOULD include `data.appointment.confirmed_at` on every `confirmed` appointment status and `data.appointment.proposed_times` on every `proposed` status. - Buyer agents that receive `data.appointment.status: "proposed"` SHOULD present the alternative times to the user and re-submit a fresh `lead.submit` with the chosen time in `appointment.appointment_at`. ## Defaults and locale rules When optional context fields are omitted, AAP defines deterministic fallbacks so buyer agents and dealer agents agree without out-of-band coordination. - **Prices and fee amounts are integers in whole US dollars.** `msrp`, `list_price`, `price`, and `fees[].amount` use plain integers (e.g. `26780` or `500`). - **Address default country.** `Address.country` is optional; when omitted, the assumed country is `US`. - **Idempotency.** Buyer agents that retry `lead.submit` after a network failure SHOULD pass an `idempotency_key` (UUID recommended). Dealer agents SHOULD dedupe on this key for at least 24 hours and return the original `lead_id` and status on retries. - **Consent expiration.** When `ConsentGrant.expires_at` is omitted, the dealer MAY apply its own default expiration window per local regulation; an explicit `expires_at` always wins. The dealer MUST reject the lead with `INVALID_CONSENT` if the grant has already expired. ## Pricing rules ### Pricing MUSTs - **`price` MUST be the authoritative advertised vehicle price.** It includes all mandatory, non-government dealer charges and required dealer add-ons, may use only discounts available to every consumer, and is not reduced by an additional required down payment or conditioned on dealer financing. It excludes government charges such as tax, title, and registration, so it is not an out-the-door quote. - **`fees` is an optional breakdown, not a condition of `price`.** `Vehicle.price` remains authoritative when `Vehicle.fees` is omitted. When `fees` is present, `[]` means no mandatory dealer charges and a non-empty array completely itemizes charges already included in `price`. Buyer agents MUST NOT add those amounts again or join them from `dealer.information`. - **`price_min` / `price_max` and `sort.field: "price"` apply to `price`.** `inventory.search` `filters.price_min`, `filters.price_max`, and `sort.field: "price"` are evaluated against `price`, not `list_price` or `msrp`. Vehicles without `price` do not match price-bound filters. See Pricing and fee disclosure. ### Pricing SHOULDs - Dealer agents SHOULD publish `list_price`, `msrp`, `price`, and `fees` together when all are known. `list_price + sum(fees)` need not equal `price` because `price` may include universally available discounts; buyer agents do not derive a replacement price. - Buyer agents SHOULD compare offers across dealers on `price`, not `list_price`. If only `list_price` and optional `fees` are known, the publisher omits `price`, and consumer agents treat those components as informational rather than payable or computable. ## Authentication AAP v1.3.0 defines **no authentication of its own** — agents are public by default. A dealer that needs to protect its endpoint uses A2A's native `securitySchemes` / `securityRequirements` on its agent card; obtaining and presenting credentials then follows A2A (a transport concern), not AAP. See Discovery. ## Rate limits - Dealer agents SHOULD return `RATE_LIMITED` (HTTP 429 / JSON-RPC code -32002) with `retryable: true` and a hint in `details.retry_after_ms` when the buyer agent exceeds a per-key quota. ## Ordering of rules When two rules appear to conflict, the more restrictive one wins. For example: a vehicle whose `status` is not one of `available` | `intransit` | `pending` is out of stock and MUST NOT be returned as available, even if the dealer's own internal cache says otherwise. The buyer agent's choice of `privacy.anonymous: true` does NOT override the consent rules — those apply only to lead.* skills, where customer info changes the call. # Errors AAP defines a single typed error payload (`aap.error`) that every dealer agent MUST use when a skill cannot be fulfilled. The error rides inside the standard A2A error envelope: `error.data` of the JSON-RPC error response (Section 9.5). AAP v1.3.0 uses a single transport — JSON-RPC 2.0; the HTTP+JSON (REST) binding was removed in v1.1.0. ## Error payload shape ```json { "type": "aap.error", "error_id": "err_01HZ9EXAMPLE", "code": "SCHEMA_VALIDATION_FAILED", "message": "request failed validation with 2 errors", "retryable": false, "details": { "errors": [ { "instanceLocation": "/filters/year_min", "keyword": "type", "error": "must be an integer" }, { "instanceLocation": "/filters/make", "keyword": "additionalProperties", "error": "unknown filter key" } ] }, "created_at": "2026-04-30T10:15:30Z" } ``` | Field | Type | Required | Description | |---|---|---|---| | `type` | const | yes | Always `aap.error`. | | `error_id` | string | yes | Unique identifier for this error instance (UUID recommended), suitable for support correlation. | | `code` | enum | yes | One of the 12 AAP error codes below. | | `message` | string | yes | Human-readable summary. May be surfaced to the end user. | | `retryable` | boolean | yes | Whether the buyer agent SHOULD retry the same request after a backoff. | | `details` | object | no | Code-specific details. For validation errors it carries an **`errors[]`** array listing EVERY problem at once (see below); other codes use it for retry hints, ids, etc. | | `created_at` | date-time | yes | When the error was generated by the dealer agent. | ### `details.errors[]` — all problems at once Validation errors (`SCHEMA_VALIDATION_FAILED`, `MISSING_REQUIRED_FIELD`, `INVALID_CONDITION`) put **every** failing field in a single `details.errors` array so a buyer agent fixes the whole payload in one pass instead of one round-trip per error. Each entry follows the JSON-Schema-2020-12 standard output unit shape: | Field | Type | Description | |---|---|---| | `instanceLocation` | string | JSON Pointer to the failing field (e.g. `/consent/scope/0`). | | `keyword` | string | The violated constraint (`type`, `required`, `additionalProperties`, `enum`, …). | | `error` | string | Human-readable message for that field. | A dealer agent MUST return all currently-detectable validation errors in one response — never just the first. ## Error code reference The 12 codes, their meaning, recommended JSON-RPC code, and `retryable` default. | `code` | Meaning | JSON-RPC | `retryable` default | |---|---|---|---| | `UNSUPPORTED_SKILL` | The agent does not implement this skill. | -32601 (Method not found) | `false` | | `SCHEMA_VALIDATION_FAILED` | Request body fails JSON Schema validation. | -32602 (Invalid params) | `false` | | `MISSING_REQUIRED_FIELD` | A specifically required field is absent. | -32602 (Invalid params) | `false` | | `INVALID_CONDITION` | `vehicle_of_interest.condition` is in the trade-in vocabulary, or `trade_in.condition` is in the sale-condition vocabulary. | -32602 (Invalid params) | `false` | | `VEHICLE_NOT_FOUND` | The supplied `vin` / `stock` / `vehicle_id` does not match any listing. | -32000 (Server error) | `false` | | `VEHICLE_UNAVAILABLE` | The vehicle exists but its `status` is no longer one of `available` \| `intransit` \| `pending`. | -32000 (Server error) | `false` | | `CONTACT_CONSENT_REQUIRED` | `customer` info present without `consent`, or follow-up channel not in `consent.allowed_channels`. | -32000 (Server error) | `false` | | `INVALID_CONSENT` | `consent` is present but malformed, expired, or its scope does not cover the called skill. | -32000 (Server error) | `false` | | `APPOINTMENT_TIME_UNAVAILABLE` | The requested `appointment_at` cannot be honored AND the dealer has no proposed alternatives. | -32000 (Server error) | `false` | | `IDEMPOTENCY_CONFLICT` | An `idempotency_key` was reused with a different request payload. | -32000 (Server error) | `false` | | `RATE_LIMITED` | Client has exceeded the dealer's rate limit. | -32002 (Server error reserved) | **`true`** | | `INTERNAL_ERROR` | Unhandled dealer-side error. | -32603 (Internal error) | `true` | `retryable` is a default, not a hard rule. Dealer agents MAY override it per-instance — for example, a `SCHEMA_VALIDATION_FAILED` is conceptually non-retryable (the request is malformed and a retry will fail identically), but a transient `INTERNAL_ERROR` is conceptually retryable. Buyer agents MUST honor the value the dealer returns rather than the table default. ## Per-code semantics ### `UNSUPPORTED_SKILL` Returned when a buyer agent calls a skill id the dealer agent does not implement. AAP v1.3.0 agents declare the subset of the five skills they implement (at least one) on their agent card, so buyer agents SHOULD check the declared skills before calling. This code also covers forward-compat scenarios where future AAP versions add skills not present in v1.3.0. ### `SCHEMA_VALIDATION_FAILED` Returned when the request body does not satisfy the AAP request schema (missing `type`, wrong field types, unknown filter keys, unknown enum values, etc.). `details.errors[]` MUST list **all** failing fields at once — each with its `instanceLocation`, `keyword`, and `error` — so the buyer agent can correct the entire payload in a single retry. ### `MISSING_REQUIRED_FIELD` Returned when a specifically required field is absent. This overlaps with `SCHEMA_VALIDATION_FAILED`; dealer agents MAY use either, but `MISSING_REQUIRED_FIELD` is preferred when the issue is a single missing required field rather than a structural validation problem (e.g. a `lead.submit` request whose `appointment.appointment_type` is `test_drive` but no `vehicle_of_interest` is provided). ### `INVALID_CONDITION` Returned when the `condition` value is set to an item from the wrong vocabulary for its context: `vehicle_of_interest.condition` MUST be one of `new` | `used` | `cpo`, and `trade_in.condition` MUST be one of `excellent` | `good` | `fair` | `poor`. The base `Vehicle` schema accepts the union of both vocabularies because the same shape is used in inventory results too; `lead.submit` enforces the per-context subset and rejects with `INVALID_CONDITION` when violated. ### `VEHICLE_NOT_FOUND` Returned by `inventory.vehicle` (or by `lead.submit` when `vehicle_of_interest` does not match) when none of the supplied identifiers (`vin`, `stock`, `vehicle_id`, year+make+model) match a listing. ### `VEHICLE_UNAVAILABLE` Returned when the listing existed but is no longer available — e.g. its `status` moved out of the `available` | `intransit` | `pending` set between an `inventory.search` snapshot and a follow-up `inventory.vehicle` call. Buyer agents SHOULD recompute search results and surface the change to the user. ### `CONTACT_CONSENT_REQUIRED` Returned when a `lead.submit` request omits `consent`, or when the `consent.allowed_channels[]` does not include the channel the dealer's process needs to use for follow-up. See Behavior rules. ### `INVALID_CONSENT` Returned when `consent` is structurally present but unusable: e.g. `consent.scope[]` is not exactly `["lead_submission"]`, or `consent.granted_at` is in the future, or `consent_text` is empty. ### `APPOINTMENT_TIME_UNAVAILABLE` Returned by `lead.submit` when an `appointment` block was included, the requested `appointment_at` cannot be honored, AND the dealer has no `proposed_times` to offer. Dealers SHOULD prefer `data.appointment.status: "proposed"` with alternative times over this error whenever possible. The lead itself MAY still be `received` even when the appointment portion fails. ### `IDEMPOTENCY_CONFLICT` Returned by `lead.submit` when an `idempotency_key` is reused with a request payload that differs from the original submission. A retry with the *same* key AND the *same* payload MUST be treated as idempotent — the dealer returns the original lead (e.g. `data.status: "duplicate"`), not this error. `retryable: false`: the buyer agent must use a fresh `idempotency_key` for a genuinely new request. ### `RATE_LIMITED` Returned when the buyer agent exceeds the dealer's per-key quota. `retryable: true` is the default. Dealer agents SHOULD include `details.retry_after_ms` (or `details.retry_after_seconds`) so the buyer agent can back off appropriately. AAP does not standardize the rate-limit values themselves. ### `INTERNAL_ERROR` A catch-all for unexpected dealer-side failures. `retryable: true` is the default; the buyer agent SHOULD retry with exponential backoff. Dealer agents SHOULD set `error_id` so support tickets can correlate to logs. ## Example error payloads ### JSON-RPC error response ```json { "jsonrpc": "2.0", "id": "req-3", "error": { "code": -32602, "message": "Invalid params: filters.year_min must be an integer", "data": { "type": "aap.error", "error_id": "err_01HZ9EXAMPLE", "code": "SCHEMA_VALIDATION_FAILED", "message": "request failed validation with 2 errors", "retryable": false, "details": { "errors": [ { "instanceLocation": "/filters/year_min", "keyword": "type", "error": "must be an integer" }, { "instanceLocation": "/filters/make", "keyword": "additionalProperties", "error": "unknown filter key" } ] }, "created_at": "2026-04-30T10:15:30Z" } } } ``` ### Consent-required example A `lead.submit` request with `customer` but no `consent`: ```json { "type": "aap.error", "error_id": "err_01HZ9CONSENT01", "code": "CONTACT_CONSENT_REQUIRED", "message": "Customer info present but no ConsentGrant. Provide a 'consent' block with scope ['lead_submission'].", "retryable": false, "details": { "missing": "consent", "expected_scope": "lead_submission" }, "created_at": "2026-04-30T10:15:45Z" } ``` ### Rate-limit example ```json { "type": "aap.error", "error_id": "err_01HZ9RATE01", "code": "RATE_LIMITED", "message": "Per-key rate limit exceeded.", "retryable": true, "details": { "retry_after_ms": 30000 }, "created_at": "2026-04-30T10:16:00Z" } ``` ## What dealer agents MUST and MUST NOT do - Dealer agents MUST return errors using this schema (typed `aap.error` payload), not free-text messages. - Dealer agents MUST NOT leak internal stack traces in `message`. Use `details` for structured diagnostic information that is safe to display. - Dealer agents MUST set `retryable` truthfully. Buyer agents follow this signal to decide whether to retry. # Versioning Auto Agent Protocol uses [Semantic Versioning](https://semver.org/) for approved releases. The repository deliberately separates editable work from public releases: - `spec/latest/` is the only editable specification source. - `spec/v{major}.{minor}/` is an immutable release snapshot. - `https://autoagentprotocol.org/latest/` aliases the latest **approved contract release**, not `spec/latest/`. - `https://autoagentprotocol.org/docs/latest/` serves the editable documentation and links to the newest frozen documentation release. - There is no `next` directory, URL, or package channel. The word “latest” therefore has scoped meanings: `spec/latest/` and `/docs/latest/` expose current work, while the public contract-artifact `/latest/` URL means the latest stable release. Draft schema identifiers use the non-routable `https://draft.autoagentprotocol.invalid` namespace so they cannot be mistaken for a public contract. ## SemVer policy Every release is represented by a full SemVer such as `1.3.0` and a public major/minor contract path such as `v1.3`. Human-facing release labels always use all three SemVer components (`1.2.0`); wire identifiers, folders, and URLs use the corresponding major/minor contract label (`v1.2`). Because the URL cannot distinguish patch snapshots, the release tool only creates `MAJOR.MINOR.0` contracts. Editorial corrections can be made in the editable docs and included in a later release; published snapshots are not rewritten. | Change | Required release | |---|---| | New optional field, schema, skill, or behavior | Minor | | Removed or renamed field; tighter type or enum | Major | | Changed required-field meaning | Major | | Documentation or example changes included in a snapshot | Next minor or major snapshot | The automated compatibility report is conservative and structural. A minor candidate is refused when it detects a validation-affecting change. Passing that check does not replace maintainer review of semantics, security, legal text, or interoperability. ## Stable identifiers Released schemas and extensions are version-pinned: ```text https://autoagentprotocol.org/v1.3/schemas/vehicle.schema.json https://autoagentprotocol.org/extensions/aap/v1.3 ``` The schema `$id` matches its public URL. Relative `$ref` values stay within the same release. A dealer advertises the supported AAP version through the A2A extension URI; buyer agents should use the exact version the dealer advertises. ## Immutability A release freezes all material needed to reproduce and review it: - schemas, examples, and `skills.yaml`; - documentation and sidebar snapshots; - generated TypeScript, JSON-RPC OpenAPI, and MCP artifacts; - release provenance, compatibility report, and SHA-256 integrity manifest; - version-specific documentation images referenced by the snapshot. CI compares the entire pull request with its base commit and rejects additions, edits, deletions, copies, or renames within an already released path. Integrity manifests also detect local or post-merge drift. Release artifacts are copied from their reviewed snapshots; old releases are never regenerated with newer tooling. ## Public latest and released versions `releases.json` is the explicit registry of approved releases and names exactly one stable release. The production build copies that frozen contract to both its pinned artifact URL and `/latest/`. Documentation keeps the editable latest pages and every frozen version separately visible. | URL | Meaning | |---|---| | `/v1.3/...` | Immutable production contract | | `/latest/...` | Alias of the registry's stable release | | `/docs/latest/...` | Editable latest documentation, with an unreleased banner | | `/docs/v1.3/...` | Immutable documentation for release 1.3.0 | Production agents should pin `/v1.3/` (or another advertised contract), because `/latest/` advances when maintainers approve a new release. ## Release process All normal changes edit `spec/latest/` and `docs/`. A maintainer then rehearses a release: ```bash pnpm release:prepare 1.3.0 --dry-run ``` After reviewing the compatibility report and committing the approved draft, the maintainer runs the command without `--dry-run`. It copies the working source into new pinned directories, transforms draft identifiers, validates schemas and examples, generates artifacts, records provenance and hashes, updates the stable registry, and leaves `spec/latest/` unchanged. The command refuses an existing destination, a dirty working tree, a skipped version, a patch contract, or a breaking minor candidate. It does not commit, tag, push, publish packages, or deploy the site. Those remain explicit review steps. See [RELEASING.md](https://github.com/auto-agent-protocol/auto-agent-protocol/blob/main/RELEASING.md) for the maintainer checklist. ## For implementers - Pin the version advertised by the dealer; do not infer compatibility. - Validate against version-pinned schema URLs. - A dealer may advertise multiple extension versions during migration. - Do not use repository draft identifiers on the wire. - Do not depend on `/latest/` remaining on the same release. # Contributing Auto Agent Protocol is developed in public at [github.com/auto-agent-protocol/auto-agent-protocol](https://github.com/auto-agent-protocol/auto-agent-protocol). The specification and schemas use Apache-2.0; documentation prose uses CC-BY-4.0. ## Propose a change 1. Open an issue describing the real-world interoperability problem and the proposed contract behavior. 2. Let maintainers confirm the direction and expected SemVer impact. 3. Open a focused pull request against `main`. 4. Update schemas, examples, normative documentation, and tests together where relevant. 5. Run the complete validation suite before requesting review. ```bash pnpm install pnpm validate pnpm typecheck pnpm check:releases pnpm test:release pnpm build ``` ## Repository paths | Path | Purpose | |---|---| | `spec/latest/` | Editable schemas, examples, and skill manifest. Contract changes go here. | | `docs/` | Editable documentation shown by the local draft server. | | `spec/v*/` | Frozen specification releases. Never edit. | | `versioned_docs/`, `versioned_sidebars/` | Frozen release documentation. Never edit. | | `releases/v*/` | Reviewed generated artifacts, provenance, reports, and integrity manifests. Never edit. | | `releases.json` | Explicit release registry and stable-release pointer. | | `partners.json` | Partner register behind [/partners](/partners). Alphabetical; validated by `pnpm run validate:partners`. | | `generated/latest/` | Uncommitted draft generation output. | | `packages/` | Packages built from the registry's stable release. | | `tools/` | Validators, generators, release preparation, and integrity checks. | There is no `next` channel. Both local and production builds serve editable documentation with an unreleased banner at `/docs/latest/`; the version selector separately exposes every frozen release by full SemVer. Public contract artifacts at `/latest/` remain an alias of the frozen stable release. ## Released versions are frozen Do not edit, delete, rename, or add files within any existing release directory. This applies to old docs and examples as well as schemas. The whole-PR freeze check and SHA-256 manifests enforce that rule. If a released contract needs a correction, propose the correction in `spec/latest/` and release a new minor or major contract as required. Version-specific documentation images, including compatibility branding files under `static/img/v*/`, are part of their release and are frozen too. Shared unversioned brand assets may evolve without rewriting a released snapshot. ## Generated files `pnpm generate` writes draft artifacts only to `generated/latest/`. It never updates a stable package or release snapshot. `pnpm copy-static` assembles the site from immutable release data and mirrors the registry's stable release to public `/latest/`. Only the explicit release command creates a version directory: ```bash pnpm release:prepare 1.3.0 --dry-run ``` Release preparation is a maintainer operation, not a normal contribution step. It requires a clean tree, refuses overwrites, and performs no commit, tag, push, publication, or deployment. ## Review expectations - Explain the observed dealer or buyer-agent need. - Keep fields optional unless interoperability requires otherwise. - State privacy, consent, security, and compatibility consequences. - Include positive and negative examples where validation behavior changes. - Avoid provider-specific semantics in the open contract. - Treat a green structural compatibility report as evidence, not final approval. ## Community Be civil, be specific, and cite sources. Use [issues](https://github.com/auto-agent-protocol/auto-agent-protocol/issues) for proposals and [pull requests](https://github.com/auto-agent-protocol/auto-agent-protocol/pulls) for reviewed changes. ## Partners Organizations with a public, verifiable connection to AAP or to the A2A standard it profiles. Listed alphabetically at https://autoagentprotocol.org/partners; updated 2026-09-04. - A2A Protocol (Upstream standard, https://a2a-protocol.org): The Agent2Agent open standard that AAP profiles. A2A defines how independently built AI agents discover one another and exchange typed messages; AAP adds the automotive vocabulary on top of A2A v1.0 without changing the wire. Accepted in August 2026 as a Growth Stage project at the Agentic AI Foundation, which A2A describes as Linux Foundation-directed. - Dealer Handshake (AAP implementation, https://dealerhandshake.com, https://carhandshake.com): Connects a dealership's live inventory to compatible AI assistants over AAP and routes consented shopper leads into the CRM the dealer already runs, delivered as ADF. Car Handshake, the consumer marketplace it operates, publishes an agent card declaring the AAP v1.2 extension and serving inventory.facets, inventory.search, and inventory.vehicle over the JSON-RPC binding. - Space Auto (Dealership platform, https://space.auto): Unified dealership platform covering websites, digital retailing, AI-powered CRM, automation, and analytics for franchise and independent retailers, with DAISI as its AI assistant layer. Space Auto, Inc. is based in Dallas, Texas, and joined the A2A Protocol partner ecosystem in July 2026. ## Machine-readable artifacts - OpenAPI (JSON-RPC, 3.1): https://autoagentprotocol.org/v1.3/openapi-jsonrpc.yaml - MCP manifest: https://autoagentprotocol.org/v1.3/mcp.json - JSON Schemas (2020-12): https://autoagentprotocol.org/v1.3/schemas/.schema.json - Agent Card example: https://autoagentprotocol.org/v1.3/examples/agent-card.example.json - Source repository: https://github.com/auto-agent-protocol/auto-agent-protocol