Cuppa Full API — Integration Guide

Overview

The Cuppa Full API lets you sell and manage travel eSIMs entirely inside your own website, app or booking system. You build the plan selector and own the customer journey; Cuppa supplies the plans, bills your business, provisions each eSIM and delivers it.

You buildCuppa handles
The plan selector, cart and checkoutThe catalogue of destinations and plans
Collecting the traveller's paymentBilling your business the wholesale price
One server call after the booking is paidBuying, provisioning and delivering the eSIM
Optional: your own eSIM management screensUsage, top-ups, suspension and cancellation

Your checkout stays yours. Cuppa never collects the traveller's payment, never receives their card details and does not require you to adopt a payment processor.

If you would rather not build a plan selector, use the Low-Code Embed. Both use the same POST /v1/fulfilments call.

How it works

  1. Your server lists the plans available for the traveller's destination.
  2. You show those plans in your own interface at your retail price.
  3. The traveller selects a plan and pays through your existing checkout.
  4. Once the booking is paid, your server calls POST /v1/fulfilments.
  5. Cuppa bills your business, provisions the eSIM, and either emails it to the traveller or hands you the installation details.

Only step 4 is required. Listing plans, tracking status and managing eSIMs afterwards are there when you need them.

Before you begin

Set up three things in your Cuppa account:

  • The plans you want to sell and your retail price for each
  • A company card on file, or a prepaid credit balance
  • Your branding for the traveller's activation email, if Cuppa will send it

During early pilots, Cuppa can configure these for you.

EnvironmentKeyBehaviour
Testsk_test_EXAMPLESimulated billing and provisioning. No real charge, no real eSIM
Livesk_live_EXAMPLEReal billing and real eSIMs

Base URL: https://api.cuppa.so/v1

Every request is HTTPS and JSON, and carries Authorization: Bearer <secret key>. Call the API from your server only; never place a secret key in browser or mobile app code. Amounts are integers in the currency's minor unit (1000 = $10.00).

Step 1: List regions and plans

Ask Cuppa which destinations you can sell, then fetch the plans for one of them.

curl 'https://api.cuppa.so/v1/regions' \
  -H 'Authorization: Bearer sk_test_EXAMPLE'
{
  "data": [
    { "code": "JP", "name": "Japan", "type": "country" },
    { "code": "EU", "name": "Europe", "type": "region", "countries": ["FR", "DE", "ES", "IT"] }
  ]
}
curl 'https://api.cuppa.so/v1/offers?destination=JP' \
  -H 'Authorization: Bearer sk_test_EXAMPLE'
{
  "data": [
    {
      "offerId": "offer_jp_1gb_7d",
      "destination": "JP",
      "name": "Japan 1 GB · 7 days",
      "dataMb": 1024,
      "validityDays": 7,
      "currency": "USD",
      "retailAmountMinor": 1000,
      "wholesaleAmountMinor": 400,
      "supportsTopUp": true,
      "supportsSms": false
    }
  ]
}
  • retailAmountMinor is the price you set in your Cuppa account and charge the traveller.
  • wholesaleAmountMinor is what Cuppa bills your business for each eSIM.
  • GET /v1/offers/{offerId} returns one plan, for when you only need to confirm its price.

Plans and prices change rarely. You may cache these responses for up to an hour rather than calling on every page view.

Step 2: Sell the plan in your own checkout

Show the plans however suits your product, add the chosen one to your cart at retailAmountMinor, and store its offerId against the booking. Then take payment exactly as you do today.

Prices are fixed. There is nothing to quote, lock or reserve before the traveller pays.

Have your server decide the amount for the eSIM line from the offer, as it does for your other extras, rather than trusting a price sent by the browser.

Step 3: Request the eSIM after payment

Once your own backend has confirmed the booking is paid, tell Cuppa. This is the only call you must make.

Option A: Cuppa emails the traveller

curl -X POST 'https://api.cuppa.so/v1/fulfilments' \
  -H 'Authorization: Bearer sk_test_EXAMPLE' \
  -H 'Content-Type: application/json' \
  -d '{
    "orderId": "BK-104233",
    "itemId": "esim-1",
    "offerId": "offer_jp_1gb_7d",
    "customerEmail": "traveller@example.com",
    "customerName": "Example Traveller"
  }'

Cuppa sends an activation email with a QR code, carrying your name, logo, colours and wording.

Option B: You deliver the eSIM yourself

Use this to send the eSIM in your own booking email or app, without sharing the traveller's name or email with Cuppa.

{
  "orderId": "BK-104233",
  "itemId": "esim-1",
  "offerId": "offer_jp_1gb_7d",
  "delivery": "partner_api"
}

Cuppa does not email the traveller. You retrieve the installation details in Step 4.

The response

Both options return 202 Accepted:

{
  "fulfilmentId": "ful_3Xa9Lw7c",
  "status": "processing",
  "orderId": "BK-104233",
  "itemId": "esim-1",
  "offerId": "offer_jp_1gb_7d",
  "wholesaleAmountMinor": 400,
  "currency": "USD"
}
  • Call from your server, at the point you know you have been paid. That is usually your payment processor's server-side confirmation. Never trigger it from a browser success page, or when the traveller merely clicks Pay.
  • Retries are safe. orderId is your booking reference and itemId names one eSIM within it. Sending the same pair again returns the existing fulfilment; it never bills or provisions twice.
  • A timeout is not a failure. If a request times out, send it again with the same orderId and itemId. Never change them to force a new attempt.
  • Several eSIMs on one booking. Make one call per eSIM, each with its own itemId.
  • 202 is an acknowledgement, not delivery. A fulfilment is one eSIM, from this request until the plan expires.

Step 4: Track the outcome and deliver

Most fulfilments finish within seconds. Read one at any time with GET /v1/fulfilments/{fulfilmentId}.

{
  "fulfilmentId": "ful_3Xa9Lw7c",
  "status": "delivered",
  "orderId": "BK-104233",
  "itemId": "esim-1",
  "offerId": "offer_jp_1gb_7d",
  "billing": { "status": "charged", "amountMinor": 400, "currency": "USD" },
  "esim": { "iccid": "8910300000000000000", "state": "not_installed" }
}
StatusMeaningWhat you do
processingCuppa is billing your account and provisioning the eSIMNothing
provisionedThe eSIM is ready. Final state for partner_api deliveryRetrieve the installation details and send them
deliveredCuppa emailed the activation details to the travellerNothing
action_requiredYour card was declined or your credits ran out. No eSIM has been boughtUpdate your card or top up; Cuppa then resumes automatically
failedCuppa could not provision the plan and has reversed the wholesale chargeRefund the traveller's eSIM line, or offer another plan

billing.status is pending, charged, refunded or not_charged.

If provisioning fails

  • Cuppa retries first. A provisioning error is retried automatically before a fulfilment is marked failed, and a retry can never buy a second eSIM.
  • Your money is returned automatically. On failed, Cuppa refunds the wholesale charge to your card or restores your credits.
  • failed is final. To try again or offer a different plan, send a new itemId.

Installation details for your own delivery

Once a partner_api fulfilment is provisioned:

curl 'https://api.cuppa.so/v1/fulfilments/ful_3Xa9Lw7c/install' \
  -H 'Authorization: Bearer sk_test_EXAMPLE'
{
  "iccid": "8910300000000000000",
  "smdpAddress": "rsp.example.com",
  "activationCode": "EXAMPLE_NONFUNCTIONAL_CODE",
  "lpaString": "LPA:1$rsp.example.com$EXAMPLE_NONFUNCTIONAL_CODE",
  "qrPayload": "LPA:1$rsp.example.com$EXAMPLE_NONFUNCTIONAL_CODE"
}

Render qrPayload as a QR code for the traveller to scan. These values are credentials: restrict access, and never put them in logs, analytics or URLs.

Webhooks

To be told about changes instead of polling, register one HTTPS endpoint.

curl -X PUT 'https://api.cuppa.so/v1/webhook' \
  -H 'Authorization: Bearer sk_test_EXAMPLE' \
  -H 'Content-Type: application/json' \
  -d '{ "url": "https://book.example.com/hooks/cuppa" }'

The response includes a signingSecret, shown once.

EventSent when
fulfilment.provisionedThe eSIM is ready for your own delivery
fulfilment.deliveredThe activation email has been sent
fulfilment.action_requiredBilling your business failed
fulfilment.failedProvisioning failed and the charge was reversed
esim.data_lowThe traveller is close to using all their data
esim.expiringThe plan is about to expire
esim.state_changedThe eSIM was installed, suspended, cancelled, revoked or expired
{
  "type": "fulfilment.delivered",
  "createdAt": "2026-12-01T10:02:11Z",
  "data": { "fulfilmentId": "ful_3Xa9Lw7c", "orderId": "BK-104233", "itemId": "esim-1", "status": "delivered" }
}

Each request carries a Cuppa-Signature header, an HMAC-SHA256 of the raw body using your signing secret. Verify it before acting, reply 2xx promptly, and expect the occasional duplicate.

Manage eSIMs after delivery

Every action below applies to one fulfilment, and only to fulfilments on your own account. Not every plan supports every action; an unsupported one returns not_supported.

ActionRequestEffect
List eSIMsGET /v1/fulfilments?orderId=BK-104233Your fulfilments, newest first. Filter by orderId, status or esimState; page with cursor
Check usageGET /v1/fulfilments/{id}/usageData used, data remaining and expiry
Top upPOST /v1/fulfilments/{id}/topupsAdds data to an active eSIM. Billed to your business at the top-up's wholesale price
SuspendPOST /v1/fulfilments/{id}/suspendPauses data service. Reversible
UnsuspendPOST /v1/fulfilments/{id}/unsuspendRestores a suspended eSIM
CancelPOST /v1/fulfilments/{id}/cancelCancels an eSIM that has not been installed, and refunds the wholesale charge
RevokePOST /v1/fulfilments/{id}/revokePermanently disables the eSIM. Irreversible; no refund
Send SMSPOST /v1/fulfilments/{id}/smsSends a text message to the eSIM, on plans where supportsSms is true

eSIM states

esim.state is not_installed, active, suspended, cancelled, revoked or expired.

Usage

{
  "fulfilmentId": "ful_3Xa9Lw7c",
  "state": "active",
  "dataUsedMb": 412,
  "dataRemainingMb": 612,
  "expiresAt": "2026-12-08T10:02:11Z"
}

Usage is reported by the mobile network and can lag real use by up to a few hours.

Top up

List the top-ups a plan allows with GET /v1/offers/{offerId}/topups, take payment from the traveller in your own checkout, then:

{
  "topupOfferId": "topup_jp_1gb",
  "topupId": "topup-1"
}

topupId is your own reference. As with itemId, repeating it returns the existing top-up and never bills twice. Top-ups follow the same statuses as a fulfilment.

Cancel and revoke

  • Cancel is for an eSIM the traveller will not use, typically after a booking is cancelled. It is only possible while esim.state is not_installed. Cuppa refunds the wholesale charge; refunding the traveller is yours to do.
  • Revoke is for fraud or misuse. It cannot be undone and is never refunded.

Send SMS

{ "message": "Your rental car is ready at Bay 14." }

Messages go only to the eSIM on that fulfilment, up to 160 characters.

Pricing and billing

Every plan has two fixed prices. There is no dynamic pricing and nothing to quote.

PriceSet byExample
Retail priceYou, in your Cuppa account$10.00, charged by you to the traveller
Wholesale priceCuppa$4.00, billed to your business for each eSIM fulfilled

In this example you keep $6.00 per eSIM. Each plan you sell is set up in one of two ways:

  • Paid add-on. The traveller pays your retail price, which must be at or above the wholesale price.
  • Included eSIM. You fund the eSIM as part of the booking. The traveller pays $0 and Cuppa bills you the wholesale price. The retail minimum does not apply. Ask your Cuppa contact to enable this for a plan.

The wholesale price never rises without 30 days' written notice. You are billed the wholesale price in force when you call POST /v1/fulfilments, and the response states the amount.

How Cuppa bills you

  • Pay as you go. Cuppa charges your saved company card for each eSIM or top-up before provisioning it. No upfront cost.
  • Prepaid credits. Cuppa deducts each eSIM or top-up from your credit balance, with no card charge per order.

Cuppa never buys an eSIM before your business has been billed, and never bills twice for one orderId and itemId. A declined card or empty balance pauses the fulfilment as action_required; it does not affect the traveller's payment to you.

Check your balance

curl 'https://api.cuppa.so/v1/balance' \
  -H 'Authorization: Bearer sk_test_EXAMPLE'
{
  "billingMethod": "prepaid_credits",
  "availableAmountMinor": 48200,
  "currency": "USD",
  "fulfilmentsAwaitingPayment": 0
}

Reference

Endpoints

OperationMethod and path
Supported regionsGET /v1/regions
Get all data packagesGET /v1/offers?destination={code}
Plan price requestGET /v1/offers/{offerId}
Order an eSIMPOST /v1/fulfilments
Query allocated eSIMsGET /v1/fulfilments and GET /v1/fulfilments/{id}
Installation detailsGET /v1/fulfilments/{id}/install
Usage checkGET /v1/fulfilments/{id}/usage
Top upGET /v1/offers/{offerId}/topups, POST /v1/fulfilments/{id}/topups
SuspendPOST /v1/fulfilments/{id}/suspend
UnsuspendPOST /v1/fulfilments/{id}/unsuspend
CancelPOST /v1/fulfilments/{id}/cancel
RevokePOST /v1/fulfilments/{id}/revoke
Send SMSPOST /v1/fulfilments/{id}/sms
Balance queryGET /v1/balance
Set webhookPUT /v1/webhook

POST /v1/fulfilments body

FieldRequiredDescription
orderIdYesYour booking reference. Shown on your Cuppa statement
itemIdYesYour name for this eSIM within the booking, e.g. esim-1. orderId and itemId together must be unique on your account
offerIdYesThe plan the traveller selected
deliveryNocuppa_email (default) or partner_api
customerEmailFor cuppa_emailWhere the activation email is sent
customerNameNoUsed to address the email

Errors

Errors return a non-2xx status and a body of the form { "error": { "code": "offer_not_found", "message": "..." } }.

HTTPCodeMeaning
401invalid_keyMissing, wrong or revoked secret key
403access_deniedYour account is not enabled for this operation
404offer_not_foundNo such plan on your account, or it is no longer sold
404fulfilment_not_foundNo such fulfilment on your account
409item_conflictThis orderId and itemId already have a fulfilment for a different plan
409invalid_stateThe eSIM is not in a state that allows this action, e.g. cancelling an installed eSIM
422invalid_requestA required field is missing or malformed
422not_supportedThis plan does not support the action, e.g. SMS or top-up
429rate_limitedToo many requests. Retry after the Retry-After interval
5xxserver_errorRetry the same request with the same orderId and itemId

A declined company card is not an API error. The request is accepted and the fulfilment moves to action_required.

Test mode and go-live checklist

Build against your sk_test_... key. In test mode there is no card charge and no real eSIM; installation details are non-functional. Cuppa sends a clearly labelled TEST activation email to authorised test recipients only. Switch to your sk_live_... key once Cuppa activates your production account.

  • Plans and prices shown to travellers come from GET /v1/offers
  • The offerId is stored against the booking
  • Your server, not the browser, decides the amount charged for the eSIM line
  • Only bookings confirmed paid by your server trigger the fulfilment call
  • Repeated payment callbacks and timeouts resend the same orderId and itemId
  • A failed fulfilment leads to a refund of the traveller's eSIM line
  • action_required reaches someone who can fix your billing
  • For partner_api delivery, installation details are kept out of logs and URLs
  • Webhook signatures are verified, if you use webhooks
  • A cancelled booking cancels its eSIM, where it has not been installed
  • The secret key exists only on your server