Partner Reseller API
Browse the eSIM catalog, place orders on behalf of your own customers, and receive delivery confirmation from your own backend β no browser session required.
https://triptel.co/api/v1/partner1. Authentication
All endpoints except the token endpoint itself require a Bearer access token in the Authorization header. The long-lived API key you were issued (esim_live_...) is never sent on these calls directly β it is only ever used once, to obtain a token.
1.1 Exchange your API key for an access token
/auth/tokenPOST /api/v1/partner/auth/token
Content-Type: application/json
{
"api_key": "esim_live_1a2b3c4d5e6f7g8h..."
}{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 3600
}- Tokens are valid for 1 hour. There is no refresh token β when a token expires, call this endpoint again with your API key to get a new one (standard OAuth2 Client Credentials style flow).
401if the key is invalid, malformed, or has been revoked.- Rate limited to 10 requests/minute per IP.
1.2 Use the token on every other call
GET /api/v1/partner/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...401if the token is missing, malformed, expired, or was issued for a different purpose β a regular customer login token cannot be used here, and this token cannot be used against the regular customer-facing API either.- If your key is revoked by an admin, tokens already issued for it stop working on their very next use β you don't have to wait out the full hour.
Full flow example
TOKEN=$(curl -s -X POST https://triptel.co/api/v1/partner/auth/token \
-H "Content-Type: application/json" \
-d '{"api_key": "esim_live_..."}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s https://triptel.co/api/v1/partner/me \
-H "Authorization: Bearer $TOKEN"2. Endpoints
All request/response bodies are JSON. Every endpoint below requires the Bearer token from Β§1.2.
/countriesLists all active destination countries. Optional query param search filters by name or ISO2 code.
[
{
"id": 1,
"iso2": "US",
"iso3": "USA",
"name_en": "United States",
"name_bn": null,
"flag_url": "https://...",
"region": null,
"is_popular": true,
"is_active": true
}
]Rate limit: 120/minute.
/countries/{iso2}/productsLists active eSIM data products for a country. iso2 is case-insensitive.
[
{
"id": "a1b2c3d4-...",
"country_id": 1,
"title_en": "1GB / 7 Days",
"title_bn": null,
"data_amount_gb": 1.0,
"validity_days": 7,
"retail_price": 4.50,
"currency": "USD",
"is_unlimited": false,
"is_active": true,
"coverage_networks": null
}
]404 if the country ISO2 doesn't exist. Rate limit: 120/minute.
/meYour merchant profile, effective commission rate, and current wallet balance.
{
"business_name": "Acme Travel Co",
"commission_rate_pct": 5.00,
"wallet_balance": 142.5000,
"wallet_currency": "USD",
"webhook_configured": true
}Reflects any active volume-based commission tier, falling back to your flat rate. Rate limit: 120/minute.
/ordersPlaces an order and provisions an eSIM asynchronously. Your merchant wallet is debited the product's retail_price immediately; the order returns in PROCESSING status right away β poll GET /orders/{id} or wait for the webhook (Β§3) to find out whether it completed.
{
"product_id": "a1b2c3d4-...",
"customer_reference": "your-internal-order-id-123"
}customer_reference is optional β echoed back in every response and webhook so you can reconcile without storing our order_id first.
{
"order_id": "e5f6a7b8-...",
"order_no": "PTR-20260904-A1B2C3",
"status": "PROCESSING"
}What happens after PROCESSING:
- Success β order becomes
COMPLETED,iccid/lpa_string/qr_code_dataare populated, and commission is credited to your account. - Failure β order becomes
FAILED, no eSIM is issued, and the full amount is refunded to your wallet automatically. No commission is paid on a failed order.
400 insufficient wallet balance (no order created) Β· 404 product not found/inactive Β· rate limit 30/minute.
/orders/{order_id}Polls the current state of an order β the fallback if you're not using webhooks.
{
"order_id": "e5f6a7b8-...",
"order_no": "PTR-20260904-A1B2C3",
"customer_reference": "your-internal-order-id-123",
"status": "PROCESSING",
"iccid": null,
"lpa_string": null,
"qr_code_data": null,
"is_mock_fallback": null,
"created_at": "2026-09-04T12:00:00Z"
}status is one of PROCESSING, COMPLETED, FAILED. 404 if the order doesn't exist or isn't yours. Rate limit: 120/minute.
/webhook-configSets, rotates, or clears your webhook endpoint. Self-service β no admin involvement needed.
{ "webhook_url": "https://yourapp.example.com/webhooks/esim" }{
"webhook_url": "https://yourapp.example.com/webhooks/esim",
"webhook_secret": "whsec_...",
"warning": "Store this secret securely - it will not be shown again in full."
}A new webhook_secret is generated every time you set or change the URL β save it immediately, it verifies webhook signatures (Β§3) and is never shown again.
Send {"webhook_url": null} to clear your webhook entirely (you'll only be able to poll after that):
{ "webhook_url": null, "webhook_secret": null }Rate limit: 10/minute.
3. Webhooks
If you have a webhook_url configured, we POST to it once your order reaches a terminal state (COMPLETED or FAILED).
Delivery attempts: one immediately when the order finishes processing; if that fails (non-2xx response, timeout, connection error), a retry sweep runs every 5 minutes and tries again, up to 3 attempts total. There's no dead-letter queue after that β poll GET /orders/{id} if you suspect a delivery was missed.
Payload
{
"order_id": "e5f6a7b8-...",
"customer_reference": "your-internal-order-id-123",
"status": "COMPLETED",
"iccid": "8988212345678901234",
"lpa_string": "LPA:1$rsp.truphone.com$MATCHID123",
"qr_code_data": "LPA:1$rsp.truphone.com$MATCHID123",
"is_mock_fallback": false
}On a FAILED order, the eSIM fields are all null β check status first.
Signature verification
Every delivery includes an X-Webhook-Signature header: the HMAC-SHA256 of the exact raw request body, hex-encoded, using your webhook_secret.
import hmac, hashlib
def verify_webhook(raw_body: bytes, signature_header: str, webhook_secret: str) -> bool:
expected = hmac.new(webhook_secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)Always verify against the raw bytes of the request body, before any JSON parsing β re-serializing and re-hashing a parsed-then-reserialized payload is not guaranteed to match byte-for-byte.
4. Rate Limits
| Endpoint | Limit |
|---|---|
POST /auth/token | 10/minute (per IP) |
POST /orders | 30/minute (per API key) |
PUT /webhook-config | 10/minute (per API key) |
| Everything else | 120/minute (per API key) |
A 429 response means you've hit the limit β back off and retry after a short delay. Limits are keyed to your specific API key (via the token), not your IP, except for the token endpoint itself (which has no partner identity yet).
5. Errors
Standard HTTP status codes; error detail is always in the JSON body's detail field:
{ "detail": "Insufficient wallet balance. Please top up." }| Status | Meaning |
|---|---|
| 400 | Bad request β invalid input or a business rule violation |
| 401 | Missing/invalid/expired token, or invalid/revoked API key at the token endpoint |
| 404 | Resource not found (deliberately indistinguishable from "not yours") |
| 429 | Rate limit exceeded |
| 502 | Upstream provisioning failure (rare from your side β provisioning happens after POST /orders already returned) |
6. Getting API Access
Key issuance and revocation are Super-Admin-only actions performed from the internal admin console β there is no self-service signup. If you're a merchant looking to integrate programmatically, contact your account admin once your partner application is approved.
If you suspect your key has leaked, ask for immediate revocation β any access tokens already issued from it stop working on their next use, not just after they naturally expire.
Not a partner yet? Apply to the B2B Reseller Program