{"openapi":"3.1.0","info":{"title":"Nesoraa API","version":"1.0.0","summary":"Programmable connectivity infrastructure.","description":"Nesoraa provisions eSIMs through a provider-independent API.\n\n## Authentication\nEvery request carries `Authorization: Bearer <api key>`. A key belongs to\nexactly one project and one environment, which is why no request takes a\nproject or environment parameter — the credential decides both.\n\n## Environments\nA key is `test` or `live`. They are separate worlds: a test key cannot see\nlive eSIMs, cannot reserve live inventory, and cannot resolve a plan that is\nonly mapped for live.\n\n⚠️ Test mode models Nesoraa behaviour. It does NOT simulate a mobile network:\na test eSIM is not installable on a device, because no upstream network was\nasked to create one.\n\n## Idempotency\nEvery request that spends money requires an `Idempotency-Key` header.\nRetrying with the same key returns the original result forever — it never\nbuys a second eSIM. This is durable, stored alongside the purchase rather\nthan cached, so a restart on either side cannot lose the protection.\n\n## Asynchrony\n`POST /v1/esims` returns **202**, not 201. Nesoraa has taken payment and\nreserved an eSIM; the upstream network has not been contacted yet. Poll the\neSIM or its operation, or wait for a webhook. An eSIM is installable when its\n`activation` object appears.\n\n## Rate limits\nLimits protect the platform, not your plan — they are generous relative to\nany normal integration and a correct client should never see one.\n\nEvery response carries `RateLimit-Limit`, `RateLimit-Remaining` and\n`RateLimit-Reset`, so you can pace a client without ever hitting a **429**.\nWhen you do, `Retry-After` is a whole number of seconds and is never zero;\nthe request was **not** performed, so retrying after that interval is safe.\n\nBudgets are per API key, and also per project and per organization, so one\nnoisy key cannot exhaust a colleague's. `test` and `live` hold separate\nbudgets. Requests that spend money or reach a mobile network — creating an\neSIM, sending a message — have a much smaller budget than reads.\n\n## Errors\nEvery failure carries a stable `type` to switch on, a specific `code`, a\nhuman `message`, and the `request_id` to quote to support.\n\n## Webhooks\nRegister an endpoint and Nesoraa POSTs every event it produces to it. The\nbody is the same `event` object `GET /v1/events` returns, so a receiver and\na poller parse the same shape.\n\n### Verifying a delivery\nEvery delivery carries a `Nesoraa-Signature` header:\n\n```\nNesoraa-Signature: t=1785499331,v1=6f3a…\n```\n\n`t` is the Unix timestamp in **seconds**. Each `v1` is\nHMAC-SHA256 over the exact string `` `${t}.${rawBody}` ``, keyed with your\nendpoint's signing secret, hex encoded.\n\n⚠️ Sign the **raw bytes you received**, not a re-serialization of the parsed\nJSON. Key order, whitespace and unicode escaping all differ between the two,\nand the signature will not match.\n\n⚠️ Compare in constant time, and reject a delivery whose timestamp is more\nthan **300 seconds** old. The timestamp is inside the\nHMAC precisely so you can: signing the body alone produces a token that is\nvalid forever, and anyone who captures one delivery could replay it\nindefinitely.\n\n⚠️ **Expect more than one `v1` during a secret rotation**, and\naccept the delivery if **any** of them matches. Rotation emits one signature\nper currently-valid secret so that both the old and the new receiver verify\nduring the overlap window — swapping atomically is not rotation, it is a\nscheduled outage. Match on the `v1` prefix rather than on the header's shape,\nso a future `v2=` alongside does not break a receiver written today.\n\n### Delivery and retries\nDelivery is at-least-once: a receiver must be idempotent on `event.id`. A\nnon-2xx response or a timeout is retried with exponential backoff for a\nbounded number of attempts; a delivery that exhausts them is kept and can be\nreplayed from the portal or through\n`POST /v1/webhook_deliveries/{id}/replay`. Repeated failure disables the\nendpoint rather than retrying forever, and the reason is readable on the\nendpoint itself.\n\n⚠️ Return 2xx as soon as you have durably stored the event. Doing your\nprocessing before responding is what turns a slow consumer into a retry\nstorm.\n\n### The secret\nIt is shown **once**, when the endpoint is created or its secret rotated,\nand is stored encrypted — it cannot be read back through the API or the\nportal, only replaced."},"servers":[{"url":"/api/platform","description":"Nesoraa Public API"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Plans","description":"The catalogue."},{"name":"eSIMs","description":"Provisioned connectivity."},{"name":"Operations","description":"Asynchronous work, and how it ended."},{"name":"Wallet","description":"Balance and the immutable ledger."},{"name":"Messages","description":"SMS to a provisioned eSIM."},{"name":"Events","description":"Everything that has happened."},{"name":"Webhooks","description":"Delivery of events to your endpoint."},{"name":"API keys","description":"Credential lifecycle."}],"paths":{"/v1/countries":{"get":{"tags":["Plans"],"operationId":"listCountries","summary":"List countries and whether they are purchasable","description":"Where Nesoraa can carry traffic. ⚠️ `availability` is derived from the plan catalogue, not from network coverage: a country is only `available` when an active plan covers it. `coming_soon` means we can reach it but no plan exists yet, and `unsupported` means we cannot serve it. Do not treat a listed country as buyable — check `availability`, then find a plan in /v1/plans.","x-required-scopes":["catalog:read"],"responses":{"200":{"description":"A page of countries.","content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more","next_cursor"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Country"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/plans":{"get":{"tags":["Plans"],"operationId":"listPlans","summary":"List purchasable plans","description":"The catalogue as this project may buy it. Prices are what you pay per eSIM. A plan reads the same in both environments; what differs is whether it can be provisioned, which surfaces at purchase.","x-required-scopes":["catalog:read"],"responses":{"200":{"description":"A page of plans.","content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more","next_cursor"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Plan"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims":{"get":{"tags":["eSIMs"],"operationId":"listEsims","summary":"List eSIMs","description":"⚠️ Activation credentials are never included in a list. Retrieve a single eSIM to obtain them.","x-required-scopes":["esims:read"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Page size. Values above the maximum are clamped rather than rejected, so asking for everything returns a page and a cursor instead of a 400."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The `next_cursor` from a previous page. Omit for the first page. A page whose `next_cursor` is null is the last one."},{"name":"state","in":"query","required":false,"schema":{"type":"string","enum":["pending","active","suspended","disconnected","failed"]}}],"responses":{"200":{"description":"A page of eSIMs.","content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more","next_cursor"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Esim"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["eSIMs"],"operationId":"createEsim","summary":"Buy an eSIM","description":"Reserves inventory, debits the wallet and records the intent to\nprovision — in one database transaction — then returns **202**.\n\nThe upstream network has NOT been contacted when this returns. Poll the\neSIM, poll its operation, or wait for `esim.provisioned`.\n\n⚠️ `Idempotency-Key` is required. Retrying with the same key returns the\nsame eSIM; a different key buys a second one.","x-required-scopes":["esims:write"],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,128}$"},"description":"Unique per purchase, reused on retry. A UUID is a good choice."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan_id"],"properties":{"plan_id":{"type":"string","description":"From `GET /v1/plans`."}}}}}},"responses":{"202":{"description":"Accepted. Provisioning has not happened yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Esim"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Wallet balance too low. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"No inventory, or the plan cannot be provisioned here. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}":{"get":{"tags":["eSIMs"],"operationId":"getEsim","summary":"Retrieve an eSIM, with activation credentials","description":"The only endpoint that returns activation credentials.\n\n`activation` appears once the eSIM is `active` or `suspended`. It is a\nbearer credential: whoever holds `lpa` can install the profile. Show it\nto your end user and do not log it.","x-required-scopes":["esims:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."}],"responses":{"200":{"description":"The eSIM.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Esim"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such eSIM under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}/usage":{"get":{"tags":["eSIMs"],"operationId":"getEsimUsage","summary":"Retrieve consumption and serving network","description":"How much of the allowance is left, and which network is carrying it.\n\n⚠️ **These figures are polled, not live.** `observed_at` says when we\nlast read them and `stale` says whether we still vouch for the reading.\nThere is no upstream push and no bulk endpoint, so usage is\nsynchronised in the background — an endpoint that called the network on\nevery request would let one integration exhaust the shared budget.\n\nEvery field is nullable, and that is the honest shape: an eSIM that has\nnever been used has no counters, no activation time and no network. Zero\nwould be a different and false statement.\n\n`period.activated_at` is when the allowance clock STARTED — first use,\nnot purchase.","x-required-scopes":["esims:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."}],"responses":{"200":{"description":"The current snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such eSIM under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}/suspend":{"post":{"tags":["eSIMs"],"operationId":"suspendEsim","summary":"Suspend this eSIM","description":"Stop this eSIM carrying traffic, reversibly.\n\n⚠️ **Accepted, not applied.** The response is `202`: the operation is\nqueued and a worker asks the network moments later. Nothing about the\neSIM has changed when this returns — poll the operation for the outcome.\n\nOnly an `active` eSIM can be suspended. Anything else is a `409` naming\nthe current state, so your code can branch without polling.\n\nAn `Idempotency-Key` header is required. Retrying with the same key\nreturns the same operation rather than creating a second one.","x-required-scopes":["esims:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,128}$"},"description":"Unique per action, reused on retry. A UUID is a good choice."}],"responses":{"202":{"description":"The operation was recorded and is queued. Poll `GET /v1/operations/{id}` for the outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleAccepted"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such eSIM under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This eSIM is not active, or another operation is already in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}/resume":{"post":{"tags":["eSIMs"],"operationId":"resumeEsim","summary":"Resume a suspended eSIM","description":"Return a suspended eSIM to service.\n\n⚠️ **Accepted, not applied** — see suspend. The `202` means queued.\n\nOnly a `suspended` eSIM can be resumed. A disconnected one cannot: that\nstate is final in Nesoraa and a replacement is a new purchase.\n\nAn `Idempotency-Key` header is required.","x-required-scopes":["esims:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,128}$"},"description":"Unique per action, reused on retry. A UUID is a good choice."}],"responses":{"202":{"description":"The operation was recorded and is queued. Poll `GET /v1/operations/{id}` for the outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleAccepted"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such eSIM under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This eSIM is not suspended, or another operation is already in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}/disconnect":{"post":{"tags":["eSIMs"],"operationId":"disconnectEsim","summary":"Disconnect this eSIM permanently","description":"⚠️ **Final.** End this eSIM’s service permanently.\n\nA disconnected eSIM is never returned to service through this API. The\nupstream subscriber record survives — this is not the supplier’s\nirreversible end-of-life state, which Nesoraa cannot reach at all — but\nNesoraa treats the eSIM as finished, because re-selling a cancelled\nsubscription would silently restore service a customer ended.\n\nGetting service again means buying a new eSIM.\n\n⚠️ **Accepted, not applied.** The `202` means queued; poll the\noperation. An `Idempotency-Key` header is required.","x-required-scopes":["esims:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,128}$"},"description":"Unique per action, reused on retry. A UUID is a good choice."}],"responses":{"202":{"description":"The operation was recorded and is queued. Poll `GET /v1/operations/{id}` for the outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleAccepted"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such eSIM under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This eSIM is already disconnected, or another operation is already in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}/location":{"get":{"tags":["eSIMs"],"operationId":"getEsimLocation","summary":"Retrieve the last known approximate network position","description":"Where the mobile network last placed this eSIM.\n\n⚠️ **This is an approximate position derived from the serving cell**\n— not GPS, not device-reported, and not consented to by the handset.\n`position.accuracy_meters` is a median error radius: the eSIM is\nsomewhere within roughly that distance, not at the point given. Do not\nbuild a geofence on it without accounting for that radius.\n\n**It requires its own scope, `location:read`, and `esims:read` will not\nwork.** Position says where a person is, so a credential issued to read\neSIM state does not acquire the ability to track handsets. Grant it\ndeliberately.\n\n**Positions are pushed by the network, not polled by us**, on a cadence\nthe network decides. That is why the response carries `age_seconds`\nrather than a `stale` flag as usage does — we cannot honestly tell you\na threshold for data whose arrival we do not control, so you apply the\none your use case justifies.\n\nA **404 with code `location_not_available`** is a normal answer, and a\ncommon one: an eSIM that has never attached to a network, or whose\nnetwork does not report position, will not have one. It is distinct\nfrom `esim_not_found`.","x-required-scopes":["location:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."}],"responses":{"200":{"description":"The last known position.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such eSIM (`esim_not_found`), or no position has ever been reported for it (`location_not_available`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/esims/{id}/messages":{"post":{"tags":["Messages"],"operationId":"sendMessage","summary":"Send an SMS to this eSIM","description":"Returns **202**. The message is recorded and authorised; the mobile\nnetwork has not been contacted yet. Poll the message or wait for a webhook.\n\n⚠️ **There is no delivery receipt.** The strongest state a message can\nreach is `accepted` — the network took it for onward transmission. No\npart of this API can tell you a handset displayed anything, and\n`accepted` must not be read as delivery.\n\n⚠️ **You cannot choose an arbitrary sender.** `sender` may name only an\nidentity registered to your project AND approved by an operator, and only\nif your project permits per-request senders. Otherwise the project\ndefault is used. This prevents impersonation and is not configurable by\nthe caller.\n\n`text` is limited to 140 printable ASCII characters. Non-Latin scripts\nand emoji are refused rather than transmitted incorrectly.\n\n`Idempotency-Key` is required: an SMS cannot be unsent.","x-required-scopes":["messages:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The eSIM id."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,128}$"},"description":"Unique per message, reused on retry."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":140,"description":"Printable ASCII only."},"sender":{"type":["string","null"],"description":"An approved sender identity. Omit to use the project default."}}}}}},"responses":{"200":{"description":"Idempotent replay of an earlier message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"202":{"description":"Accepted for dispatch. Not yet sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Sender not permitted, eSIM unreachable, or idempotency key reused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/operations":{"get":{"tags":["Operations"],"operationId":"listOperations","summary":"List operations","x-required-scopes":["esims:read"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Page size. Values above the maximum are clamped rather than rejected, so asking for everything returns a page and a cursor instead of a 400."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The `next_cursor` from a previous page. Omit for the first page. A page whose `next_cursor` is null is the last one."},{"name":"state","in":"query","required":false,"schema":{"type":"string","enum":["pending","executing","uncertain","succeeded","failed"]}},{"name":"esim_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of operations.","content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more","next_cursor"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Operation"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/operations/{id}":{"get":{"tags":["Operations"],"operationId":"getOperation","summary":"Retrieve an operation","x-required-scopes":["esims:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The operation id."}],"responses":{"200":{"description":"The operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such operation under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/wallet":{"get":{"tags":["Wallet"],"operationId":"getWallet","summary":"Retrieve the balance","description":"One balance per billing account, shared by both environments — there is no separate test balance. Read-only: adding funds is a payment, not an API call.","x-required-scopes":["wallet:read"],"responses":{"200":{"description":"The wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This project has no billing account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/wallet/transactions":{"get":{"tags":["Wallet"],"operationId":"listWalletTransactions","summary":"List ledger entries","description":"Append-only, newest first. `reference` is the resource an entry paid for, so a line on a statement joins back to the eSIM it bought.","x-required-scopes":["wallet:read"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Page size. Values above the maximum are clamped rather than rejected, so asking for everything returns a page and a cursor instead of a 400."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The `next_cursor` from a previous page. Omit for the first page. A page whose `next_cursor` is null is the last one."},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["CREDIT","DEBIT","REFUND"]}}],"responses":{"200":{"description":"A page of ledger entries.","content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more","next_cursor"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/WalletTransaction"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events":{"get":{"tags":["Events"],"operationId":"listEvents","summary":"List events","x-required-scopes":["events:read"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Page size. Values above the maximum are clamped rather than rejected, so asking for everything returns a page and a cursor instead of a 400."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The `next_cursor` from a previous page. Omit for the first page. A page whose `next_cursor` is null is the last one."},{"name":"type","in":"query","required":false,"schema":{"type":"string"}},{"name":"resource_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"actor_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of events.","content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more","next_cursor"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events/{id}":{"get":{"tags":["Events"],"operationId":"getEvent","summary":"Retrieve an event","description":"Use this to verify a webhook you did not authenticate: if the id in a delivery does not exist under your own credential, the delivery was not ours.","x-required-scopes":["events:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The event id."}],"responses":{"200":{"description":"The event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such event under this credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook_endpoints":{"get":{"tags":["Webhooks"],"operationId":"listWebhookEndpoints","summary":"List webhook endpoints","x-required-scopes":["webhooks:read"],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"operationId":"createWebhookEndpoint","summary":"Create a webhook endpoint","x-required-scopes":["webhooks:write"],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook_endpoints/{id}":{"get":{"tags":["Webhooks"],"operationId":"getWebhookEndpoint","summary":"Retrieve a webhook endpoint","x-required-scopes":["webhooks:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Webhooks"],"operationId":"updateWebhookEndpoint","summary":"Update a webhook endpoint","x-required-scopes":["webhooks:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","x-required-scopes":["webhooks:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook_endpoints/{id}/rotate_secret":{"post":{"tags":["Webhooks"],"operationId":"rotateWebhookSecret","summary":"Rotate the signing secret","x-required-scopes":["webhooks:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook_deliveries":{"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"List delivery attempts","x-required-scopes":["webhooks:read"],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook_deliveries/{id}":{"get":{"tags":["Webhooks"],"operationId":"getWebhookDelivery","summary":"Retrieve a delivery attempt","x-required-scopes":["webhooks:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook_deliveries/{id}/replay":{"post":{"tags":["Webhooks"],"operationId":"replayWebhookDelivery","summary":"Replay a delivery","x-required-scopes":["webhooks:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys":{"get":{"tags":["API keys"],"operationId":"listApiKeys","summary":"List API keys","x-required-scopes":["keys:read"],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["API keys"],"operationId":"createApiKey","summary":"Create an API key — the secret is shown once","x-required-scopes":["keys:write"],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys/{id}":{"delete":{"tags":["API keys"],"operationId":"revokeApiKey","summary":"Revoke an API key","x-required-scopes":["keys:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource id."}],"responses":{"200":{"description":"Success."},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid credential was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credential, insufficient scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Wait for the number of seconds in `Retry-After`, then retry — the request was not performed, so retrying is safe. `RateLimit-Remaining` and `RateLimit-Reset` are also present on successful responses, so a well-behaved client never needs to reach this status to discover its budget.","headers":{"Retry-After":{"description":"Whole seconds to wait before retrying. Always at least 1.","schema":{"type":"integer","minimum":1}},"RateLimit-Limit":{"description":"The burst capacity for this operation class, for your API key.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Whole requests left in the current burst budget.","schema":{"type":"integer","minimum":0}},"RateLimit-Reset":{"description":"Seconds until the budget is fully replenished.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something broke on our side. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A Nesoraa API key. The secret is shown once, at creation, and is stored only as a hash — it cannot be recovered, only replaced."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","not_found_error","payment_error","conflict_error","rate_limit_error","api_error"],"description":"A small closed set. Switch on this to decide behaviour."},"code":{"type":"string","description":"Specific and open-ended. What exactly happened."},"message":{"type":"string","description":"Written for a developer, not an end user."},"param":{"type":"string","description":"The field at fault, when there is one."},"request_id":{"type":"string","description":"Quote this to support."}}}}},"Country":{"type":"object","description":"A country Nesoraa knows about. ⚠️ Carries no id and no `livemode`: a country is a fact about the world, identical in both environments, and has no per-tenant row to address.","properties":{"object":{"type":"string","enum":["country"]},"iso2":{"type":"string","description":"ISO 3166-1 alpha-2, upper case."},"name":{"type":"string"},"availability":{"type":"string","enum":["available","coming_soon","unsupported"],"description":"`available` — an active plan covers it, buy it now. `coming_soon` — reachable, but no plan exists yet. `unsupported` — no active coverage, or not offered."}}},"Plan":{"type":"object","properties":{"object":{"type":"string","enum":["plan"]},"id":{"type":"string","description":"A Nesoraa identifier. Prefixed by resource kind and k-sortable, so lexical order is creation order — which is what makes cursor pagination stable."},"name":{"type":"string"},"description":{"type":["string","null"]},"coverage_kind":{"type":"string","enum":["country","region","global"]},"country_codes":{"type":"array","items":{"type":"string"},"description":"ISO 3166-1 alpha-2, lower case."},"data_bytes":{"type":"integer","description":"The exact allowance. Authoritative."},"data_mb":{"type":"integer","description":"The same allowance, rounded, for display."},"validity_days":{"type":"integer","description":"Counted from first use, not from purchase."},"currency":{"type":"string","description":"ISO 4217."},"price":{"type":"number","description":"What you pay per eSIM."}}},"Esim":{"type":"object","properties":{"object":{"type":"string","enum":["esim"]},"id":{"type":"string","description":"A Nesoraa identifier. Prefixed by resource kind and k-sortable, so lexical order is creation order — which is what makes cursor pagination stable."},"livemode":{"type":"boolean"},"state":{"type":"string","enum":["pending","active","suspended","disconnected","failed"]},"plan_id":{"type":"string"},"iccid":{"type":["string","null"],"description":"GSMA identifier. Null until provisioning succeeds."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"},"activation":{"type":"object","description":"⚠️ Bearer credential. Present only on a single-resource read of an installable eSIM, never in a list, an event or a webhook.","properties":{"smdp_server":{"type":"string"},"activation_code":{"type":"string"},"lpa":{"type":"string","description":"`LPA:1$<smdp>$<code>` — what a QR encodes."}}}}},"Operation":{"type":"object","properties":{"object":{"type":"string","enum":["operation"]},"id":{"type":"string","description":"A Nesoraa identifier. Prefixed by resource kind and k-sortable, so lexical order is creation order — which is what makes cursor pagination stable."},"livemode":{"type":"boolean"},"kind":{"type":"string","enum":["provision","suspend","resume","disconnect"]},"state":{"type":"string","enum":["pending","executing","uncertain","succeeded","failed"],"description":"`uncertain` means we dispatched the work and do not yet know whether it took effect. It resolves by evidence, never by retrying. An eSIM in this state is neither delivered nor refundable yet."},"esim_id":{"type":"string"},"failure_reason":{"type":["string","null"],"description":"Stable Nesoraa vocabulary. Null unless `failed`."},"created_at":{"type":["string","null"],"format":"date-time"},"dispatched_at":{"type":["string","null"],"format":"date-time"},"settled_at":{"type":["string","null"],"format":"date-time"}}},"LifecycleAccepted":{"type":"object","description":"An operation was queued. ⚠️ Nothing upstream has changed yet — poll `GET /v1/operations/{id}` for the outcome, and treat `state: pending` as \"asked\", not \"done\".","properties":{"operation_id":{"type":"string","description":"Poll this at `GET /v1/operations/{id}`."},"esim_id":{"type":"string"},"kind":{"type":"string","enum":["suspend","resume","disconnect"]},"state":{"type":"string","enum":["pending"]},"from":{"type":["string","null"],"description":"The eSIM state when the operation was accepted."},"to":{"type":["string","null"],"description":"The state it will reach if the network applies the change."},"description":{"type":"string"}}},"Usage":{"type":"object","description":"A snapshot, not a live reading. Check `stale` before acting on it.","properties":{"object":{"type":"string","enum":["usage"]},"esim_id":{"type":"string"},"livemode":{"type":"boolean"},"data":{"type":"object","properties":{"used_bytes":{"type":["integer","null"],"description":"Cumulative bytes consumed."},"total_bytes":{"type":["integer","null"],"description":"The allowance."},"remaining_bytes":{"type":["integer","null"],"description":"Floored at zero — an over-consumed package reports 0, never a negative."}}},"period":{"type":"object","properties":{"activated_at":{"type":["string","null"],"format":"date-time","description":"When the allowance clock started — FIRST USE, not purchase. Null until then."},"expires_at":{"type":["string","null"],"format":"date-time"}}},"network":{"type":"object","description":"The serving network. Null until the eSIM attaches. Device and location data are deliberately not published.","properties":{"mcc":{"type":["integer","null"],"description":"Mobile country code."},"mnc":{"type":["integer","null"],"description":"Mobile network code."},"technology":{"type":["string","null"],"description":"Radio technology, e.g. `4G - LTE`."},"observed_at":{"type":["string","null"],"format":"date-time","description":"The network's own timestamp for this reading."}}},"observed_at":{"type":["string","null"],"format":"date-time","description":"When Nesoraa last read these figures."},"stale":{"type":"boolean","description":"True when the observation is older than we vouch for, or has never been made. Published rather than hidden: usage is polled."}}},"Location":{"type":"object","description":"An APPROXIMATE position derived from the serving cell tower. Not GPS, not device-reported. Read `position.accuracy_meters` before acting on it.","properties":{"object":{"type":"string","enum":["location"]},"esim_id":{"type":"string"},"livemode":{"type":"boolean"},"position":{"type":"object","properties":{"latitude":{"type":"number","description":"Degrees, WGS 84."},"longitude":{"type":"number","description":"Degrees, WGS 84."},"accuracy_meters":{"type":["integer","null"],"description":"Median error radius in metres — the eSIM is somewhere within roughly this distance, not at the point given. Null means the error is UNKNOWN, not zero."},"precision":{"type":"string","enum":["network_approximate"],"description":"How the position was derived. `network_approximate` = from the serving cell tower. Published as a field so an integrator cannot mistake it for GPS."}}},"observed_at":{"type":"string","format":"date-time","description":"When the NETWORK observed the position, not when we stored it."},"age_seconds":{"type":"integer","description":"Seconds since the observation. Given instead of a `stale` flag because positions are pushed on a cadence we do not control — any threshold we published would be a claim about a cadence we cannot make. Apply your own."}}},"Message":{"type":"object","description":"A send attempt. The body is never returned — it is not stored after dispatch.","properties":{"object":{"type":"string","enum":["message"]},"id":{"type":"string","description":"A Nesoraa identifier. Prefixed by resource kind and k-sortable, so lexical order is creation order — which is what makes cursor pagination stable."},"livemode":{"type":"boolean"},"esim_id":{"type":"string"},"sender":{"type":"string","description":"The identity shown on the handset."},"length":{"type":"integer","description":"Characters sent. The text itself is not returned."},"status":{"type":"string","enum":["pending","accepted","failed"],"description":"`accepted` means the network took it for onward transmission. There is NO `delivered` state and there cannot be — no delivery receipt exists upstream."},"failure_reason":{"type":["string","null"],"description":"Stable Nesoraa vocabulary. Null unless failed."},"created_at":{"type":["string","null"],"format":"date-time"}}},"Wallet":{"type":"object","properties":{"object":{"type":"string","enum":["wallet"]},"balance":{"type":"number","description":"Spendable now."},"currency":{"type":"string"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"WalletTransaction":{"type":"object","properties":{"object":{"type":"string","enum":["wallet_transaction"]},"id":{"type":"string"},"type":{"type":"string","enum":["CREDIT","DEBIT","REFUND"],"description":"Direction. `amount` is always positive."},"amount":{"type":"number"},"balance_after":{"type":"number"},"description":{"type":"string"},"reference":{"type":["string","null"],"description":"The resource this paid for."},"created_at":{"type":["string","null"],"format":"date-time"}}},"Event":{"type":"object","properties":{"object":{"type":"string","enum":["event"]},"id":{"type":"string"},"livemode":{"type":"boolean"},"type":{"type":"string"},"resource_id":{"type":["string","null"]},"data":{"type":"object","description":"⚠️ Never contains an activation credential."},"created_at":{"type":["string","null"],"format":"date-time"}}},"Environment":{"type":"string","enum":["live","test"]},"Scope":{"type":"string","enum":["organizations:read","organizations:write","projects:read","projects:write","members:read","members:write","keys:read","keys:write","catalog:read","catalog:write","orders:read","orders:write","esims:read","esims:write","usage:read","usage:write","location:read","location:write","wallet:read","wallet:write","messages:read","messages:write","events:read","events:write","webhooks:read","webhooks:write","providers:read","providers:write","audit:read","audit:write"]}}}}