Partner API

The Terudo partner API. Authentication with secret keys, trip quotes, orders in commission or net mode, idempotency, eSIMs, top-ups, pagination, currencies, errors and rate limits.

REST, JSON, one base URL: https://terudo.com/api/partner/v1. Every call carries a key:

curl https://terudo.com/api/partner/v1/me -H "Authorization: Bearer sk_test_…"

Use secret keys (sk_…) from your server only. A publishable key (pk_…) reaches only the public routes Elements use. The full list of routes is in the API reference; an OpenAPI file is at https://terudo.com/developers/openapi.json.

The usual flow

  1. Quote the trip: POST /quotes with destination, start_date, end_date (and profile: light, normal, heavy). The answer lists the plans that cover the stay; one has recommended: true.
  2. Check if needed: GET /coverage/{destination} (is the traveller's French plan enough?) and GET /compatibility?device=… (does the phone take an eSIM?).
  3. Order: POST /orders with the plan id, the client's email and your reference.
  4. Follow: webhooks (order.paid, order.delivered, esim.installed, esim.low_data, …) or GET /orders/{id}.
  5. After the sale: usage, top-ups, resending the install email, cancelling or refunding an eSIM never installed.

Plans

  • Fixed plans from the grid: MA-10 is Morocco, 10 GB, 30 days.
  • Daily plans, built for a number of days: MA-DAY2-8D is Morocco, 2 GB a day for 8 days, then reduced speed.

GET /plans?destination=MA&days=8 lists both at your rate: public price, default client price, your net price and commission. GET /destinations lists the codes (countries, and regions such as EU-35). A plan id is passed as is to POST /orders.

Orders: commission or net

curl -X POST https://terudo.com/api/partner/v1/orders \
  -H "Authorization: Bearer sk_test_…" -H "Content-Type: application/json" \
  -d '{
        "plan": "MA-DAY2-8D",
        "kind": "commission",
        "reference": "visa-48213",
        "client": { "email": "[email protected]", "locale": "fr" },
        "trip": { "start_date": "2026-12-03", "end_date": "2026-12-09" },
        "return_url": "https://www.your-agency.example/bookings/48213",
        "metadata": { "booking_id": "48213" }
      }'
  • kind: "commission" (default): the order is pending with a checkout_url. Show it to the traveller, or pass notify_client: true and Terudo emails it. discount_percent (0–15, default 5) is the client discount, taken from your commission. currency lets the traveller pay in USD, GBP, CHF, CAD, AUD, JPY, AED, SAR, CNY, INR, BRL or MXN at Terudo's own rounded price; amounts in your statements stay in euros. POST /orders/{id}/remind emails the payment link again (two reminders at most, 24 h apart).
  • kind: "net": paid from your prepaid credit at your net price, delivered at once, or at deliver_at (ISO datetime, 90 days at most), or days_before_trip with trip. A daily plan must be installed within 30 days: sold more than 25 days before the trip, it is delivered 7 days before departure unless you say otherwise. auto_topup_max (0–10) adds the same plan from your credit when data runs low. Insufficient credit answers 402 insufficient_credit.

Order statuses: pending, paid, ordered, delivered, failed, refunded, cancelled, expired (an unpaid link that lapsed). Once delivered, the order carries an esim object: id, status, install links, page_url, qr_url, activation code (secret key only).

Idempotency

Give every order a reference (your booking or file id, 120 characters at most, unique per account). Creating an order with a reference that exists returns that order (200) instead of a duplicate (201). Without a reference in the body, an Idempotency-Key header plays the same role. Retry safely on timeouts.

GET /orders/{id} accepts the Terudo id or your reference.

eSIMs after the sale

Route
GET /esims/{id} Status, installation, validity, usage, activation code, iOS and Android install links, QR code
GET /esims/{id}/usage Data used and total, refreshed at most every 10 minutes
GET /esims/{id}/topups then POST /esims/{id}/topups Add data or days to the same eSIM. Net: from your credit. Commission: a new payment link for the traveller, returned as a new order.
POST /esims/{id}/resend Emails the install instructions again (three a day)
POST /esims/{id}/suspend, /resume Net orders: cut or restore data (lost phone, traveller gone)
POST /orders/{id}/cancel Cancels an unpaid link, or refunds an eSIM never installed (to your credit in net, to the traveller's card in commission)

The eSIM id is also the token of the traveller's "My eSIM" page (page_url) and the esimId of the esim-status element.

Lists and pagination

Lists answer { "object": "list", "data": [ … ] }, newest first, 50 a page. Pass starting_after=<id of the last item> for the next page. GET /orders filters on reference, status and email.

Errors

Errors answer { "error": { "code": "…", "message": "…" } } with an HTTP status:

Status Codes
401 unauthorized Key missing, revoked or unknown
402 insufficient_credit Credit too low for a net order
403 forbidden, demo_key Wrong key kind, origin not listed, or checkout with the demo key
404 not_found Unknown, or another account's
409 conflict, not_refundable, not_topupable, not_remindable The order's state no longer allows it
422 invalid_request, unknown_plan, invalid_trip, plan_too_short A parameter was refused; the message says which
429 rate_limited 600 requests a minute per key
503 payments_closed Live payments not open yet

Messages are written to be shown to your staff; code is what your code should test.

Statements

GET /statements gives, per month, the sales, the commissions due and the net purchases, with the payout once made; GET /statements/{YYYY-MM} lists the month's paid sales.

Test mode

sk_test_… keys reach the same API in the sandbox: orders are test sales, the pay page simulates the payment, eSIMs are not real, nothing is billed, statements stay empty. Webhooks have their own test endpoints. See Testing.