{"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 reach a real network, and cannot move money.\n\n**Test mode is a deterministic simulator.** `GET /v1/offers` with a test key\nlists simulated products in every coverage scope; buying one charges nothing\n(`wallet_debited: false`) and provisions deterministically, with well-formed\nactivation data that installs nothing on a device. Products named\n\"Simulated outcome: …\" drive the failure, uncertain-then-recovered and\nuncertain-then-reviewed paths on purpose. Catalogue plans (`plan_id`) are\nbought in live only.\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":"https://api.nesoraa.com","description":"Live — nsk_live_ keys only. Real money, real profiles."},{"url":"https://api-dev.nesoraa.com","description":"Test — nsk_test_ keys only. The simulator; nothing is provisioned."}],"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":"The destinations Nesoraa publishes. ⚠️ `availability` describes the fixed plan catalogue (`/v1/plans`) only: a country is `available` when an active plan covers it, `coming_soon` when our plan network reaches it but no plan exists yet, and `unsupported` when no plan can serve it. It is NOT the answer to \"can I buy here\" — `GET /v1/offers?country=` is, and it may sell a destination this endpoint marks `coming_soon` or `unsupported`.","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/offers":{"get":{"tags":["Plans"],"operationId":"listOffers","summary":"List the offers this project can buy now, in one scope","description":"The same catalogue the portal shows, priced for this project. Buy one with\n`POST /v1/esims { \"offer_id\": … }`.\n\nExactly one scope per request: `country=DZ` (that country's own products),\n`coverage=regional` or `coverage=global`. Regional and global are never\nreturned for a country request. A scope this environment does not serve is\na **422 `coverage_not_available`**, never an empty list.\n\nAn `id` is bound to this project and environment and expires (`expires_at`).\nThe price is re-resolved at purchase; send `max_price` to bound it.\n\nNesoraa sells connectivity from several upstream networks and presents them\nas ONE catalogue: a product appears once, whoever supplies it, and no\nsupplier is named in this response.\n\n`Nesoraa-Catalogue-Complete: false` means one of those upstreams could not\nbe read for this request, so **products are missing from the list** — it is\nnot a hint about staleness. Treat a partial list as a short cache entry and\nretry; do not treat a product's absence from a partial list as withdrawal.\n\n**Test keys** list simulated products in every scope. Buying one charges\nnothing and provisions deterministically.","x-required-scopes":["catalog:read"],"parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"description":"ISO 3166-1 alpha-2. Required when `coverage` is `country` or omitted."},{"name":"coverage","in":"query","required":false,"schema":{"type":"string","enum":["country","regional","global"],"default":"country"}}],"responses":{"200":{"description":"The offers in this scope.","headers":{"Nesoraa-Catalogue-Complete":{"description":"`true` when every upstream network answered. `false` means at least one could not be read and products are missing from this list. No upstream is named: which one failed is Nesoraa's operational concern.","schema":{"type":"string","enum":["true","false"]}}},"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/Offer"}},"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"}}}},"422":{"description":"`coverage_not_available`: this scope is not served in this environment.","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/offers/{id}":{"get":{"tags":["Plans"],"operationId":"getOffer","summary":"Re-quote one offer","description":"Whether the product is still sold, and its price now — the same resolution a purchase performs. A freshly issued `id` is returned.","x-required-scopes":["catalog:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The offer id."}],"responses":{"200":{"description":"The offer, re-quoted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"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 offer for this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`offer_withdrawn`: the product is no longer sold.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`coverage_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"}}}},"503":{"description":"`catalogue_unavailable`: could not be confirmed now. Retry.","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":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Alias of `cursor`, accepted for compatibility. Sending both with different values is a 400 `conflicting_cursor`."},{"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 — even after the offer expired or while purchases are paused. A\nkey reused for a DIFFERENT purchase is refused with `409 idempotency_key_reused`.\n\nSend `offer_id` (from `GET /v1/offers`) or `plan_id` (from `GET /v1/plans`,\nlive only). The price is resolved server-side now; `max_price` refuses the\npurchase with `409 price_changed` if it is higher. A price in the body is ignored.\n\n**Test keys** buy from the simulator: no wallet is touched (`wallet_debited:\nfalse`) and no real network is contacted.","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","description":"Exactly one of `offer_id` or `plan_id`.","properties":{"offer_id":{"type":"string","description":"From `GET /v1/offers`."},"plan_id":{"type":"string","description":"From `GET /v1/plans`. Live only."},"max_price":{"type":"number","exclusiveMinimum":0,"description":"The most you agree to pay, in the product's currency. A ceiling, never the price."},"currency":{"type":"string","pattern":"^[A-Za-z]{3}$","description":"Optional. Refused with 422 `currency_mismatch` if the product is sold in another."},"client_reference":{"type":"string","pattern":"^[A-Za-z0-9._:-]{1,64}$","description":"Your own reference, echoed on the eSIM."}}}}}},"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"}}}},"404":{"description":"`offer_not_found` or `plan_not_found`. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`price_changed`, `idempotency_key_reused`, `offer_withdrawn`, `inventory_unavailable`, `capacity_unavailable`, `product_unavailable`, `purchases_paused`. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`currency_mismatch`, `coverage_not_available`, or a plan purchase in test. 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"}}}},"503":{"description":"`catalogue_unavailable`: retry with the same key. Nothing was charged.","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","unprocessable_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":"Plan-catalogue availability. `available` — an active plan covers it. `coming_soon` — reachable, but no plan exists yet. `unsupported` — no plan can serve it. Offers (`GET /v1/offers?country=`) are the authority on what can be bought."}}},"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."}}},"Offer":{"type":"object","properties":{"object":{"type":"string","enum":["offer"]},"id":{"type":"string","description":"Pass as `offer_id`. Bound to this project and environment; expires."},"livemode":{"type":"boolean"},"product_ref":{"type":"string","description":"Stable reference for this product in this scope."},"name":{"type":"string"},"category":{"type":"string","enum":["fixed_data","unlimited","voice_sms"]},"coverage":{"type":"string","enum":["country","regional","global"]},"country_codes":{"type":"array","items":{"type":"string"}},"data_bytes":{"type":["integer","null"]},"data_gb":{"type":["number","null"]},"unlimited":{"type":"boolean"},"daily_data_bytes":{"type":["integer","null"]},"throttle_kbps":{"type":["integer","null"]},"validity_days":{"type":["integer","null"]},"voice":{"type":"boolean"},"sms":{"type":"boolean"},"voice_minutes":{"type":["integer","null"]},"sms_count":{"type":["integer","null"]},"price":{"type":"number","description":"The price now. Re-resolved at purchase."},"currency":{"type":"string"},"expires_at":{"type":["string","null"],"format":"date-time"}}},"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","null"],"description":"The product bought. Same value as `product_ref`."},"product_ref":{"type":["string","null"],"description":"A catalogue plan id or a `prod_…` reference."},"product_name":{"type":["string","null"]},"price":{"type":["number","null"],"description":"What this purchase cost."},"currency":{"type":["string","null"]},"wallet_debited":{"type":["boolean","null"],"description":"False for a test-mode purchase."},"client_reference":{"type":["string","null"]},"refunded_at":{"type":["string","null"],"format":"date-time","description":"Set when a failed provisioning was refunded."},"installable":{"type":"boolean","description":"Whether a single read would return installation details now."},"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`."},"requires_attention":{"type":"boolean","description":"True while the outcome could not be established from evidence and is waiting for a reviewed decision (`operation.review_required`). The eSIM stays pending until then."},"review_reason":{"type":["string","null"],"enum":["absent_proven","activation_unavailable","unresolved_timeout","manual",null],"description":"Why it needs attention. Null unless `requires_attention`."},"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"]}}}}