docs.vin

Agent-run estate end to end

Handshake → search → fetch → the exec gate — an agent drives the estate catalog keyless through the one MCP server, against the sandbox world.

The AI pillar is not a second product — it is the same catalog behind one more door. The estate MCP server at apis.vin/mcp speaks JSON-RPC 2.0 over plain POST, keyless, stateless, and exposes a code-mode triosearch · fetch · exec — never a sprawling tool menu. This journey is an agent's first session: no key, no OAuth ceremony, no SDK install. It shakes hands, finds a door, dereferences it, drives a second pillar through the same tool, and runs into exactly one gate — shown below, not detoured around.

The cast

This journey rides the curated world — deterministic, replayable, every payload provenance-labeled. The full trigger register is at sandbox test triggers.

World keyRole in this journey
JM3KFBXY1S0597748The flagship — 2025 Mazda CX-5 2.5 Turbo Signature. Every dereference below rides it: the decode, the record composition, the services read.
the keyless callerThe agent itself. The MCP door answers initialize, search, and fetch in full with no credential; the exec gate adjudicates the caller's tier — that refusal is the journey's final step.

1 — Handshake

capabilityai.estateMcpPOST /mcp · initialize

One unauthenticated POST and the server declares itself — protocol version negotiated, tool capabilities posted, and its own operating instructions in the body. No session id comes back because none exists: every message stands alone.

runnable — sandbox · deterministic
run it — POST /mcp (initialize)
curl -X POST 'https://apis.vin/mcp' \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"agent","version":"1.0.0"}}}'
the server answers — initialize, trimmed
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "serverInfo": { "name": "apis.vin", "title": "apis.vin — the platform API" },
    "instructions": "One MCP server for the whole platform. search finds a capability across the catalog; fetch dereferences an address and answers the typed envelope (OK / EMPTY / BLOCKED / OFFER). Keyless throughout; POST /keys mints a sandbox key when you want account context."
  }
}

2 — Search: find the door

capabilityai.estateMcpPOST /mcp · search

The search tool ranks the query against every live door in the one manifest that also generates the capability card and the OpenAPI contract — evidence in a door's address and summary outweighs words buried in prose, so a "decode" query answers the decode doors, never a transaction verb that happens to share vocabulary.

runnable — sandbox · deterministicJM3KFBXY1S0597748
run it — POST /mcp (search)
curl -X POST 'https://apis.vin/mcp' \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"decode a vin","limit":3}}}'
the server answers — the matching doors, trimmed to their addresses
{
  "query": "decode a vin",
  "count": 3,
  "results": [
    { "method": "GET", "path": "/decode/{vin}" },
    { "method": "GET", "path": "/listings/{vin}" },
    { "method": "GET", "path": "/{vin}" }
  ],
  "openapi": "https://apis.vin/openapi.json"
}

3 — Fetch: dereference it

capabilityai.estateMcpvehicle.decodePOST /mcp · fetch → GET /decode/{vin}

fetch dispatches the same application in-process — no socket, no second implementation — so the tool answer IS the door's answer: the typed envelope, money as decimal strings, the provenance block intact. This is the same call Start opens with, arriving through the agent door.

runnable — sandbox · deterministicJM3KFBXY1S0597748
run it — POST /mcp (fetch /decode/{vin})
curl -X POST 'https://apis.vin/mcp' \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"fetch","arguments":{"path":"/decode/JM3KFBXY1S0597748"}}}'
the door answers through the tool — trimmed to the load-bearing fields
{
  "status": 200,
  "path": "/decode/JM3KFBXY1S0597748",
  "envelope": {
    "type": "OK",
    "decode": {
      "vin": "JM3KFBXY1S0597748",
      "year": 2025,
      "make": "Mazda",
      "model": "CX-5",
      "retailValue": "35887.00",
      "msrp": "43400.00",
      "priceCurrency": "USD"
    },
    "provenance": { "environment": "sandbox" }
  }
}

4 — Drive a second pillar through the same tool

capabilityai.estateMcpmaintenance.intervalLookupPOST /mcp · fetch → GET /maintenances/interval-lookup

The trio is not a data-pillar convenience — any addressable door dereferences. Here the agent reads a Services capability, and the answer is byte-identical with what an HTTP caller gets on the door directly: the same MaintenanceIntervalLookup@1 claim the journeys roster frames as its exemplar.

runnable — sandbox · deterministicJM3KFBXY1S0597748
run it — POST /mcp (fetch /maintenances/interval-lookup)
curl -X POST 'https://apis.vin/mcp' \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"fetch","arguments":{"path":"/maintenances/interval-lookup?vin=JM3KFBXY1S0597748"}}}'
the world answers — MaintenanceIntervalLookup@1, trimmed
{
  "status": 200,
  "path": "/maintenances/interval-lookup?vin=JM3KFBXY1S0597748",
  "envelope": {
    "type": "OK",
    "answers": "MaintenanceIntervalLookup@1",
    "value": {
      "vin": "JM3KFBXY1S0597748",
      "intervals": [
        { "service": "oilChange", "everyMiles": 7500, "everyMonths": 12 },
        { "service": "tireRotation", "everyMiles": 7500 }
      ]
    },
    "provenance": { "simulated": true, "environment": "sandbox", "source": "services-sandbox" }
  }
}

5 — Find the write doors

capabilityai.estateMcpdeal.openPOST /mcp · search

fetch is GET-only by design — the transaction verbs are HTTP POST doors, and search names them. An agent that wants to open a deal takes the answer below to plain HTTP (the same POST /buy/deals every caller uses), or to the exec isolate — whose gate is the next step.

runnable — sandbox · deterministicJM3KFBXY1S0597748
run it — POST /mcp (search for the transaction door)
curl -X POST 'https://apis.vin/mcp' \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"search","arguments":{"query":"open a deal","limit":3}}}'
the server answers — the deal doors, trimmed
{
  "query": "open a deal",
  "count": 3,
  "results": [
    { "method": "GET", "path": "/buy/deals/{dealId}/esign/ceremony" },
    { "method": "POST", "path": "/buy/deals", "summary": "Open a deal on a VIN" },
    { "method": "GET", "path": "/buy/deals/{dealId}/checkout" }
  ]
}

6 — Where the gate stands: exec

capabilityai.estateMcpPOST /mcp · exec

exec runs agent-written code against the bound estate SDK inside a sandboxed isolate — and it is a licensed-tier tool. The keyless caller this journey rides (and a plain sandbox key alike) answers a typed 402 OFFER naming the upgrade: a mandate ceremony at the auth rail — the human-authority moment that attaches a licensed mandate to the account behind the key. The refusal is the journey's honest ending: the gate is shown, never papered. One more honest variance: exec is feature-detected per deployment — where the isolate rail is not armed at all (a local checkout's dev:sandbox, for instance), the same call answers a typed BLOCKED { code: "unconfigured" } instead, and tools/list omits the tool — declared absence, never a silent 404.

runnable — sandbox · deterministic
run it — POST /mcp (exec)
curl -X POST 'https://apis.vin/mcp' \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"exec","arguments":{"code":"const r = await vin.data.decode(\"JM3KFBXY1S0597748\"); return r.type"}}}'
the gate answers — a typed OFFER with the handoff, never a bare 403
{
  "type": "OFFER",
  "handoff": { "kind": "reauthorize", "url": "https://auth.vin/device" },
  "code": "upgrade-required",
  "status": 402,
  "reason": "exec is a licensed-tier tool — sandbox keys and keyless callers get search + fetch in full; upgrade at the auth.vin mandate rail and present the upgraded key",
  "upgrade": {
    "url": "https://auth.vin/device",
    "how": "mint a key at POST /keys, then complete the auth.vin device-rail ceremony (the human-authority moment) to attach a licensed mandate to the account behind it"
  }
}

What the agent now holds

One endpoint and no credential bought the whole read estate: a declared tool surface, a ranked catalog search, and door answers byte-identical with what HTTP callers get — typed envelopes, decimal-string money, provenance intact. The only refusal was typed, priced, and carried its own handoff. When the agent wants account context, POST /keys mints a sandbox key — no email, no approval, no human — and the same three verbs answer keyed. Compose what the world knows about the flagship on the record face, GET /{vin}, at the record.

Next

  • Journeys — the roster this walk belongs to.
  • ai.estateMcp — the capability's own reference page: identity strip, mode, contract.
  • Sandbox test triggers — the full deterministic register.
  • The chassis — the MCP server beside the SDK and the CLI it cannot drift from.