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
- Quote the trip:
POST /quoteswithdestination,start_date,end_date(andprofile:light,normal,heavy). The answer lists the plans that cover the stay; one hasrecommended: true. - Check if needed:
GET /coverage/{destination}(is the traveller's French plan enough?) andGET /compatibility?device=…(does the phone take an eSIM?). - Order:
POST /orderswith the plan id, the client's email and yourreference. - Follow: webhooks (
order.paid,order.delivered,esim.installed,esim.low_data, …) orGET /orders/{id}. - After the sale: usage, top-ups, resending the install email, cancelling or refunding an eSIM never installed.
Plans
- Fixed plans from the grid:
MA-10is Morocco, 10 GB, 30 days. - Daily plans, built for a number of days:
MA-DAY2-8Dis 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 ispendingwith acheckout_url. Show it to the traveller, or passnotify_client: trueand Terudo emails it.discount_percent(0–15, default 5) is the client discount, taken from your commission.currencylets 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}/remindemails 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 atdeliver_at(ISO datetime, 90 days at most), ordays_before_tripwithtrip. 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 answers402 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.