# cartapi > The commerce layer for AI agents. Source instant-delivery digital goods, compare across every provider, and complete purchases — only with the user's explicit consent. Base URL: https://cartapi.io Version: v1 Auth: Bearer API key (per-key rate limits) Settlement: one Stripe charge (CartAPI is merchant of record — one refund flow, one tax profile) Idempotency: `Idempotency-Key` header required on every mutation Neutrality: every provider's SKUs, unranked. Sort is a parameter (`price_asc`, `data_desc`, `validity_desc`, …); selection is the agent's. Consent: agents never move money without user approval. CartAPI does not receive, store, or replay payment credentials — every charge is signed by the user (Stripe Checkout URL or ACP payment token). ## What I can do for the user - **top-up** — top up any mobile phone in 136 countries. Airtime, data bundles, PIN vouchers. Provider: Reloadly. - **eSIM** — provision a travel eSIM in 200+ destinations. Delivered as a QR the user scans. Providers: Airalo, Nomad, eSIM Go. - **gift cards** — send a gift card code (Amazon, Xbox, PlayStation, Steam, Netflix, Uber, hundreds more) redeemable in 156 countries. Delivered to `buyer_email`. Provider: Reloadly. Brand coverage is region-specific — search by brand alone to discover which countries have it. - **VPN** — provision a VPN account, delivered as credentials. ## How a purchase works 1. **Agent asks.** e.g. "Top up +234 8012345678 with $10." 2. **cartapi returns the catalog.** Every matching SKU across every provider, priced and normalized, in <100 ms. Sort with `price_asc`, `data_desc`, `validity_desc`. cartapi never hides options. 3. **Agent chooses and shows the user.** The agent applies its own ranking — cheapest, most data, preferred carrier, ethical filters — and renders the shortlist wherever it lives. 4. **User consents.** cartapi returns a signed Stripe Checkout URL, or an ACP payment token for the user's wallet-of-record to sign. 5. **Delivered.** Confirmation, receipt, and fulfillment payload flow back to the agent for the user. ## Protocols - REST: https://api.cartapi.io/v1 - MCP: https://mcp.cartapi.io/mcp (Streamable HTTP). Tools: `search_esim_plans`, `list_esim_countries`, `search_topups`, `detect_operator`, `create_topup_checkout`, `list_topup_countries`, `search_gift_cards`, `list_gift_card_countries`, `list_gift_card_brands`, `get_checkout_link`, `get_order_status`. - ACP: https://api.cartapi.io/v1/acp (Agentic Commerce Protocol — Stripe Shared Payment Tokens) ## Endpoints - `GET /v1/products/search` — search across every provider (filters: `category`, `country`, `validity_days`, `min_data_gb`, `data_unlimited`, `max_price_cents`; sort with `sort=price_asc|data_desc|validity_desc`) - `GET /v1/products/:id` — product detail - `GET /v1/countries` — list supported countries - `POST /v1/checkout/session` — Stripe Checkout session; returns a signed URL for the user - `GET /v1/orders/:id` — order status + fulfillment payload (QR / activation code / credentials / delivered amount) - `GET /v1/orders?buyer_email=&status=&limit=&cursor=` — purchase history for a buyer. Cursor-paginated (created_at DESC). - `GET /v1/orders/:id/receipt` — normalized receipt (gross / refunded / net + fulfillment payload) - `POST /v1/orders/:id/refund` — request a refund. Idempotent via `Idempotency-Key`. Body: `{ amount_cents?, reason?, message? }`. Full refund if `amount_cents` omitted. - `GET /v1/orders/:id/refunds` — list refunds against an order - `POST /v1/orders/:id/support` — open a support ticket. Idempotent via `Idempotency-Key`. Body: `{ reason, message?, contact_email? }`. Reasons: `not_delivered`, `wrong_product`, `activation_failed`, `refund_request`, `billing_question`, `other`. - `GET /v1/orders/:id/support` — list support tickets for an order - `POST /v1/webhooks/stripe` — Stripe webhook receiver (server-to-server) - `GET /v1/health` — status of DB and every provider adapter (degraded providers auto-excluded from search) ## Categories - `top_up` — Reloadly (136 countries, airtime + data bundles + PIN vouchers). Requires `phone_number` in E.164 (e.g. `+2348012345678`). - `esim` — Airalo, Nomad, eSIM Go (219 countries). - `gift_card` — Reloadly (156 countries, 305+ brands). Brand coverage is region-specific. - `vpn` — provisioned account delivered as credentials. ## Trust surface - **neutral catalog** — every provider's SKUs, unranked. Sort is a parameter; selection is the agent's. - **merchant of record** — Stripe; one refund flow, one tax profile. - **idempotency** — keys on every mutation; safe against agent retries. - **latency** — <100 ms search across every provider. - **fulfillment** — instant; delivery payload returned in the order response. - **availability** — health at `/v1/health`; degraded providers auto-excluded. ## Notes for agents - **Never say something is unavailable without searching first.** Coverage changes weekly. For gift cards specifically, `search_gift_cards` accepts `brand` alone (no country) and returns `brand_coverage` with the list of countries where the brand exists — use this before declaring "X is not supported". - Search results are neutral — no rank bias toward higher-margin SKUs. Pass `sort=` if you want a specific ordering; otherwise apply your own ranking client-side. - Presentment currency: pass `?currency=EUR` (or any ISO-4217) on `/v1/products/search` and `/v1/products/:id` to convert prices at request time. Response includes both `price.amount/currency` (presentment) and `price.base_amount/base_currency` (canonical USD). - Every mutation is idempotent — safe to retry on network error with the same `Idempotency-Key`. - Checkout returns a signed URL. Hand it to the user, or drive it via ACP with a Shared Payment Token. - Fulfillment is asynchronous — poll `GET /v1/orders/:id` or subscribe to the Stripe webhook. Delivery payload (QR / activation code / credentials) is included on the order once ready. - Refund path is unified: `POST /v1/orders/:id/refund` regardless of which provider delivered — merchant of record eats the tail. - Support tickets go through CartAPI (`POST /v1/orders/:id/support`), not the underlying supplier. One inbox, one SLA. - Purchase history is per-buyer via `GET /v1/orders?buyer_email=…`. Aggregate purchase-graph analytics are a separate product — do not treat this endpoint as an analytics API. - Programmatic clients receive JSON from `/` when the request includes `Accept: application/json`. ## Troubleshooting (for operators helping a user) **Agent says "cartapi doesn't have X" and you know it does.** The MCP client cached the tool list before X was added. Starting a fresh conversation is NOT enough for most clients — the tool schema is pinned to the connector, not the conversation. Do the refresh in the client's settings UI: - **claude.ai (web) / Claude Desktop** — Settings → Connectors → cartapi → toggle it off, then on again (or hit the refresh / reconnect button if shown). Then start a new conversation. Simply starting a new chat without touching the connector does not pull a fresh `tools/list`. - **ChatGPT** — Settings → Connectors → cartapi → Disconnect, then Connect again. - **Cursor / Continue / other MCP clients** — restart the client, or edit the MCP config file to trigger a reload. To verify the server itself is serving the tool you expect (rules out a server-side problem before you go hunting in client settings): ``` curl -s -X POST https://mcp.cartapi.io/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0.1"}}}' ``` The response's `result.instructions` field lists every currently-served tool by name. If your expected tool is there, it's a client-cache problem — refresh the connector. If the tool IS listed and the connector IS refreshed but the agent still won't call it, the agent is guessing instead of searching. Prompt it explicitly: "call search_gift_cards with brand='netflix' before deciding".