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=,v1=<hex HMAC-SHA256 of "t.body" with the endpoint secret>. Verify the signature and reject a timestamp older than 5 minutes. Failures are retried after 1, 5, 30, 120 and 720 minutes; 25 failures in a row switch the endpoint off, to be re-enabled from the portal or the API. Body: { id, type, created_at, mode, data: { order } }.

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).