ChoEsim Partner API: sandbox guide
What the sandbox is
The sandbox is a separate, simulated copy of the API. You use it with a test key that starts with cesim_test_.
- It has its own catalogue of fake plans. Plan ids start with
test_plan_. - It has its own fake wallet.
- It never reaches an eSIM supplier. It never spends money.
- Test and live data never mix. A test key cannot see live orders. A live key cannot see test orders.
A test key goes through the same checks as a live key: authentication, scopes, validation, idempotency, rate limits, wallet rules and webhooks. Your code can use the same paths in both modes.
Test and live are two different keys. Going live means creating a live key in the partner portal, which is possible after your first top-up has been credited. You do not "switch" a test key.
Responses show livemode: false for test data.
Fund the test wallet
A test wallet starts at zero. Add fake money with POST /sandbox/wallet/topup.
curl -s -X POST "$CESIM_BASE/sandbox/wallet/topup" \
-H "Authorization: Bearer cesim_test_REPLACE_ME" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount_minor": 10000}'
- It needs the
orders:writescope, because it changes a (fake) balance. - It needs an
Idempotency-Key. amount_minoris an integer from 1 to 1,000,000.- The maximum balance is 1,000,000 minor units (USD 10,000.00 of fake money).
- A live key gets
403 forbidden_scope.
Magic values
The sandbox is deterministic. The plan id or the order reference decides what happens. Use these values to test every path.
| You send | What happens |
|---|---|
plan_id: test_plan_ok_1gb_7d | The order is fulfilled immediately. |
plan_id: test_plan_slow_1gb_7d | The order is accepted. It stays in processing for 30 seconds, then it is fulfilled. |
plan_id: test_plan_unknown_1gb_7d | The order is in processing with status_detail: under_review for 120 seconds, then it is fulfilled. |
plan_id: test_plan_fail_1gb_7d | The order fails. The wallet is credited. An order.failed event is sent. |
plan_id: test_plan_oos_1gb_7d | 422 plan_unavailable. |
plan_id: test_plan_price_change_1gb_7d | 409 price_changed on the first attempt for each reference. |
plan_id: test_plan_nocancel_1gb_7d | The order is fulfilled. A cancel call returns 409 esim_not_cancellable. |
reference starts with sbx-429- | 429 rate_limited with Retry-After: 2. |
reference starts with sbx-insufficient- | 402 insufficient_balance. |
The fulfilled test eSIMs have a fake activation string like LPA:1$sandbox.choesim.invalid$TEST-3F9A2C01. It cannot be installed on a phone.
Simulate each path
Success
Order test_plan_ok_1gb_7d. You get 201 with status: fulfilled.
A delay (30 seconds)
Order test_plan_slow_1gb_7d. You get 202, status: processing and a Location header.
curl -i -X POST "$CESIM_BASE/orders" \
-H "Authorization: Bearer cesim_test_REPLACE_ME" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"reference":"sbx-slow-001","items":[{"plan_id":"test_plan_slow_1gb_7d","quantity":1}]}'
Then poll GET /orders/{id} and wait for fulfilled. Honour Retry-After. If you have a webhook endpoint, you also get order.fulfilled.
The state changes with time. It is computed from the order's created_at, so you do not have to keep a connection open.
If you repeat the same request with the same Idempotency-Key after the 30 seconds, you get 201 with Idempotent-Replayed: true. The status code follows the current state of the order.
The unknown-outcome path (120 seconds)
In real life, a supplier call can time out and ChoEsim cannot tell at once whether the eSIM was bought. The order then shows status: processing and status_detail: under_review. The wallet stays debited until the outcome is proven.
Order test_plan_unknown_1gb_7d to practise this. For 120 seconds the order is processing with under_review, then it becomes fulfilled.
What your code must do:
- Treat
processingas "not finished". It is not a failure. - Never create a second order. Poll
GET /orders/{id}, or repeat the same request with the sameIdempotency-Key. - Do not give your customer a refund or a retry until you see
failed. - Expect that a real order can stay in
under_reviewlonger than 120 seconds. In live mode, ChoEsim staff check it. The sandbox gives no timing promise for live mode.
An order.delayed event may be sent for a slow order. It is informational.
A definite failure
Order test_plan_fail_1gb_7d. The order ends as failed, the wallet is credited, and order.failed is sent. Check GET /wallet/entries to see the credit.
A plan that cannot be bought
Order test_plan_oos_1gb_7d. You get 422 plan_unavailable. Nothing is debited.
A price change
Order test_plan_price_change_1gb_7d with expected_total_minor set. The first attempt for a given reference returns 409 price_changed. Nothing is debited. The error body includes expected_total_minor and current_total_minor.
Use it to test this flow: read current_total_minor, decide whether to accept it, then send the order again with the new total. Because the order was refused before any money was taken, nothing is stored for the Idempotency-Key. You may send the corrected request with the same key, or with a new key. Both work.
The 422 idempotency_key_reuse error applies only when a key is already attached to an order that was created and you send a different body with it.
A cancel that is refused
Order test_plan_nocancel_1gb_7d, then call POST /orders/{id}/cancel. You get 409 esim_not_cancellable.
Rate limit
Send an order whose reference starts with sbx-429-. You get 429 rate_limited with Retry-After: 2. Wait two seconds, then retry.
Not enough money
Send an order whose reference starts with sbx-insufficient-. You get 402 insufficient_balance. Nothing is debited.
Idempotency and duplicates
You can test these without magic values:
- Repeat the same order with the same
Idempotency-Keyand body. You get the current state of the same order, withIdempotent-Replayed: true. - Repeat it with the same key and a different body. You get
422 idempotency_key_reuse. - Send a new key but an old
reference. You get409 duplicate_reference. The body includes the existingorder_id.
Webhooks in the sandbox
A test-mode key sends real webhooks to a test-mode endpoint. Register a real HTTPS endpoint with a test key and you receive real, signed requests.
Test endpoints follow the same URL rules as live ones: https, port 443, public address, no redirects. A localhost URL is rejected. For local development, use a tunnel service that gives you a public HTTPS address.
Webhook events from test mode have livemode: false.
Limits of the sandbox
- The sandbox does not test real supplier behaviour. Real delivery time, real supplier errors and real cancellation rules can differ.
- The test catalogue is fake. Plan names, volumes and prices are not real. Do not copy them to your site.
- Test activation strings do not work on a phone.
- Live limits can differ. Your live key has its own order, daily, open-order and cancellation limits. Read
GET /mewith the live key. - Timing is fixed. The slow path is 30 seconds and the unknown path is 120 seconds. Live timing is not fixed.
- Rate limits. The sandbox uses the same rate limiter as live mode. See the API guide for the default figures.
- Usage data.
GET /esims/{id}/usagemay be empty or old, in both modes. Fields arenullwhen unknown.
A real purchase on a live key is a separate process agreed with ChoEsim. Never use the live key to "test".