partner api
Sell global eSIM data under your own brand. You call goAFK, we handle the carrier relationship, and your end users never see a third party.
https://goafk.io/api/v1quickstart
Partner accounts are opened by our team — there is no self-serve signup. Once your account exists you will have an API key and a pre-paid USD balance. Three calls take you from nothing to a working eSIM.
1. Find a plan. 2. Buy it. 3. Give the returned activation code to your end user. That is the whole integration.
curl https://goafk.io/api/v1/plans?country=FR \
-H "Authorization: Bearer $GOAFK_API_KEY"curl -X POST https://goafk.io/api/v1/esims \
-H "Authorization: Bearer $GOAFK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"plan_id":"156057"}'The response carries esim.activation_code — an LPA string. Show it to your user as text or as a QR code and the profile installs. Nothing else is required.
authentication
Every request carries your API key as a bearer token. Keys look like gak_live_… and are shown exactly once, at the moment we create them: we store only a hash, so a lost key cannot be recovered and has to be replaced.
Authorization: Bearer gak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTreat the key like a password. It authorises spending against your balance, so it belongs on your server only — never in a browser, a mobile app, or a public repository. If a key is exposed, tell us and we will revoke it; revocation takes effect on the next request.
A missing, malformed, unknown or revoked key all return the same 401 INVALID_API_KEY. That is deliberate — the error never confirms that a particular key once existed.
balance & pricing
Your account holds a pre-paid balance in USD. Buying an eSIM or a top-up debits it; nothing else does. Every amount in this API is an integer number of cents — there are no floating-point prices to round.
Prices are personal to your account: the catalogue is returned at wholesale plus your contracted markup, so price_usd_cents from GET /plans is exactly what you will be charged. Two partners can see different numbers for the same plan, and that is expected.
The debit is atomic and happens before we ask the carrier for an eSIM. If your balance will not cover the plan you get 402 INSUFFICIENT_FUNDS and nothing is charged. If the debit succeeds but the carrier then refuses, we write a compensating refund automatically and mark the order provider_failed — you are never charged for an eSIM you did not receive.
To add funds, contact your account manager. Top-ups are not yet self-serve.
idempotency
Both spending endpoints — POST /esims and POST /esims/{id}/topup — require an Idempotency-Key header. It is not optional, because a network timeout on a purchase is otherwise indistinguishable from a failure, and retrying blind would charge you twice.
Use any string up to 200 characters; a fresh UUID per logical purchase is the usual choice. Store it before you send the request, so a retry after a crash can reuse it.
Replaying a key returns the original order with Idempotency-Replayed: true and does not charge again. The key is the unit of replay, not the request body: sending a different plan_id under a key you already used returns the first order, it does not buy the second plan. Always use a new key for a genuinely new purchase.
rate limits
Each API key has a requests-per-minute ceiling agreed with your account manager. Every authenticated response reports where you stand:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Your ceiling, in requests per minute. |
X-RateLimit-Remaining | Requests left in the current 60-second window. |
Retry-After | Seconds to wait. Sent only with a 429. |
X-Request-Id | On every response. Quote it in a support ticket and we can find the exact request. |
Over the limit you get 429 RATE_LIMITED. Back off for the Retry-After interval rather than retrying immediately. GET /plans is the call most worth caching: the catalogue changes slowly and it is not paginated.
errors
Every failure returns the same shape, with the HTTP status carrying the category and error.code carrying the specific reason. Branch on the code — the message is written for humans and its wording can change.
{
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Partner balance is insufficient for plan price 4.50 USD",
"request_id": "6f1d0b3e-6c1a-4a4e-9f2a-1c2d3e4f5a6b"
}
}| Status | Code | What it means | What to do |
|---|---|---|---|
| 400 | INVALID_REQUEST | A required field is missing or the wrong type. | Fix the request. Retrying it unchanged will fail the same way. |
| 400 | IDEMPOTENCY_KEY_REQUIRED | A spending operation arrived without an Idempotency-Key header. | Send one. It is required so a retry cannot double-charge you. |
| 400 | IDEMPOTENCY_KEY_TOO_LONG | The Idempotency-Key exceeded 200 characters. | Use a shorter key — a UUID is ideal. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown, or revoked. | Check the Authorization header format. An unknown key and a revoked key are reported identically on purpose, so this error never confirms a key once existed. |
| 402 | INSUFFICIENT_FUNDS | The purchase price exceeds your balance. NOTHING was charged. | Top up your balance with your account manager, then retry. |
| 403 | PARTNER_SUSPENDED | Your account is suspended. Reads and writes are both refused. | Contact your account manager. Retrying will not help. |
| 404 | PLAN_NOT_FOUND | No plan with that id. | Re-fetch the catalogue; plan ids change as the carrier catalogue changes. |
| 404 | ESIM_NOT_FOUND | No eSIM with that id in YOUR account. | Check the id. An eSIM belonging to another partner returns this same 404, so it is never possible to probe for one. |
| 404 | ORDER_NOT_FOUND | No order with that id in your account. | Check the id. |
| 405 | METHOD_NOT_ALLOWED | The HTTP verb is not supported on that path. | Check the reference below for the verb each path accepts. |
| 429 | RATE_LIMITED | You exceeded your requests-per-minute ceiling. | Back off for the Retry-After interval, then retry. |
| 500 | INTERNAL_ERROR | Something failed on our side. | Retry with the SAME Idempotency-Key if the call spends money. Quote the request_id if it persists. |
| 502 | PROVIDER_ERROR | The carrier refused or could not be reached. | For a purchase or top-up your balance has ALREADY been refunded automatically — retry with a NEW Idempotency-Key to try again. |
webhooks
Rather than polling, you can have us POST to your server when something happens on your account. Add an endpoint in the partner dashboard, where you will also find its signing secret and a log of recent deliveries.
Every delivery is a JSON body with the same envelope. Deduplicate on id: a retry re-sends the identical body, so a receiver that has already processed an id should acknowledge and do nothing.
{
"id": "whd_7c1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"event": "esim.purchased",
"created_at": "2026-08-17T09:24:11.482Z",
"data": {
"esim": {
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
},
"order": {
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
}
}Verifying the signature
Anyone can POST to your endpoint. The signature is what proves a delivery came from us — check it before acting on the body, and compare in constant time.
| Header | Meaning |
|---|---|
X-goAFK-Signature | t=<unix seconds>,v1=<hex> — HMAC-SHA256 of `${t}.${rawBody}` using your endpoint secret. |
X-goAFK-Event | The event name, so you can route without parsing the body. |
X-goAFK-Delivery | The delivery id, identical to id in the body. |
Sign the raw request body, not a re-serialized object — re-encoding changes the bytes and the signature will not match. Reject anything whose timestamp is more than 5 minutes from your own clock, which is what stops a captured delivery being replayed later.
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map(p => p.trim().split('=')),
);
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
if (!Number.isFinite(age) || age > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
// Constant time: a plain === leaks the answer one byte at a time.
return parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}Retries
Reply with any 2xx and we stop. Reply with a 5xx, a 408, a 429, or nothing at all, and we retry 6 times with increasing gaps — 1m, 5m, 30m, 2h, 6h, 24h — which spans more than a day in total.
Any other 4xx stops the retries immediately. A 404 or a 401 means the endpoint is misconfigured, and the same request would fail identically six hours later. Acknowledge first and process afterwards: we time out after 10 seconds, and a slow reply costs you a duplicate delivery.
Events
esim.purchased
A POST /esims succeeded and an eSIM was issued.
{
"id": "whd_7c1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"event": "esim.purchased",
"created_at": "2026-08-17T09:24:11.482Z",
"data": {
"esim": {
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
},
"order": {
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
}
}esim.topped_up
A top-up succeeded and the eSIM has more data.
{
"id": "whd_7c1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"event": "esim.topped_up",
"created_at": "2026-08-17T09:24:11.482Z",
"data": {
"esim": {
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
},
"order": {
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
}
}order.failed
We charged you, the carrier refused, and the money was returned automatically.
{
"id": "whd_7c1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"event": "order.failed",
"created_at": "2026-08-17T09:24:11.482Z",
"data": {
"order_id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"error_code": "PROVIDER_ERROR",
"refunded_cents": 1595,
"currency": "USD"
}
}balance.low
A purchase took your balance below your alert threshold. Fires on the crossing only.
{
"id": "whd_7c1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"event": "balance.low",
"created_at": "2026-08-17T09:24:11.482Z",
"data": {
"balance_cents": 640,
"threshold_cents": 1000,
"currency": "USD"
}
}plans
Browse the catalogue at your own pricing.
GET/plans
List available plans
Returns the full catalogue, priced at your markup. This endpoint is NOT paginated — narrow it with the filters below rather than fetching everything on each page load, and cache the result.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
country | query | string | ISO 3166-1 alpha-2 country code. Case-insensitive. |
region | query | string | Regional grouping, matched exactly and case-sensitively. |
curl -X GET https://goafk.io/api/v1/plans \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"plans": [
{
"id": "156057",
"country_code": "FR",
"country_name": "France",
"region": "Europe",
"type": "fixed",
"data_gb": 5,
"duration_days": 30,
"price_usd_cents": 1595,
"currency": "USD"
}
]
}Responses
| Status | Meaning |
|---|---|
| 200 | The catalogue. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 429 | Rate limit exceeded. Retry after the Retry-After interval. |
GET/plans/{id}
Retrieve one plan
Fetch a single plan by id, priced at your markup.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | The plan id. |
curl -X GET https://goafk.io/api/v1/plans/156057 \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"plan": {
"id": "156057",
"country_code": "FR",
"country_name": "France",
"region": "Europe",
"type": "fixed",
"data_gb": 5,
"duration_days": 30,
"price_usd_cents": 1595,
"currency": "USD"
}
}Responses
| Status | Meaning |
|---|---|
| 200 | The plan. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 404 | No plan with that id. |
esims
Issue eSIMs, inspect them, and top them up.
POST/esims
Buy an eSIM
Debits your balance and issues an eSIM. The debit happens FIRST and is atomic: if it fails, nothing else runs. If the debit succeeds but the carrier then refuses, the charge is automatically reversed and the order is recorded as `provider_failed` — you are never charged for an eSIM you did not receive.
An `Idempotency-Key` header is REQUIRED. If a request times out, retry it with the SAME key: the reply is the original order, and no second charge is made.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Idempotency-Keyrequired | header | string | REQUIRED on every operation that spends money. Any string up to 200 characters; a UUID per logical purchase is the usual choice. Replaying a key returns the original order instead of charging again. |
Request body
| Field | Type | Description |
|---|---|---|
plan_id | string | The id of the plan to buy, from GET /plans. |
curl -X POST https://goafk.io/api/v1/esims \
-H "Authorization: Bearer $GOAFK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"plan_id":"156057"}'{
"esim": {
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
},
"order": {
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
}Responses
| Status | Meaning |
|---|---|
| 201 | The eSIM was issued and your balance was debited. On an idempotent replay the status is still 201, with `Idempotency-Replayed: true` set. |
| 400 | plan_id is missing, or the Idempotency-Key header is absent or over 200 characters. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 402 | Your balance will not cover this plan. Nothing was charged. |
| 404 | No plan with that id. |
| 502 | The carrier refused. Your balance has already been refunded. |
GET/esims
List your eSIMs
Your own eSIMs only, newest first. Another partner's inventory is never visible.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
page | query | integer | 1-based page number. Values below 1 are clamped to 1. |
limit | query | integer | Rows per page. Clamped to the 1–100 range. |
curl -X GET https://goafk.io/api/v1/esims \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"esims": [
{
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
}
],
"total": 128,
"page": 1,
"limit": 50
}Responses
| Status | Meaning |
|---|---|
| 200 | A page of eSIMs. |
| 401 | Missing, malformed, unknown or revoked API key. |
GET/esims/{id}
Retrieve one eSIM
Returns cached fields, which is enough to render an installation screen. For live consumption use the usage endpoint instead.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | The goAFK eSIM id (not the ICCID). |
curl -X GET https://goafk.io/api/v1/esims/9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6 \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"esim": {
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
}
}Responses
| Status | Meaning |
|---|---|
| 200 | The eSIM. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 404 | No such eSIM in YOUR account. An id belonging to another partner also returns 404. |
POST/esims/{id}/topup
Top up an eSIM
Adds another plan to an eSIM you already issued, keeping the same ICCID so the end user does not reinstall anything. Charging works exactly as it does for a purchase: atomic debit first, automatic refund if the carrier refuses, and an `Idempotency-Key` header is REQUIRED.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | The goAFK eSIM id to top up. |
Idempotency-Keyrequired | header | string | REQUIRED on every operation that spends money. Any string up to 200 characters; a UUID per logical purchase is the usual choice. Replaying a key returns the original order instead of charging again. |
Request body
| Field | Type | Description |
|---|---|---|
plan_id | string | The plan to add. |
curl -X POST https://goafk.io/api/v1/esims/9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6/topup \
-H "Authorization: Bearer $GOAFK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"plan_id":"156057"}'{
"esim": {
"id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"iccid": "8944478000001234567",
"activation_code": "LPA:1$smdp.example.com$QR-G-5C-1LO-1S9L3JT",
"qr_code_url": "https://cdn.example.com/qr/8944478000001234567.png",
"status": "inactive",
"data_total_mb": 5120,
"data_used_mb": 0,
"expires_at": null,
"plan_id": "156057"
},
"order": {
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
}Responses
| Status | Meaning |
|---|---|
| 201 | The top-up was applied. `order` is null in the rare case the order row could not be re-read afterwards; the top-up itself still succeeded. |
| 400 | plan_id is missing, or the Idempotency-Key header is absent or over 200 characters. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 402 | Your balance will not cover this plan. Nothing was charged. |
| 404 | No such eSIM in your account, or no plan with that id. |
| 502 | The carrier refused the top-up. Your balance has already been refunded. |
GET/esims/{id}/usage
Read live usage
Queries the carrier directly, so this is the authoritative number — and it is slower than the other reads. Poll it on demand, not on a tight loop. The result also refreshes the cached fields on the eSIM.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | The goAFK eSIM id. |
curl -X GET https://goafk.io/api/v1/esims/9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6/usage \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"usage": {
"iccid": "8944478000001234567",
"data_used_mb": 812,
"data_total_mb": 5120,
"status": "active",
"expires_at": "2026-09-16T09:24:11.482Z"
}
}Responses
| Status | Meaning |
|---|---|
| 200 | Live usage. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 404 | No such eSIM in your account. |
| 502 | The carrier could not be reached. Retry; nothing was changed. |
orders
Your billing history.
GET/orders
List your orders
Every charge against your balance, newest first. This is the record to reconcile your own billing against.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
page | query | integer | 1-based page number. Values below 1 are clamped to 1. |
limit | query | integer | Rows per page. Clamped to the 1–100 range. |
curl -X GET https://goafk.io/api/v1/orders \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"orders": [
{
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
],
"total": 128,
"page": 1,
"limit": 50
}Responses
| Status | Meaning |
|---|---|
| 200 | A page of orders. |
| 401 | Missing, malformed, unknown or revoked API key. |
GET/orders/{id}
Retrieve one order
Fetch a single order from your own history.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string | The order id. |
curl -X GET https://goafk.io/api/v1/orders/2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b \
-H "Authorization: Bearer $GOAFK_API_KEY"{
"order": {
"id": "2b7c9d1e-4f60-4a23-9c81-5d6e7f809a1b",
"kind": "purchase",
"price_usd_cents": 1595,
"currency": "USD",
"plan_id": "156057",
"partner_esim_id": "9c1f2a44-3b6e-4c88-a0d1-77e2b3c4d5f6",
"status": "completed",
"created_at": "2026-08-17T09:24:11.482Z"
}
}Responses
| Status | Meaning |
|---|---|
| 200 | The order. |
| 401 | Missing, malformed, unknown or revoked API key. |
| 404 | No such order in your account. |
schemas
The objects returned by the endpoints above. Fields marked optional may be absent; a type ending in | null is always present but may be null.
Plan
A data plan, priced for the calling partner.
| Field | Type | Description |
|---|---|---|
id | string | Pass this as plan_id when buying. |
country_code | string | ISO 3166-1 alpha-2, upper case. |
country_name | string | English country name. |
region | string | null | Regional grouping, or null for a single-country plan. |
type | "fixed" | "unlimited" | 'fixed' has a data cap; 'unlimited' does not, and reports data_gb as null. |
data_gb | number | null | Included data in GB. Null for unlimited plans. |
duration_days | integer | Validity from activation. |
price_usd_cents | integer | What YOUR account is charged, in integer cents — wholesale plus your contracted markup. Two partners can legitimately see different prices for the same plan. |
currency | "USD" | Always USD. |
Esim
An eSIM issued to the calling partner.
| Field | Type | Description |
|---|---|---|
id | string | goAFK identifier. Use this in URLs. |
iccid | string | The SIM serial number. |
activation_code | string | The LPA string to install the profile. Hand it to the end user verbatim, or encode it as a QR code yourself. |
qr_code_url | string | null | Ready-made QR image, when the provider supplies one. |
status | "inactive" | "active" | "expired" | "depleted" | Starts 'inactive' and becomes 'active' on first use. This field is a cached copy; call the usage endpoint for the live value. |
data_total_mb | integer | Total data allowance in MB. |
data_used_mb | integer | Cached consumption in MB. |
expires_at | string | null | Null until the plan is activated. |
plan_id | string | The plan this eSIM was issued from. |
Order
One movement against your balance.
| Field | Type | Description |
|---|---|---|
id | string | |
kind | "purchase" | "topup" | 'purchase' issued a new eSIM; 'topup' added data to an existing one. |
price_usd_cents | integer | What you were charged, in integer cents. |
currency | "USD" | |
plan_id | string | |
partner_esim_id | string | null | The eSIM this order produced or topped up. Null if provisioning failed. |
status | "completed" | "provider_failed" | "refunded" | 'provider_failed' means you were charged, the provider refused, and the money was automatically returned to your balance. The row is kept as an audit trail. |
created_at | string |
Usage
Live consumption, read from the network at request time.
| Field | Type | Description |
|---|---|---|
iccid | string | |
data_used_mb | integer | |
data_total_mb | integer | |
status | "inactive" | "active" | "expired" | "depleted" | |
expires_at | string | null |
Error
| Field | Type | Description |
|---|---|---|
error | object |
support
Questions, a new key, more balance, or a higher rate limit — write to support@goafk.io. Include the request_id from the response if you are reporting a specific failure.