Install
openclaw skills install esimpal-apiUse when building or debugging an agent (e.g. Telegram/WhatsApp bot, AI assistant) that integrates with the eSIMPal API to buy eSIMs for end-users, create or...
openclaw skills install esimpal-apiUse this skill when implementing or testing an agent that uses the eSIMPal API to buy eSIMs for end-users (list plans, create orders, process payments, activate, and deliver).
POST /v1/orders and POST /v1/orders/{orderId}/pay as high-risk operations and never run them silently.POST /v1/orders/{orderId}/packages/{packageId}/activate/new and POST /v1/orders/{orderId}/packages/{packageId}/activate/existing as approval-gated operations (activation can be irreversible and may consume inventory).orders:read, orders:write) and rotate keys if exposure is suspected.ESIMPAL_API_KEY is missing, stop and return a credentials error. Do not continue.POST /v1/orders and POST /v1/orders/{orderId}/pay, if explicit user confirmation is missing in the current conversation, refuse to execute.POST /v1/orders/{orderId}/packages/{packageId}/activate/new and POST /v1/orders/{orderId}/packages/{packageId}/activate/existing, if explicit user confirmation is missing in the current conversation, refuse to execute.https://getesimpal.com/api (or the env override the user provides, always ending in /api).GET https://getesimpal.com/api/v1/plans?country=TR&min_data_gb=1Authorization: Bearer ${ESIMPAL_API_KEY}.ESIMPAL_API_KEY; do not hardcode. Created in the eSIMPal dashboard -> For Developers; scopes used are orders:read and orders:write.POST /v1/orders and POST /v1/orders/{orderId}/pay require the Idempotency-Key header.
order_id back.Examples:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 # new order
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 # retry -> same order returned
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7 # different order
List plans - GET /v1/plans?country={ISO2}&min_data_gb={number}
Returns { plans: [...] }. Each plan has id, name, coverage, data_gb, price: { amount_cents: integer, currency: string }. Use plan_id when creating the order.
Create order - POST /v1/orders
Content-Type: application/json, Idempotency-Key: <uuid> (required).{ "plan_id": "<from step 1>", "quantity": 1, "customer_email": "optional", "customer_ref": "optional e.g. telegram:123" }.{ order_id, status, total_amount_cents, currency, plan_id, quantity, customer_email, customer_ref, expires_at, esims }. Store order_id.(Optional) Change currency - PATCH /v1/orders/{orderId}
{ "currency": "EUR" } (3-letter ISO code, case-insensitive).created status (before payment is initiated).total_price.Start payment - POST /v1/orders/{orderId}/pay
Idempotency-Key: <uuid> (required). Use a new UUID per new payment attempt.{ status, checkout_url, expires_at }. Send checkout_url to the user so they can pay in the browser.Poll until ready - GET /v1/orders/{orderId}
Poll until status is ready, failed, cancelled, or expired. When status === "ready", the order is paid and provisioned; esims is populated.
Recommended polling strategy:
Retry-After, respect that value instead.Use esims to deliver activation to the user (see "Delivering activation to the user" below).
POST /v1/orders/{orderId}/cancel - cancels a pending order.created or payment_pending status.{ order_id, status: "cancelled" }.Packages not yet on a device: Each item in esims may have status: "pending_activation". Then either:
activation_options.requires_user_choice === true (or GET /v1/orders/{orderId}/profiles returns one or more profiles), the agent must ask the user which path to use before activating:
POST /v1/orders/{orderId}/packages/{packageId}/activate/new (no body). Creates a new eSIM profile and assigns the package. Response includes activation links and qr_code_url.GET /v1/orders/{orderId}/profiles to list the customer's devices; then POST /v1/orders/{orderId}/packages/{packageId}/activate/existing with body { "esim_profile_id": "<profile id from profiles>" }.Already activated: If esims[i].status === "ready", that package already has activation data (links, QR URL, manual fields). Use it directly.
From the order (when status === "ready") or from the activate/new or activate/existing response, use:
package_id, profile_id, esim_profile_id, order UUIDs) in end-user messages by default.| Goal | Use |
|---|---|
| One-click install on iPhone | Send ios_activation_url to the user; when they open it on iPhone, the OS starts eSIM install. |
| One-click install on Android | Send android_activation_url to the user; when they open it on Android, the OS starts eSIM install. |
| Send a QR image | qr_code_url is a URL. GET it with Authorization: Bearer ${ESIMPAL_API_KEY}; response is image/png. Upload/send that image (e.g. Telegram sendPhoto). Do not cache - QR URLs are short-lived. |
| Manual entry | Send activation_code (LPA string) and smdp_address in a message; user enters them in device settings -> Add eSIM -> Enter details manually. |
| Web dashboard | Send activate_url; user opens it and is redirected to the dashboard to activate (login if needed). |
All of the above are optional and nullable; prefer one method (e.g. one link per platform or QR) and fall back to manual if needed.
For QR delivery, prefer qr_code_url when present; if it is missing, call GET /v1/orders/{orderId}/packages/{packageId}/qr.
All paths are relative to the base URL (https://getesimpal.com/api).
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/plans?country=&min_data_gb= | (read) | List plans. Price is { amount_cents, currency }. |
| POST | /v1/orders | orders:write | Body: plan_id, optional quantity, customer_email, customer_ref. Header: Idempotency-Key (UUID). |
| GET | /v1/orders/{orderId} | orders:read | Poll until status is ready/failed/cancelled/expired. When ready, esims has packages. |
| PATCH | /v1/orders/{orderId} | orders:write | Body: { "currency": "EUR" }. Change currency before payment. |
| POST | /v1/orders/{orderId}/cancel | orders:write | Cancel an unpaid order. |
| POST | /v1/orders/{orderId}/pay | orders:write | Header: Idempotency-Key (UUID). Returns checkout_url. |
| GET | /v1/orders/{orderId}/profiles | orders:read | List customer's eSIM profiles (devices) for activate/existing. |
| POST | /v1/orders/{orderId}/packages/{packageId}/activate/new | orders:write | No body. Creates a new eSIM profile for this package. |
| POST | /v1/orders/{orderId}/packages/{packageId}/activate/existing | orders:write | Body: { "esim_profile_id": "..." }. Adds package to existing device. |
| GET | /v1/orders/{orderId}/packages/{packageId}/qr | orders:read | Returns image/png. Do not cache (Cache-Control: no-store). |
Retry-After, wait that many seconds before retrying. Otherwise back off exponentially (2s -> 4s -> 8s).{ code, message, retryable }. If retryable === true, retry with exponential backoff up to 3 attempts.Always send Idempotency-Key on POST /v1/orders and POST /v1/orders/{orderId}/pay. Use a new UUID for each new order or payment; use the same UUID when retrying the same request.
{
"order_id": "uuid",
"status": "ready",
"total_amount_cents": 800,
"currency": "USD",
"plan_id": "uuid",
"quantity": 1,
"customer_email": "user@example.com",
"customer_ref": "telegram:123",
"expires_at": "2026-03-08T12:00:00Z",
"esims": [...]
}
esims{
"package_id": "uuid",
"status": "ready",
"activation_code": "LPA:1$smdp.example.com$MATCHING-ID",
"smdp_address": "smdp.example.com",
"ios_activation_url": "https://...",
"android_activation_url": "https://...",
"activate_url": "https://...",
"qr_code_url": "https://...",
"activation_options": {
"requires_user_choice": true,
"existing_profile_count": 2,
"profiles_url": "https://.../v1/orders/{orderId}/profiles",
"activate_new_url": "https://.../activate/new",
"activate_existing_url": "https://.../activate/existing"
}
}
All activation fields are nullable. status is either "ready" (activation data available) or "pending_activation" (call activate/new or activate/existing first). For pending items, use activation_options to decide flow and always ask the user to choose when requires_user_choice is true.