# Courio: full documentation > Courio (courio.si) is a REST API and MCP server that prints a PDF and mails it as a real postal letter to supported destinations worldwide. No account. Quote for free, get the user to approve and pay (Stripe Checkout link, or x402 USDC on Base), then track the letter. Short version: https://courio.si/llms.txt. Installable skill: https://courio.si/skill.md. Interfaces: REST https://api.courio.si/v1, OpenAPI 3.1 https://api.courio.si/openapi.json, MCP (streamable HTTP, no auth) https://api.courio.si/mcp with tools get_coverage, quote_letter, send_letter, get_letter_status. Over MCP you cannot pay: send_letter returns a Stripe link for the user. ## x402 spend limit Official x402 clients refuse payments above $1 by default. A letter usually costs more: raise the spend limit of your x402 client to at least the quote's `usd_amount`, or it will refuse to pay. ## Flow Courio sends physical letters on behalf of AI agents. No account is needed: an agent quotes, creates, pays and follows a letter with plain HTTPS calls. ## Agent flow 1. **Quote** - `POST /v1/letters/quote` with sender, recipient, a service level and the document (`pdf_base64`, `pdf_url` or `text`). You get a frozen price (EUR, VAT included, plus the USD amount for x402) valid 24 h, and a `quote_id`. Check `GET /v1/coverage` first if unsure a country/service exists. 2. **Create** - `POST /v1/letters` with the `quote_id` and a `payment_method`. - `stripe`: answers **201** with `payment.checkout_url` (card-only Stripe Checkout). A human opens it and pays. - `x402` (may be disabled on a deployment: you then get **422** `payment_method_unavailable`, use `stripe`): the first call without payment answers **402** with the requirements in the `PAYMENT-REQUIRED` header (and the body). Sign a USDC payment and resend the *same* request with the `PAYMENT-SIGNATURE` header; the answer is **201** with `PAYMENT-RESPONSE`. Official x402 clients cap a single payment at $1 by default: raise the spend limit to at least the quote's `usd_amount`, or the client will refuse to pay. 3. **Pay** - the letter is created in `awaiting_payment` (Stripe) or directly `paid` (x402). 4. **Poll or listen** - `GET /v1/letters/{id}` or receive webhooks (below). Statuses: `awaiting_payment`, `paid`, `submitted`, `in_transit`, `delivered`, `returned`, `failed`, `manual_pending`, `manual_done`, `expired`. **Payment is irreversible once made**: after payment the letter is printed and mailed; it cannot be cancelled or edited. If Courio cannot fulfil it, the payment is refunded automatically (Stripe) or by hand (x402). Check the document and the addresses before paying. ## Terms Paying for a letter accepts the terms of sale: https://courio.si/terms. Privacy policy: https://courio.si/privacy. Legal notice: https://courio.si/legal. Quote and letter-creation responses repeat `terms_url` and `privacy_url`: show the terms to the human before payment. Every `POST /v1/*` requires an `Idempotency-Key` header. Errors are `application/problem+json` with `type`, `title`, `status`, `code` and a one-sentence `hint` meant for an agent. ## Authentication Optional. Without a key you can quote, create and read a letter you know the id of (redacted view). A `ck_` API key (`Authorization: Bearer ck_...`) attaches letters to your key, gives the full view of your letters, enables `GET /v1/letters` and has its own daily limit. ## Webhooks Pass `webhook_url` (public https) when creating a letter. The creation response returns a `webhook_secret` (only there). Each status change is POSTed as JSON `{event_id, letter_id, status, occurred_at, data}` with the header `Courio-Signature: t=,v1=` where `v1 = HMAC-SHA256(webhook_secret, ".")`. Verify the signature on the raw body and reject old timestamps. Any non-2xx answer is retried with backoff for 24 h; deduplicate on `event_id`. ## MCP The same capabilities are exposed as MCP tools at `/mcp` (streamable HTTP, stateless, POST only). ## Coverage and starting prices From GET https://api.courio.si/v1/coverage at build time (1 page, black and white; the exact price is in the quote): - FR: standard (Lettre verte) from €8.70 VAT incl., 5-15 days, handled manually; tracked (Lettre suivie) from €14.30 VAT incl., 5-15 days, handled manually; registered (Lettre recommandée) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Lettre recommandée avec AR) from €29.40 VAT incl., 5-15 days, handled manually - DE: standard (Standardbrief) from €8.70 VAT incl., 5-15 days, handled manually; tracked (Einschreiben Einwurf) from €14.30 VAT incl., 5-15 days, handled manually; registered (Einschreiben) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Einschreiben Rückschein) from €29.40 VAT incl., 5-15 days, handled manually - AT: standard (Brief) from €8.70 VAT incl., 5-15 days, handled manually; registered (Einschreiben) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Einschreiben mit Rückschein) from €29.40 VAT incl., 5-15 days, handled manually - CH: standard (B-Post Brief) from €8.70 VAT incl., 5-15 days, handled manually; tracked (A-Post Plus) from €14.30 VAT incl., 5-15 days, handled manually; registered (Einschreiben) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Einschreiben mit Rückschein) from €29.40 VAT incl., 5-15 days, handled manually - NL: standard (Brief) from €8.70 VAT incl., 5-15 days, handled manually; registered (Aangetekende brief) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Aangetekend met ontvangstbevestiging) from €29.40 VAT incl., 5-15 days, handled manually - BE: standard (Lettre non prioritaire) from €8.70 VAT incl., 5-15 days, handled manually; registered (Envoi recommandé) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Recommandé avec accusé de réception) from €29.40 VAT incl., 5-15 days, handled manually - LU: standard (Lettre) from €8.70 VAT incl., 5-15 days, handled manually; registered (Envoi recommandé) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Recommandé avec avis de réception) from €29.40 VAT incl., 5-15 days, handled manually - GB: standard (Second Class) from €8.70 VAT incl., 5-15 days, handled manually; tracked (Tracked 48) from €14.30 VAT incl., 5-15 days, handled manually; registered (Signed For) from €22.50 VAT incl., 5-15 days, handled manually - ES: standard (Carta ordinaria) from €8.70 VAT incl., 5-15 days, handled manually; registered (Carta certificada) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Carta certificada con aviso de recibo) from €29.40 VAT incl., 5-15 days, handled manually - US: standard (First-Class Mail) from €8.70 VAT incl., 5-15 days, handled manually; tracked (First-Class Mail with tracking) from €14.30 VAT incl., 5-15 days, handled manually; registered (Certified Mail) from €22.50 VAT incl., 5-15 days, handled manually; registered_ar (Certified Mail with Return Receipt) from €29.40 VAT incl., 5-15 days, handled manually - * (every other country): standard (Letter) from €8.70 VAT incl., 5-15 days, handled manually ## Endpoints ### POST /v1/letters/quote Get a frozen price for a letter. Validates addresses and the document, runs content moderation and routes to a provider. Creates no letter and charges nothing. The quote is valid 24 h. Parameters: - Idempotency-Key (header, required): Unique key (max 255 chars) per logical request. Repeating a request with the same key and body replays the stored response; same key with another body is `idempotency_key_reused`. Body (* = required): service*: string, sender*: object, recipient*: object, document*: one of, options: object, currency: string Example body: ```json { "service": "registered_ar", "sender": { "name": "Acme SAS", "address_lines": [ "12 rue de la Paix" ], "postal_code": "75002", "city": "Paris", "country": "FR", "email": "ops@acme.example" }, "recipient": { "name": "Erika Mustermann", "address_lines": [ "Unter den Linden 1" ], "postal_code": "10117", "city": "Berlin", "country": "DE" }, "document": { "text": "Madame, Monsieur,\n\nPar la présente, nous vous notifions la résiliation du contrat n° 4711.\n\nCordialement" }, "options": { "color": false, "duplex": true }, "currency": "EUR" } ``` Answers: 200, 400, 401, 422, 429, 500, 503 ### POST /v1/letters Create a letter from a quote and pay for it. With `payment_method: "stripe"` answers 201 with a Checkout URL. With `"x402"`, call once without `PAYMENT-SIGNATURE` to get the 402 and its `PAYMENT-REQUIRED` header, then resend the same request with the signed payment. The 402 (and a 429 `limit_exceeded`) is never stored by the idempotency layer: retry the same request with the same key. Payment is irreversible once made. Parameters: - Idempotency-Key (header, required): Unique key (max 255 chars) per logical request. Repeating a request with the same key and body replays the stored response; same key with another body is `idempotency_key_reused`. - PAYMENT-SIGNATURE (header): x402 only: base64 JSON of the signed x402 v2 PaymentPayload for `accepts[0]` of the 402 response. Body (* = required): quote_id*: string, payment_method*: string, webhook_url: string Example body: ```json { "quote_id": "qt_7Hn2KdQ4VxLm9RwYb3TcZs5P", "payment_method": "stripe", "webhook_url": "https://agent.example/hooks/courio" } ``` Answers: 201, 400, 401, 402, 404, 409, 410, 422, 429, 500, 503 ### GET /v1/letters List the letters created with your API key. Parameters: - status (query): - limit (query): - cursor (query): `next_cursor` of the previous page. Answers: 200, 400, 401, 500 ### GET /v1/letters/{id} Read a letter and its timeline. Public with the letter id (recipient redacted to city and country, no tracking details beyond status); full view when called with the API key that created it. Proof URLs are signed and short-lived. Parameters: - id (path, required): Answers: 200, 401, 404, 500 ### GET /v1/coverage Countries and services Courio delivers, with starting prices. Parameters: - country (query): Uppercase ISO 3166-1 alpha-2 code. Answers: 200, 400, 500 ## Errors - validation_failed (400: The body, query or path did not validate; `errors` lists the fields.) - idempotency_key_required (400: Idempotency-Key header missing.) - unauthorized (401: Authorization header present but not a valid ck_ API key.) - idempotency_key_reused (422: Idempotency-Key already used with a different body.) - invalid_address (422: An address cannot be used (`field` says which).) - product_unavailable (422: No provider delivers this service to this country.) - document_invalid (422: The PDF/text is unusable (`reason`).) - content_rejected (422: Moderation refused the content.) - amount_limit_exceeded (422: The price exceeds the per-letter cap.) - not_routable (422: No provider could be routed for this letter.) - limit_exceeded (429: Daily limit reached: letters (IP, sender email, wallet or API key) or quotes (IP or API key); retry after `retry_after` seconds with the same request and key.) - internal_error (500: Unexpected server error.) - moderation_unavailable (503: Moderation could not run; nothing was accepted, retry.) - pricing_unavailable (503: No EUR/USD rate available; retry shortly.) - quote_not_found (404: No quote with this id.) - quote_already_used (409: The quote already created a letter; `letter_id` points to it.) - quote_expired (410: The 24 h quote validity elapsed; request a new quote.) - payment_method_unavailable (422: The payment method is disabled on this deployment (x402 may be off); use `stripe`.) - payment_unavailable (503: Stripe or the x402 facilitator is unavailable; retry the same request.) - letter_not_found (404: No letter with this id.) ## Pages - https://courio.si/agents - https://courio.si/countries - https://courio.si/pricing - https://courio.si/tracking