Developers

Agent Commerce API

Everything an AI agent needs to shop through OTTR for a customer, over REST, MCP or A2A. Try it first in the live playground.

Overview

Every integration follows the same four moves: search, prepare a purchase, ask the customer to approve, then complete it. Your agent never handles payment details and can never approve a purchase. The customer approves on OTTR, signed in, and OTTR pays the retailer with a single-use virtual card locked to that purchase.

Base URL: https://ottr.si/v1/agent. Machine-readable description: OpenAPI 3.1.

Getting access

While OTTR is in sandbox, agent keys are issued by the OTTR team. A key belongs to your agent integration and carries the scopes you are allowed to request: search, purchase_intents, approval_requests and orders. The key is shown once; store it as a secret.

Authentication

Send your key on every call: Authorization: Bearer ottr_ak_.... Search needs nothing else.

To act for a customer, you also need their permission. The customer authorises your agent from their OTTR Security page, choosing scopes, and gets a customer reference to give your agent. Send it as OTTR-Customer-Ref: ottr_cr_.... A reference only works with the key it was issued to, expires after 90 days, and stops working the moment the customer revokes it. OTTR stores only a keyed hash of keys and references.

REST endpoints

MethodPathScopeWhat it does
POST/searchnoneSearch products. Body: query, max_price (pounds, including delivery), limit (up to 10).
POST/purchase-intentspurchase_intentsPrepare a purchase at the current price. Body: offer_id, quantity. Charges nothing.
GET/purchase-intents/{id}purchase_intentsThe purchase's state, and its order once confirmed.
POST/purchase-intents/{id}/approval-requestsapproval_requestsSend the customer a secure approval link, on their own channel as well as in the response.
POST/purchasespurchase_intentsProceed with an approved purchase. 409 approval_required until the customer approves.
GET/orders/{id}ordersOrder status, shipment and refunds, by order id or merchant reference.
POST/orders/{id}/returnsordersStart a return. Body: reason (optional).
POST /v1/agent/search
{ "query": "coffee machine", "max_price": 500, "limit": 3 }

200 OK
{ "results": [ {
    "offer_id": "cmv2izg65000o451sprqg5ekw",
    "title": "De'Longhi Magnifica Evo Bean to Cup Coffee Machine",
    "merchant": "Ottr Test Merchant", "merchant_environment": "sandbox",
    "price": { "amount": 449, "currency": "GBP" },
    "delivery": { "amount": 0, "currency": "GBP", "estimate": "2026-10-13" },
    "total": { "amount": 449, "currency": "GBP" },
    "in_stock": true, "purchasable": true, "url": null } ] }

Prices, stock and delivery come from the retailer at the time of the call. OTTR re-checks the live price when the customer approves; if it has changed, the approval is void and nothing is charged.

Errors

Errors are JSON: { "code": "...", "message": "..." }. Messages are safe to show to a person.

401unauthorisedMissing or invalid agent key.
401customer_requiredNo OTTR-Customer-Ref header.
403customer_permissionThe customer has not authorised your agent, or revoked it.
403scopeThe customer did not grant this scope.
404not_found / unknown_offerNo such purchase, order or offer for this customer.
409not_purchasableOTTR cannot buy this offer; link the customer to the retailer instead.
409already_purchasingThis purchase is already in progress.
409approval_requiredThe customer has not approved yet.

MCP server

Streamable HTTP at https://ottr.si/api/mcp, stateless, with the same headers as the REST API. Tools: ottr_search_products, ottr_compare_products, ottr_create_purchase_intent, ottr_request_approval, ottr_execute_purchase, ottr_get_order_status.

claude mcp add --transport http ottr https://ottr.si/api/mcp \
  --header "Authorization: Bearer ottr_ak_..." \
  --header "OTTR-Customer-Ref: ottr_cr_..."

A2A

Agent card at /.well-known/agent-card.json; JSON-RPC at https://ottr.si/api/a2a, speaking A2A 1.0 and 0.3 (send A2A-Version). Methods: SendMessage, GetTask and CancelTask, with their 0.3 names.

  • search_products (no key): plain text, or data { "skill": "search_products", "query": "...", "max_price": 250 }. Returns a completed task with the offers as a data artifact.
  • compare_products (no key): data { "skill": "compare_products", "offer_ids": [...] }.
  • purchase (key and customer reference): data { "skill": "purchase", "offer_id": "..." }. Prepares the purchase, sends the customer the approval request, and returns task tsk_... in auth-required. Poll GetTask: the task always reflects the real purchase.
  • order_status (key and customer reference): data { "skill": "order_status", "order_id": "..." }.

Clients that cannot set custom headers may pass the customer reference as message.metadata.customerRef.

Purchase states

REST stateA2A task stateMeaning
OFFER_SELECTEDinput-requiredPrepared; approval not yet requested.
AWAITING_APPROVALauth-requiredWaiting for the customer.
APPROVED … ORDER_CONFIRMEDworkingFunding, virtual card, checkout.
OUTCOME_UNKNOWNworkingConfirming with the retailer before any retry. Never ordered twice.
COMPLETEDcompletedOrdered; order reference available.
FAILEDfailedNot completed; any funding is refunded.
CANCELLEDcanceledDeclined or cancelled; nothing charged.
REFUND_PENDING / REFUNDEDcompletedOrdered, then refunded.

Sandbox

OTTR is in development. Today every purchasable offer comes from Ottr Test Merchant, a sandbox retailer run by OTTR; funding, virtual cards and checkout run in sandbox and nothing real is charged or delivered. Offers from other retailers can appear in search with purchasable: false: show them, and link the customer to the retailer. The API, MCP tools and A2A skills will not change shape when live retailers and payments are switched on.