---
title: Partner API
description: 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.
---

# Partner API

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

```bash
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](https://terudo.com/developers/docs/api-reference.md); 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

```bash
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": "marie@example.com", "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](https://terudo.com/developers/docs/testing.md).
