API reference
Every route of the Terudo partner API with its key, parameters and an example, the webhook events and the error codes. Generated from the live definition.
Base URL: https://terudo.com/api/partner/v1. Every call: Authorization: Bearer <key>. JSON in and out. OpenAPI 3.1: https://terudo.com/developers/openapi.json
Account
GET /me
Your account. Key: secret key. The key’s account, its mode, whether the member rate is on, and the prepaid credit left for net orders.
Response:
{ "object": "account", "id": 12, "name": "SimpleVisa", "mode": "live", "member_rate": false, "payments_open": true, "credit_cents": 48200, "currency": "EUR" }
Catalogue
Two plan families. Fixed plans from the grid (“MA-10”: Morocco, 10 GB, 30 days). Daily plans, built on demand for a number of days (“MA-DAY2-8D”: Morocco, 2 GB a day for 8 days, then reduced speed). A plan id is passed as is when ordering.
GET /destinations
Destinations. Key: secret key. The destinations of the catalogue, with their fixed plan ids and whether daily plans exist.
Response:
{ "object": "list", "data": [ { "code": "MA", "name": "Maroc", "name_en": "Morocco", "fixed_plans": ["MA-3", "MA-10"], "daily_plans": true }, … ] }
GET /plans
Plans. Key: secret key. Plans at your rate: recommended public price, default client price, net price and commission. With destination and days, daily plans of that length are added.
| Parameter | Type | Required | Description |
|---|---|---|---|
destination |
string | no | ISO code (MA) or region (EU-35). Omitted: the whole grid. |
days |
integer 1–30 | no | Length of the daily plans to add. |
currency |
USD | GBP | CHF | CAD | AUD | JPY | AED | SAR | CNY | INR | BRL | MXN | no | Adds local_price: the client price in that currency, in minor units (whole yen). Our own rounded price, fixed until rates are next set. Every other amount stays in euros. |
Response:
{ "object": "list", "data": [ { "id": "MA-10", "object": "plan", "kind": "fixed", "destination": "MA", "name": "Maroc", "gb": 10, "days": 30, "public_price_cents": 2999, "client_price_cents": 2849, "net_cents": 1780, "commission_cents": 594 }, { "id": "MA-DAY2-7D", "kind": "daily", "gb": 2, "days": 7, "speed_after_quota": "1 Mbit/s", … } ] }
GET /plans/{id}
One plan. Key: secret key. One plan by id, fixed or daily.
POST /quotes
Quote for a trip. Key: secret key. Give the destination and dates: the answer lists the plans that cover the stay and recommends one, the cheapest that covers it all, with a buffer day for a late flight. Nothing is created.
| Parameter | Type | Required | Description |
|---|---|---|---|
destination |
string | yes | Destination code. |
start_date |
date | yes | Arrival, YYYY-MM-DD. |
end_date |
date | yes | Departure, YYYY-MM-DD. 60 days at most. |
profile |
light | normal | heavy | no | Estimated use: 0.5, 1 or 2 GB a day. Default normal. |
buffer_days |
integer 0–3 | no | Days added to the covered length. Default 1. |
currency |
USD | GBP | CHF | CAD | AUD | JPY | AED | SAR | CNY | INR | BRL | MXN | no | Adds local_price to each option, the client price in that currency. |
Request:
{ "destination": "MA", "start_date": "2026-12-03", "end_date": "2026-12-09", "profile": "normal" }
Response:
{ "object": "quote", "trip_days": 7, "covered_days": 8, "needed_gb": 7,
"options": [
{ "id": "MA-DAY1-8D", "kind": "daily", "gb": 1, "days": 8, "public_price_cents": 2099, "net_cents": 1246, "covers_trip": true, "recommended": true, "reason": "1 Go par jour pendant 8 jours, puis débit réduit" },
{ "id": "MA-10", "kind": "fixed", "gb": 10, "days": 30, "public_price_cents": 2999, "covers_trip": true, "recommended": false, "reason": "10 Go sur 30 jours, couvre 7 Go estimés" },
…
] }
GET /coverage/{destination}
Does the traveller’s plan already cover it?. Key: any key. Which main French mobile plans already include the destination, from our verified records. With brand and plan, checks one plan. To tell the client the truth before selling.
| Parameter | Type | Required | Description |
|---|---|---|---|
brand |
string | no | The traveller’s plan brand (Free, Sosh, …). |
plan |
string | no | Plan name. |
Response:
{ "object": "coverage", "destination": "MA", "french_plans_including": [], "checked_plan": null, "advice": "Aucun forfait français principal n'inclut cette destination : une eSIM est le bon réflexe." }
GET /compatibility
Does the phone take an eSIM?. Key: any key. From the model as the traveller gives it: yes, depends (some versions only, the note says which), no or unknown (model not in our list, the note describes the brand’s range). The two checks to do on the phone always come along: *#06# for the EID, phone not carrier-locked.
| Parameter | Type | Required | Description |
|---|---|---|---|
device |
string | yes | Model: iPhone 13 mini, Galaxy S23, Pixel 7a… |
Response:
{ "object": "compatibility", "device": "iPhone 13 mini", "brand": "Apple", "compatible": "yes", "note": "Sauf modèle acheté en Chine continentale…", "checks": ["Composer *#06# : si un numéro EID s'affiche, le téléphone prend une eSIM.", "Le téléphone doit être désimlocké…"] }
Orders
Two models, chosen on every order. Commission: Terudo sells to the client, who pays on our page (checkout_url) and accepts our terms; you earn the commission. Net: you buy at the net price from your prepaid credit, the eSIM is delivered at once and you invoice your client yourself. An order with a reference is idempotent: the same reference returns the same order.
POST /orders
Create an order. Key: secret key. Creates the order. In commission mode, returns a checkout_url to show the client (or notify_client to have us mail the link). In net mode, debits the credit and starts delivery, now or at deliver_at.
| Parameter | Type | Required | Description |
|---|---|---|---|
plan |
string | yes | Plan id, fixed or daily. |
kind |
commission | net | no | Default commission. |
client.email |
string | yes | The traveller. The eSIM goes to this address. |
client.locale |
fr | en | de | es | it | pt | zh | ar | hi | no | Language of the emails, the pay page and the eSIM page. A browser tag works (de-AT). Default: your shop’s language, else fr. The client can change it on their pages. |
reference |
string ≤ 120 | no | Your id (file, booking). Unique per account, idempotency key: the same reference returns the same order (200). An Idempotency-Key header stands in for it when the body has none. |
trip.start_date, trip.end_date |
date | no | Trip dates. Refused if the plan is too short. Drives reminders. |
discount_percent |
integer 0–15 | no | Commission only. Client discount taken from your commission. Default 5. |
currency |
EUR | USD | GBP | CHF | CAD | AUD | JPY | AED | SAR | CNY | INR | BRL | MXN | no | Commission only. The currency the client pays in, at our rounded price in it; they can change it on the pay page. The response gives charge (currency and amount in minor units); price_cents, the commission and your statements stay in euros. Default EUR. Net orders are always EUR. |
return_url |
https URL | no | Where the pay page sends the client once paid. |
notify_client |
boolean | no | Commission: we email the payment link. Default false. |
deliver_at |
datetime ISO 8601 | no | Deferred delivery, 90 days at most. Or days_before_trip (integer) 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 told otherwise. |
auto_topup_max |
integer 0–10 | no | Net only: dynamic plan. When data runs low, the same plan is added from your credit, at most this many times. |
metadata |
object ≤ 20 clés | no | Your data, returned as is. |
Request:
{ "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://partner.example/trips/48213", "metadata": { "trip_id": "48213" } }
Response:
{
"id": "6b1f…",
"object": "order",
"reference": "visa-48213",
"mode": "live",
"kind": "commission",
"status": "pending",
"plan": { "id": "MA-DAY2-8D", "kind": "daily", "destination": "MA", "name": "Maroc", "gb": 2, "days": 8 },
"client": { "email": "[email protected]", "locale": "fr" },
"trip": { "start_date": "2026-12-03", "end_date": "2026-12-09" },
"price_cents": 2944, "currency": "EUR", "discount_percent": 5,
"net_cents": 1840, "commission_cents": 613,
"checkout_url": "https://terudo.com/pay/…/",
"return_url": "https://partner.example/trips/48213",
"deliver_at": null, "auto_topup_max": 0,
"metadata": { "trip_id": "48213" },
"created_at": "2026-11-08T10:12:00Z", "paid_at": null, "esim": null
}
GET /orders
List. Key: secret key. Your orders, newest first, 50 a page (starting_after = last id). Filters: reference, status, email.
GET /orders/{id}
One order. Key: secret key. By Terudo id or by your reference. Statuses: pending, paid, ordered, delivered, failed, refunded, cancelled, expired. The esim object appears once delivered.
POST /orders/{id}/cancel
Cancel. Key: secret key. An unpaid link is cancelled. A paid order is refunded while the eSIM was never installed: to your credit in net mode, to the client’s card in commission mode.
| Parameter | Type | Required | Description |
|---|---|---|---|
reason |
string | no | Reason, kept in the log. |
POST /orders/{id}/remind
Remind the client. Key: secret key. Mails the payment link again (commission, two reminders at most, 24 h apart).
eSIMs
An eSIM’s id is that of its order’s esim object; it is also the token of the client’s “My eSIM” page (page_url), which you may show in your own space.
GET /esims/{id}
One eSIM. Key: secret key. Status, installation, usage, activation code, iOS and Android install links, QR code.
Response:
{
"id": "k8Zs…", // esim id, also the token of the client's page
"object": "esim",
"status": "active", "profile_status": "enabled",
"installed_at": "2026-12-02T18:04:11Z", "expires_at": "2026-12-11T00:00:00Z",
"used_bytes": 1431655765, "total_bytes": 17179869184,
"iccid": "8944…", "activation_code": "LPA:1$…$…", "smdp_address": "…", "matching_id": "…",
"ios_install_url": "https://esimsetup.apple.com/…", "android_install_url": "https://esimsetup.android.com/…",
"page_url": "https://terudo.com/esim/k8Zs…/", "qr_url": "https://terudo.com/api/esims/k8Zs…/qr/",
"topups": 0
}
GET /esims/{id}/usage
Usage. Key: secret key. Used and total data, refreshed from the supplier at most every 10 minutes.
GET /esims/{id}/topups
Available top-ups. Key: secret key. The same plan again and, for a daily plan, extra days (1, 3, 7), on the same eSIM when the supplier allows it.
POST /esims/{id}/topups
Top up. Key: secret key. Net: the top-up is taken from the credit and added to the eSIM. Commission: a payment link opens for the client and comes back as a new order.
| Parameter | Type | Required | Description |
|---|---|---|---|
plan |
string | no | A plan of the same destination. Default: the eSIM’s. |
POST /esims/{id}/resend
Resend the instructions. Key: secret key. Mails the install e-mail (QR code, install link) again to the traveller’s address on the order: lost e-mail, new phone. Delivered eSIMs only, three times a day at most.
POST /esims/{id}/suspend
Suspend. Key: secret key. Cuts the data of an eSIM bought at the net price (traveller gone, lost device). Nothing is refunded, the plan keeps running.
POST /esims/{id}/resume
Resume. Key: secret key. Restores the data.
Webhooks
We call your https address at every step. Each call carries Terudo-Signature: t=
GET /webhooks
List. Key: secret key. Your endpoints in the key’s mode, and the list of events.
POST /webhooks
Create. Key: secret key. public https url (port 443) and events (empty: all). The secret is returned.
Request:
{ "url": "https://partner.example/hooks/terudo", "events": ["order.paid", "order.delivered", "esim.installed", "esim.low_data"] }
Response:
{ "id": 3, "object": "webhook_endpoint", "url": "https://partner.example/hooks/terudo", "mode": "live", "events": [ … ], "secret": "whsec_…" }
PATCH /webhooks/{id}
Update. Key: secret key. url, events, enabled.
DELETE /webhooks/{id}
Delete. Key: secret key.
POST /webhooks/{id}/test
Send a test. Key: secret key. A signed test event, right away.
GET /webhooks/{id}/deliveries
Deliveries. Key: secret key. The last 100 deliveries, with their state and your server’s answer.
POST /webhooks/{id}/deliveries/{delivery_id}/resend
Resend. Key: secret key.
Statements
GET /statements
By month. Key: secret key. Sales, commissions due and net purchases per month, with the payout once made.
GET /statements/{month}
One month. Key: secret key. The paid sales of the month (YYYY-MM).
Elements: the publishable key
These routes serve the Elements in your pages, with a pk_… key and CORS open to the key’s sites. Never a net price. An element_session created by your server with the secret key unlocks the client discount, the member rate, and a prefilled trip and reference.
POST /element_sessions
Session for a page. Key: secret key. Returns client_token, valid 2 hours, to pass to the Elements (session option).
| Parameter | Type | Required | Description |
|---|---|---|---|
discount_percent |
integer 0–15 | no | Client discount on this page. |
client_email, reference, return_url, locale, metadata |
— | no | As for an order. |
trip |
object | no | destination, start_date, end_date, profile. |
GET /public/destinations
Destinations. Key: publishable key.
GET /public/plans
Plans at the public price. Key: publishable key. ?destination, ?days, ?session.
POST /public/quotes
Quote. Key: publishable key. As /quotes, at the public price.
POST /public/checkout
Hosted checkout. Key: publishable key. Creates a commission order and returns checkout_url. 30 an hour per IP address.
| Parameter | Type | Required | Description |
|---|---|---|---|
plan |
string | yes | |
email |
string | yes | Unless the session carries it. |
return_url, locale, trip, session |
— | no | return_url is kept only when its origin is among the sites listed on the publishable key, or when it comes from the signed session. A key with no site list takes no return address from the page. |
GET /public/esims/{id}
eSIM page. Key: publishable key. The “My eSIM” page data, without the activation code.
Webhook events
| Type | When |
|---|---|
order.paid |
The client paid (commission), or the credit was debited (net). |
order.delivered |
The eSIM was sent to the client; the esim object is filled. |
order.failed |
The supplier did not deliver; we step in. |
order.refunded |
Refunded, by you or by us. |
order.cancelled |
Payment link cancelled. |
esim.installed |
The profile is installed on a phone. |
esim.low_data |
A quarter of the data or less. |
esim.used_up |
Out of data. |
esim.expiring |
Validity ends soon. |
esim.expired |
Validity over. |
esim.deleted |
The client removed the profile from the phone. |
esim.suspended |
Data cut. |
esim.resumed |
Data restored. |
topup.delivered |
A top-up was added to the eSIM. |
Errors
Body: { "error": { "code": "…", "message": "…" } }.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized |
Key missing, revoked or unknown. |
| 403 | forbidden |
Wrong key kind, or origin not allowed. |
| 404 | not_found |
Unknown resource or another account’s. |
| 402 | payment_required / insufficient_credit |
Credit too low for a net order. |
| 422 | invalid_request, unknown_plan, invalid_trip, plan_too_short |
Parameter refused; explicit message. |
| 409 | conflict, not_refundable, not_topupable, not_remindable |
The order’s state no longer allows it. |
| 429 | rate_limited |
600 requests a minute per key. |
| 503 | payments_closed |
Payments are not open (live mode). |