Справочник API

Продавайте тарифы eSIM и виртуальные номера в своём продукте. Один API-ключ, один предоплаченный баланс и те же заказы, что вы видите после входа, — без отдельного реселлерского учёта, который нужно сверять.

Базовый URLhttps://simarea.com/api/v1

Introduction

Everything SIMAREA sells is reachable over HTTP with an API key: the catalog, your balance, buying an eSIM data plan or a phone number, reading the SMS that arrives on it, and canceling an order.

There is one account and one prepaid USD balance behind both surfaces. An order placed through this API is the same order you see when you sign in to the website — same row, same balance, same refund rules. There is no separate reseller ledger to reconcile.

  • eSIM data plans — pick a destination, buy a plan, hand the customer a QR code to install.
  • Virtual numbers — rent a dedicated non-VoIP line by the term and keep its full message history.

The two are separate product families and never collapse into one label. They differ in how long they last, what they cost, and how you buy them — so the reference below is organised by family, and each family section carries every call you need for that product start to finish.

DetailValue
Base URLhttps://simarea.com/api/v1
AuthAuthorization: Bearer sk_live_…
Content typeapplication/json
CurrencyUSD, always, as integer cents in a string
VersioningThe version is in the path, so a future v2 cannot change what v1 returns

Quickstart

  • Create a SIMAREA account and top up your balance.
  • Issue an API key from the API keys panel in your account. It is shown once, at issue time, and cannot be recovered — store it before you close the dialog.
  • Check the key works and see what it can do.
  • Read a price, then buy.
cURL
# 1 — is my key alive, and what may it do?
curl https://simarea.com/api/v1/ \
  -H "Authorization: Bearer sk_live_..."

# 2 — what does a US virtual number cost right now, and for which terms?
curl "https://simarea.com/api/v1/catalog/offers/?family=virtual_number&country=usa" \
  -H "Authorization: Bearer sk_live_..."

# 3 — rent one. The Idempotency-Key is required.
curl -X POST https://simarea.com/api/v1/orders/ \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "family": "virtual_number", "countrySlug": "usa", "serviceSlug": null, "duration": "oneWeek" }'

# 4 — poll the order and read the messages it receives.
curl https://simarea.com/api/v1/orders/o_5rT8w1Nq3vHc/ \
  -H "Authorization: Bearer sk_live_..."

eSIM plans are not purchasable yet.

That is why the purchase above is a virtual number. The eSIM section explains the reason, and what you can build and test in the meantime.

Authentication

Issue a key from the API keys panel in your account and send it as a bearer token on every request.

cURL
curl https://simarea.com/api/v1/balance/ \
  -H "Authorization: Bearer sk_live_..."

The header is the only way in.

There is no ?api_key= query parameter and there will not be one. Query strings land in web-server access logs, in browser history, in Referer headers on outbound links, and in the error reports of every proxy in between. A key in a URL is a key that has leaked.

A missing, unknown, revoked or expired key — and a key belonging to a suspended account — all answer 401 with the same code, deliberately: telling a caller which of those it was tells an attacker which of those it was.

Revocation takes effect on the very next request. Nothing about a key is cached, anywhere, for any length of time. If you believe a key has leaked, revoke it and the next call made with it fails.

Issue one key per integration. Rate limits are per key, so a burst in your bot cannot throttle your storefront, and revoking one does not take the others down with it.

Each key carries a set of permissions. A key that lacks the one a route requires gets 403, and nothing is charged.

РазрешениеЧто разрешает
readEvery GET: the catalog, your balance, your order list, one order, and the API root.
orderCreate and cancel virtual number and eSIM orders — families virtual_number and esim.
adminA key-level wildcard implying the two above. It grants nothing beyond the routes on this page: there is no administrative surface on the public API.

Response format

One thing comes back under a singular key, many under a plural one, and a failure under error. A response is never a bare JSON array, so a list can grow a sibling field — as orders did when it gained nextCursor — without breaking your client.

Ответ
{ "order":  { ... } }                              one order
{ "orders": [ { ... } ], "nextCursor": null }      a list
{ "error":  { "code": "...", "message": "..." } }  a failure
  • Money is a decimal string of integer cents, never a JSON number — "1234" is $12.34. A JSON number is an IEEE double and money does not survive one. Every amount is USD.
  • Dates are ISO 8601 strings in UTC, always with the Z.
  • Order ids are the `o_…` form. That is the only order identifier that exists outside our database, and it is what every route takes and returns.
  • Every response carries `cache-control: no-store`. These answers are per-key by construction and must not be held by a shared cache.
  • Every response carries `x-request-id`. Log it. Quoting it turns a support conversation about "an order this morning" into one about a single request.
  • No upstream is ever named. Which carrier or supplier fills an order is not part of this API and appears in no field. Do not build logic that depends on inferring it.

Every path ends in a slash. Read this before your first POST.

POST /api/v1/orders — without the trailing slash — answers 308 Permanent Redirect to /api/v1/orders/. A 308 preserves the method and the body, so a correct client follows it and the order is placed exactly once.

Several HTTP libraries drop the request body when they follow a redirect. When that happens the second request arrives empty, and the failure reads as "the API rejected my order for no reason". Write the slash and the hop never happens. The same applies to every GET.

This API is for server-to-server use and sends no CORS headers. Calling it from a browser will fail, and that is the intended behavior: a key that reaches a browser is a key your customers can read.

Idempotency

Idempotency-Key is required on every purchase.

A request without it is a 400. We do not generate one for you, and that is the whole point: a key the server invents is a fresh key on every attempt, so every retry would become a second order — which on this product means a second debit and a second number.

  • Generate one key per purchase intent, not per HTTP attempt. A UUID is ideal. It may be 8 to 200 characters of letters, digits and _ . : -.
  • Retry with the same key and the same body and you get the same order back, with nothing charged twice. This is the safe behavior after a timeout, a dropped connection or a 5xx — and after a timeout it is the *only* safe behavior, because a request that timed out may well have succeeded.
  • Reuse a key with a different body and the request is refused with 409 rather than being answered with the earlier order. Changing what you are buying means a new key.
  • Keys are scoped to your account, so yours can never collide with another customer's, and nobody can probe whether you used a particular value.
  • Reordering the JSON, adding whitespace, or sending "serviceSlug": null instead of omitting it all count as the same purchase. A retry that re-serialises its body is still a retry.

Store the key alongside your own order record before you send the request, not after. A key you generated and then lost is a key you cannot retry with.

Pagination

GET /orders/ is the only route that pages. It returns up to limit orders newest-first, plus a nextCursor.

  • limit is 1 to 200 and defaults to 50.
  • nextCursor is an opaque string. Send it back as ?cursor= for the next page. Do not parse it, do not build one, and do not assume its format is stable.
  • nextCursor is null when you have reached the end. That is the only stop condition — an empty page is not one, and neither is a page shorter than limit.
  • Keep family, open and limit identical for the whole walk. Changing a filter mid-walk makes the cursor mean something you did not intend.
  • A malformed cursor is a 400, never a silent restart at page one. An integration that silently restarts loops over the same page forever and nobody notices until the bill does.

Ordering is by creation time, newest first. New orders appear at the *front*, so a walk that takes a while will not see orders created after it started — which is what you want when you are reconciling.

Rate limits

The limit is per key, per 60-second window, and it is a property of the key. Issue one key per integration and a burst in one will not throttle the others. Your own limit is on the API root response, so you never have to discover it by hitting it.

Заголовки
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1755273600     unix seconds, when the window resets

Over the limit is 429 with a Retry-After header in seconds. Obey that header rather than a fixed back-off — it is computed from the database clock, so every one of our instances agrees on it.

When you are polling an order, poll on a schedule you can defend. There are no outbound webhooks in this version, so polling is the mechanism — but a tight loop spends your window on nothing and will throttle the purchase you actually needed to make.

Errors

Every failure is { "error": { "code", "message" } } at the status below. Switch on `code`, never on `message` — the code is the contract and the message is prose we may reword.

retryable means repeating the identical call may succeed. Anything else needs a change on your side, or a report to us with the x-request-id.

402 response
HTTP/1.1 402

{
  "error": {
    "code": "insufficient_funds",
    "message": "Insufficient balance",
    "details": {
      "balanceCents": "120",
      "requiredCents": "150",
      "shortfallCents": "30"
    }
  }
}

A details object appears only on the codes marked below, and only those. It is part of the published contract for those codes and is not a general debugging channel.

КодHTTPМожно повторитьdetailsЗначение
bad_request400нет—The request is malformed — a missing header, an unparseable body, an unknown filter value.
config_error500нет—A server-side misconfiguration. Not a client error; report it.
forbidden403нет—The key is valid but its permissions do not include the one this route requires.
idempotency_conflict409нет—This Idempotency-Key already names a different order. Use a fresh key for a new order.
illegal_transition500нет—The order was in an unexpected status when the change was applied. Nothing was charged. Report it if it repeats.
insufficient_funds402нетдаThe account balance does not cover the order. details carries the shortfall in cents.
internal_error500нет—An unhandled server-side failure. Report it with the time of the call.
invalid_amount500нет—A price or amount failed validation. Server-side; report it.
invalid_argument400нет—A pagination or filter argument was out of range or the wrong shape.
invalid_ledger_entry500нет—A ledger entry failed validation. Server-side; report it.
invalid_order_input500нет—An order field was rejected before anything was charged.
invalid_pricing_rule500нет—A pricing rule is malformed. Server-side; report it.
invalid_refund_reason500нет—A refund was attempted with an unknown reason. Server-side; report it.
invalid_signature401нет—A webhook signature did not verify. Not reachable from /api/v1.
ledger_post_failed500нет—A balance movement could not be recorded. Nothing was charged; the request is safe to retry.
not_found404нет—No such route, order, country, service or destination for this key's account.
order_create_conflict409да—The Idempotency-Key collided with an order that then rolled back. Retry with the same key.
order_family_mismatch500нет—An order was routed to the wrong family's provisioner. Server-side; report it.
order_not_cancellable409нет—The order is in a status that cannot be cancelled — it is already completed, expired, failed or cancelled.
order_not_found404нет—The order named by the request does not exist.
order_transition_failed500нет—The order's status could not be advanced. Server-side; report it.
out_of_stock409нет—No provider could supply this product right now. Nothing was charged.
provider_not_registered500нет—No adapter is registered for the provider this order needs. Server-side; report it.
provider_unavailable503да—A provider's circuit breaker is open. Safe to retry shortly.
rate_limited429дадаOver the key's rate_limit for the current 60-second window. Retry-After says when to try again.
rental_not_priced409нет—No pricing rule covers this rental term, so no price could be quoted. Nothing was charged.
sms_already_received409нет—This order has already received an SMS, so it cannot be cancelled or refunded. The code you were sent is yours.
unauthorized401нет—No Authorization: Bearer sk_live_… header, or the key is unknown, revoked, expired, or belongs to a blocked account.
upstream_error502нет—A provider rejected or failed the call. Nothing was charged, or the charge was refunded.
upstream_timeout504да—A provider did not answer in time. Safe to retry.
user_not_found404нет—The account behind the key could not be read. Server-side; report it.

Nothing is charged on a failure.

A purchase that fails before the debit never takes the money; a purchase that fails after it is refunded automatically. If you ever see a debit with no corresponding order, that is a bug and we want the x-request-id.

OpenAPI, Postman and llms.txt

This page, the OpenAPI document, the Postman collection and the llms.txt reference are all generated from one description of the API. They cannot disagree with each other, and none of them can disagree with the routes.

  • OpenAPI 3.1 — import into your generator of choice for a typed client in any language.
  • Postman collection — import straight into Postman or Insomnia. Set the apiKey collection variable and every request is ready to send.
  • llms.txt — the whole reference as plain Markdown, for pasting into an AI assistant that is writing your client.

If you are asking a model to write your integration, give it the llms.txt file rather than a screenshot of this page. It contains the same rules in the same order, and none of the layout.

Линейка продуктов

eSIM data plans

An eSIM is a data plan for a destination. You pick a destination, pick a plan, buy it, and the order comes back with everything the customer needs to install the profile — a QR image, an activation code and an SM-DP+ address — plus how much data it has used.

eSIM plans are not purchasable yet, and that is deliberate.

GET /catalog/packages/ returns an empty list on every account today. eSIM pricing is blocked on a unit question with our supplier that has not been settled, so no plan is priced and none is offered for sale. Your key authenticates, the route works, the list is empty — it is not a fault at your end and not a fault at ours.

GET /catalog/destinations/ answers in full in the meantime, and its syncedPackageCount tells you how many plans exist per destination regardless of pricing. Build and test your destination mapping now; plans appear the day they are priced, with no change to any shape on this page.

  • Read pricingUnit before priceCents: bundle is the whole plan, per_day is a daily rate and you choose the term with periodNum.
  • dataMb of null means unlimited, never zero.
  • An eSIM order is provisioning until the profile exists. Poll the order; qrCodeUrl and acCode fill in when it is installable.
GET/api/v1/catalog/destinations/read

List eSIM destinations

Every place an eSIM plan can cover — single countries, regions and global plans — in one list.

Build your destination mapping against syncedPackageCount, which counts what our supplier carries regardless of whether it is priced yet. packageCount counts only what is on sale.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "destinations": Destination[] } — см. Destination.

Ошибки

unauthorized rate_limited — все они описаны в разделе Errors.

List destinations

Запрос
curl https://simarea.com/api/v1/catalog/destinations/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "destinations": [
    {
      "slug": "thailand",
      "kind": "country",
      "name": "Thailand",
      "countryCode": "th",
      "isPopular": true,
      "fromBundlePriceCents": null,
      "fromPerDayPriceCents": null,
      "packageCount": 0,
      "syncedPackageCount": 18,
      "isListed": true
    },
    {
      "slug": "europe-esim",
      "kind": "region",
      "name": "Europe",
      "countryCode": null,
      "isPopular": true,
      "fromBundlePriceCents": null,
      "fromPerDayPriceCents": null,
      "packageCount": 0,
      "syncedPackageCount": 26,
      "isListed": true
    }
  ]
}
GET/api/v1/catalog/packages/read

List eSIM data plans

The eSIM plans on sale, optionally for one destination. code is what you send back as packageCode when you buy.

Read pricingUnit before you read priceCents. On a bundle the price is the total for the plan; on a per_day package it is a daily rate and you choose the term at purchase with periodNum.

Параметры запроса

ИмяОбязательныйОписание
destination
string
необязательныйNarrow to one destination slug. An unknown slug is a 404.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "packages": Package[] } — см. Package.

Важно знать

  • This route currently returns an EMPTY LIST, and that is the correct answer rather than a fault. eSIM pricing is blocked on an unresolved unit question with our supplier, so no plan is priced and none is offered for sale. Your key authenticates, the route works, the list is empty. An eSIM purchase attempted today is refused and nothing is charged.
  • The destinations route answers in full in the meantime, so the mapping half of your integration can be built and tested now.

Ошибки

unauthorized not_found bad_request rate_limited — все они описаны в разделе Errors.

Plans for one destination

Запрос
curl "https://simarea.com/api/v1/catalog/packages/?destination=thailand" \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "packages": []
}
POST/api/v1/orders/order

Buy

One route, two bodies, told apart by family. The body carries no price: the order is priced from live supply at the moment of purchase, and the response tells you what was actually charged.

A success is a 201 with the order read back AFTER it was created, so its status is current rather than the one it held a moment earlier.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.
Idempotency-KeyобязательныйOne value per purchase intent, NOT per HTTP attempt. 8 to 200 characters of letters, digits and _ . : -; a UUID is ideal. Absent is a 400 — we never generate one for you.

Тело запроса

ПолеТипОписание
familystringWhich product you are buying. It decides which of the fields below apply. Одно из: virtual_number, esim
countrySlug необязательноеstringvirtual_number only, and required there. From the countries route.
serviceSlug необязательноеstring | nullvirtual_number only, and optional there — send null or omit it for a line that accepts any service.
duration необязательноеstringvirtual_number only, and required there. The term you are renting for. Одно из: halfDay, oneDay, 3Days, oneWeek, twoWeeks, oneMonth, threeMonths, sixMonths, twelveMonths
autoRenew необязательноеbooleanvirtual_number only. Defaults to false. When true we charge your balance for another term as this one ends, so the line does not lapse.
destinationSlug необязательноеstringesim only, and required there. From the destinations route.
packageCode необязательноеstringesim only, and required there. The code of a package, sent back verbatim.
periodNum необязательноеinteger | nullesim only, and only on a package whose pricingUnit is per_day: how many days you are buying. Ignored on a bundle.

Возвращает

201 · { "order": Order } — см. Order.

Важно знать

  • The permission required is order, for a virtual number and an eSIM alike. A key without it gets a 403, and nothing is charged.
  • A slug that does not resolve is a 404. It is never quietly substituted for something near it.
  • Nothing is charged on any failure. Where a debit has already happened when the supplier fails, it is refunded automatically.

Ошибки

unauthorized forbidden bad_request not_found insufficient_funds out_of_stock idempotency_conflict rate_limited upstream_error upstream_timeout provider_unavailable — все они описаны в разделе Errors.

Buy an eSIM data plan

Запрос
curl -X POST https://simarea.com/api/v1/orders/ \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: 9d24a7e0-esim-001" \
  -H "Content-Type: application/json" \
  -d '{ "family": "esim",
        "destinationSlug": "thailand",
        "packageCode": "TH-7D-1GB",
        "periodNum": 7 }'
Ответ 201
{
  "order": {
    "publicId": "o_2wF6y9Bk4mTd",
    "family": "esim",
    "status": "provisioning",
    "priceCents": "420",
    "createdAt": "2026-08-24T09:31:47.104Z",
    "completedAt": null,
    "esim": {
      "destinationSlug": "thailand",
      "qrCodeUrl": null,
      "acCode": null,
      "smdpAddress": null,
      "iccid": null,
      "dataTotalMb": "1024",
      "dataUsedMb": "0",
      "expiresAt": null
    }
  }
}
GET/api/v1/orders/{publicId}/read

Read one order

One order and everything currently known about it, including every SMS received on a number order and the install details on an eSIM.

This is the route you poll while an order is in flight. There are no outbound webhooks in this version.

Параметры пути

ИмяОбязательныйОписание
publicId
string
обязательныйThe order's o_… id.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "order": Order } — см. Order.

Важно знать

  • An order belonging to another account answers 404, not 403. A key can only ever see its own account's orders.
  • Poll no faster than the rate-limit headers allow.

Ошибки

unauthorized not_found rate_limited — все они описаны в разделе Errors.

An installable eSIM

Запрос
curl https://simarea.com/api/v1/orders/o_2wF6y9Bk4mTd/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "order": {
    "publicId": "o_2wF6y9Bk4mTd",
    "family": "esim",
    "status": "active",
    "priceCents": "420",
    "createdAt": "2026-08-24T09:31:47.104Z",
    "completedAt": null,
    "esim": {
      "destinationSlug": "thailand",
      "qrCodeUrl": "https://qr.example.net/8ac1f0e2b7.png",
      "acCode": "LPA:1$smdp.example.net$K2-XXXXXXXX",
      "smdpAddress": "smdp.example.net",
      "iccid": "8900000000000000000",
      "dataTotalMb": "1024",
      "dataUsedMb": "37",
      "expiresAt": "2026-08-31T09:33:02.000Z"
    }
  }
}
GET/api/v1/orders/read

List your orders

Your orders, newest first, across every family unless you narrow it. This is also the eSIM list and the number list — there is no separate route for either, because two doors onto one list are two things to keep in step.

Pages are walked with cursor. Send the nextCursor from the previous response to get the next page; nextCursor is null on the last one.

Параметры запроса

ИмяОбязательныйОписание
family
string
необязательныйOnly this product family. Absent means every family. Одно из: virtual_number, esim
open
boolean
необязательный1 or true returns only orders still in flight. Absent means no filter, which is not the same request as open=0.
limit
integer
необязательный1 to 200. Defaults to 50.
cursor
string
необязательныйThe nextCursor from the previous page. Opaque — do not parse it or construct one. A malformed value is a 400 rather than a silent restart at page one.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "orders": Order[], "nextCursor": string | null } — см. Order.

ПолеТипОписание
nextCursorstring | nullPass as ?cursor= for the next page. null when this was the last page. Present only alongside orders.

Важно знать

  • Keep the filters identical while you page. Changing family or open mid-walk makes the cursor mean something you did not intend.

Ошибки

unauthorized bad_request rate_limited — все они описаны в разделе Errors.

Your eSIMs

Запрос
curl "https://simarea.com/api/v1/orders/?family=esim" \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "orders": [],
  "nextCursor": null
}

Page through every order

Запрос
curl "https://simarea.com/api/v1/orders/?limit=200" \
  -H "Authorization: Bearer sk_live_..."

# then, while nextCursor is not null:
curl "https://simarea.com/api/v1/orders/?limit=200&cursor=MjAyNi0wOC0yNFQwOToxNDowMy4yMjFafG9fOGtRMm00WHI5ZExw" \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "orders": [ /* 200 orders */ ],
  "nextCursor": "MjAyNi0wOC0yNFQwOToxNDowMy4yMjFafG9fOGtRMm00WHI5ZExw"
}
POST/api/v1/orders/{publicId}/cancel/read, plus order

Cancel and refund an order

Stops an order and returns what it refunded. The rules are exactly the website's own cancel button — this route is not a second policy.

Параметры пути

ИмяОбязательныйОписание
publicId
string
обязательныйThe order's o_… id.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "cancel": Cancel } — см. Cancel.

Важно знать

  • Canceling an order that our own expiry sweeper closed a moment earlier answers 200, not an error. You asked for it to stop; it has stopped.
  • An order that has already received an SMS cannot be canceled or refunded — the code you paid for arrived. That is a 409.
  • A rental that ran its full term refunds nothing, because it delivered everything it promised.

Ошибки

unauthorized forbidden not_found sms_already_received order_not_cancellable rate_limited — все они описаны в разделе Errors.

Already closed by the sweeper

Запрос
curl -X POST https://simarea.com/api/v1/orders/o_8kQ2m4Xr9dLp/cancel/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "cancel": {
    "status": "expired",
    "refundedCents": "0"
  }
}
Линейка продуктов

Virtual numbers

A virtual number is a dedicated non-VoIP line you rent for a term — from half a day up to a year. It is yours for that whole term, it takes messages from any service unless you rented it for a specific one, and its full message history stays on the order.

  • Send serviceSlug: null (or omit it) for a line that accepts any service. Name a service only when you need one guaranteed to work with it.
  • autoRenew: true charges your balance for another term as the current one ends, so the line does not lapse. It defaults to false.
  • Canceling mid-term follows the same rules as the website's cancel button. A rental that ran its full term refunds nothing — it delivered what it promised.
  • Messages arrive on the order. Poll GET /orders/{publicId}/ and read the rental.messages array.
GET/api/v1/catalog/countries/read

List every country

Every country we sell numbers in, popular ones first — the same order the website uses, so a list you cache and a page a customer reads agree.

It is NOT filtered by what is in stock this minute. That is a property of one family at one moment and the offers route answers it; a country list that shrank when a pool emptied would silently break a mapping you built last week.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "countries": Country[] } — см. Country.

Ошибки

unauthorized rate_limited — все они описаны в разделе Errors.

List countries

Запрос
curl https://simarea.com/api/v1/catalog/countries/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "countries": [
    { "slug": "usa", "name": "United States", "code": "us", "phoneCode": "+1", "isPopular": true },
    { "slug": "united-kingdom", "name": "United Kingdom", "code": "gb", "phoneCode": "+44", "isPopular": true }
  ]
}
GET/api/v1/catalog/services/read

List every service

Every app or website a number can be bought for, popular ones first. slug is what a purchase body takes.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "services": Service[] } — см. Service.

Ошибки

unauthorized rate_limited — все они описаны в разделе Errors.

List services

Запрос
curl https://simarea.com/api/v1/catalog/services/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "services": [
    { "slug": "telegram", "name": "Telegram", "isPopular": true },
    { "slug": "whatsapp", "name": "WhatsApp", "isPopular": true }
  ]
}
GET/api/v1/catalog/offers/read

Live prices and stock

What can be bought right now and for how much. Each row is the cheapest live price for a country, a service and a term across every supplier we have enabled.

family is required and there is no "all families" mode: the families do not share a price shape, and a single list would force you to branch on a field anyway.

Параметры запроса

ИмяОбязательныйОписание
family
string
обязательныйWhich product to price. Одно из: virtual_number, esim
country
string
необязательныйNarrow to one country slug.
service
string
необязательныйNarrow to one service slug.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "offers": Offer[] } — см. Offer.

Важно знать

  • A slug that does not exist is a 404, not an empty list. An unknown family is a 400. Neither is ever silently widened to "everything".
  • Prices move with supply. Quote from this route at the moment you show a price, and do not cache it for longer than you would be happy to be wrong for.

Ошибки

unauthorized forbidden bad_request not_found rate_limited — все они описаны в разделе Errors.

Virtual number prices

Запрос
curl "https://simarea.com/api/v1/catalog/offers/?family=virtual_number&country=usa" \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "offers": [
    {
      "family": "virtual_number",
      "countrySlug": "usa",
      "serviceSlug": null,
      "durationSlug": "oneWeek",
      "priceCents": "900",
      "stock": null
    },
    {
      "family": "virtual_number",
      "countrySlug": "usa",
      "serviceSlug": null,
      "durationSlug": "oneMonth",
      "priceCents": "2400",
      "stock": null
    }
  ]
}
POST/api/v1/orders/order

Buy

One route, two bodies, told apart by family. The body carries no price: the order is priced from live supply at the moment of purchase, and the response tells you what was actually charged.

A success is a 201 with the order read back AFTER it was created, so its status is current rather than the one it held a moment earlier.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.
Idempotency-KeyобязательныйOne value per purchase intent, NOT per HTTP attempt. 8 to 200 characters of letters, digits and _ . : -; a UUID is ideal. Absent is a 400 — we never generate one for you.

Тело запроса

ПолеТипОписание
familystringWhich product you are buying. It decides which of the fields below apply. Одно из: virtual_number, esim
countrySlug необязательноеstringvirtual_number only, and required there. From the countries route.
serviceSlug необязательноеstring | nullvirtual_number only, and optional there — send null or omit it for a line that accepts any service.
duration необязательноеstringvirtual_number only, and required there. The term you are renting for. Одно из: halfDay, oneDay, 3Days, oneWeek, twoWeeks, oneMonth, threeMonths, sixMonths, twelveMonths
autoRenew необязательноеbooleanvirtual_number only. Defaults to false. When true we charge your balance for another term as this one ends, so the line does not lapse.
destinationSlug необязательноеstringesim only, and required there. From the destinations route.
packageCode необязательноеstringesim only, and required there. The code of a package, sent back verbatim.
periodNum необязательноеinteger | nullesim only, and only on a package whose pricingUnit is per_day: how many days you are buying. Ignored on a bundle.

Возвращает

201 · { "order": Order } — см. Order.

Важно знать

  • The permission required is order, for a virtual number and an eSIM alike. A key without it gets a 403, and nothing is charged.
  • A slug that does not resolve is a 404. It is never quietly substituted for something near it.
  • Nothing is charged on any failure. Where a debit has already happened when the supplier fails, it is refunded automatically.

Ошибки

unauthorized forbidden bad_request not_found insufficient_funds out_of_stock idempotency_conflict rate_limited upstream_error upstream_timeout provider_unavailable — все они описаны в разделе Errors.

Rent a virtual number

Запрос
curl -X POST https://simarea.com/api/v1/orders/ \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: 3f0b7c11-rental-2026-08" \
  -H "Content-Type: application/json" \
  -d '{ "family": "virtual_number",
        "countrySlug": "usa",
        "serviceSlug": null,
        "duration": "oneWeek",
        "autoRenew": false }'
Ответ 201
{
  "order": {
    "publicId": "o_5rT8w1Nq3vHc",
    "family": "virtual_number",
    "status": "active",
    "priceCents": "900",
    "createdAt": "2026-08-24T09:20:11.550Z",
    "completedAt": null,
    "rental": {
      "phone": "+12025550188",
      "countrySlug": "usa",
      "serviceSlug": null,
      "durationSlug": "oneWeek",
      "rentedUntil": "2026-08-31T09:20:11.550Z",
      "autoRenew": false,
      "messages": []
    }
  }
}
GET/api/v1/orders/{publicId}/read

Read one order

One order and everything currently known about it, including every SMS received on a number order and the install details on an eSIM.

This is the route you poll while an order is in flight. There are no outbound webhooks in this version.

Параметры пути

ИмяОбязательныйОписание
publicId
string
обязательныйThe order's o_… id.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "order": Order } — см. Order.

Важно знать

  • An order belonging to another account answers 404, not 403. A key can only ever see its own account's orders.
  • Poll no faster than the rate-limit headers allow.

Ошибки

unauthorized not_found rate_limited — все они описаны в разделе Errors.

An installable eSIM

Запрос
curl https://simarea.com/api/v1/orders/o_2wF6y9Bk4mTd/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "order": {
    "publicId": "o_2wF6y9Bk4mTd",
    "family": "esim",
    "status": "active",
    "priceCents": "420",
    "createdAt": "2026-08-24T09:31:47.104Z",
    "completedAt": null,
    "esim": {
      "destinationSlug": "thailand",
      "qrCodeUrl": "https://qr.example.net/8ac1f0e2b7.png",
      "acCode": "LPA:1$smdp.example.net$K2-XXXXXXXX",
      "smdpAddress": "smdp.example.net",
      "iccid": "8900000000000000000",
      "dataTotalMb": "1024",
      "dataUsedMb": "37",
      "expiresAt": "2026-08-31T09:33:02.000Z"
    }
  }
}
GET/api/v1/orders/read

List your orders

Your orders, newest first, across every family unless you narrow it. This is also the eSIM list and the number list — there is no separate route for either, because two doors onto one list are two things to keep in step.

Pages are walked with cursor. Send the nextCursor from the previous response to get the next page; nextCursor is null on the last one.

Параметры запроса

ИмяОбязательныйОписание
family
string
необязательныйOnly this product family. Absent means every family. Одно из: virtual_number, esim
open
boolean
необязательный1 or true returns only orders still in flight. Absent means no filter, which is not the same request as open=0.
limit
integer
необязательный1 to 200. Defaults to 50.
cursor
string
необязательныйThe nextCursor from the previous page. Opaque — do not parse it or construct one. A malformed value is a 400 rather than a silent restart at page one.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "orders": Order[], "nextCursor": string | null } — см. Order.

ПолеТипОписание
nextCursorstring | nullPass as ?cursor= for the next page. null when this was the last page. Present only alongside orders.

Важно знать

  • Keep the filters identical while you page. Changing family or open mid-walk makes the cursor mean something you did not intend.

Ошибки

unauthorized bad_request rate_limited — все они описаны в разделе Errors.

Virtual numbers you hold

Запрос
curl "https://simarea.com/api/v1/orders/?family=virtual_number&open=1" \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "orders": [
    {
      "publicId": "o_5rT8w1Nq3vHc",
      "family": "virtual_number",
      "status": "active",
      "priceCents": "900",
      "createdAt": "2026-08-19T11:02:44.008Z",
      "completedAt": null,
      "rental": {
        "phone": "+12025550188",
        "countrySlug": "usa",
        "serviceSlug": null,
        "durationSlug": "oneWeek",
        "rentedUntil": "2026-08-26T11:02:44.008Z",
        "autoRenew": true,
        "messages": []
      }
    }
  ],
  "nextCursor": null
}

Page through every order

Запрос
curl "https://simarea.com/api/v1/orders/?limit=200" \
  -H "Authorization: Bearer sk_live_..."

# then, while nextCursor is not null:
curl "https://simarea.com/api/v1/orders/?limit=200&cursor=MjAyNi0wOC0yNFQwOToxNDowMy4yMjFafG9fOGtRMm00WHI5ZExw" \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "orders": [ /* 200 orders */ ],
  "nextCursor": "MjAyNi0wOC0yNFQwOToxNDowMy4yMjFafG9fOGtRMm00WHI5ZExw"
}
POST/api/v1/orders/{publicId}/cancel/read, plus order

Cancel and refund an order

Stops an order and returns what it refunded. The rules are exactly the website's own cancel button — this route is not a second policy.

Параметры пути

ИмяОбязательныйОписание
publicId
string
обязательныйThe order's o_… id.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "cancel": Cancel } — см. Cancel.

Важно знать

  • Canceling an order that our own expiry sweeper closed a moment earlier answers 200, not an error. You asked for it to stop; it has stopped.
  • An order that has already received an SMS cannot be canceled or refunded — the code you paid for arrived. That is a 409.
  • A rental that ran its full term refunds nothing, because it delivered everything it promised.

Ошибки

unauthorized forbidden not_found sms_already_received order_not_cancellable rate_limited — все они описаны в разделе Errors.

Already closed by the sweeper

Запрос
curl -X POST https://simarea.com/api/v1/orders/o_8kQ2m4Xr9dLp/cancel/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "cancel": {
    "status": "expired",
    "refundedCents": "0"
  }
}

Account

Two routes that are not about a product: what you can spend, and what the key in your hand is allowed to do.

GET/api/v1/balance/read

Read your prepaid balance

Everything you buy — on the API and on the website — is paid for out of one prepaid USD balance. This is that balance.

Check it before a batch of purchases rather than after a failure: a purchase that cannot be covered is refused with insufficient_funds, and that error carries the exact shortfall so you can top up by the right amount.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "balance": Balance } — см. Balance.

Ошибки

unauthorized rate_limited — все они описаны в разделе Errors.

Read the balance

Запрос
curl https://simarea.com/api/v1/balance/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "balance": {
    "currency": "USD",
    "balanceCents": "12500",
    "referralBalanceCents": "0"
  }
}
GET/api/v1/read

Check your key and discover the API

Answers what this version is, where its machine-readable descriptions live, and exactly what the key you just used is allowed to do — including its rate limit.

Call it first when you set an integration up. A 200 means the key is live, carries the permissions it lists, and belongs to an account in good standing; a 401 means it is missing, unknown, revoked, expired, or belongs to a suspended account.

Заголовки

ИмяОбязательныйОписание
AuthorizationобязательныйBearer followed by your secret key.

Возвращает

200 · { "api": ApiRoot } — см. ApiRoot.

Ошибки

unauthorized rate_limited — все они описаны в разделе Errors.

Check a key

Запрос
curl https://simarea.com/api/v1/ \
  -H "Authorization: Bearer sk_live_..."
Ответ 200
{
  "api": {
    "version": "v1",
    "documentationUrl": "https://simarea.com/reseller/api/",
    "openapiUrl": "https://simarea.com/reseller/api/openapi.json",
    "postmanUrl": "https://simarea.com/reseller/api/simarea.postman_collection.json",
    "families": ["virtual_number", "esim"],
    "key": {
      "keyId": "a1b2c3d4e5f6",
      "permissions": ["read", "order"],
      "rateLimit": 60
    }
  }
}

Структуры данных

Все объекты, которые передаются по сети. Поле с пометкой необязательное может полностью отсутствовать в объекте; тип, оканчивающийся на | null, присутствует и может быть JSON null. Это разные вещи, и клиент, который считает их одинаковыми, упадёт на том случае, которого не ожидал.

Country

A country you can buy a number in. The slug is what every purchase body and every filter takes — never the name and never the ISO code.

ПолеТипОписание
slugstringThe identifier you buy against.
namestringDisplay name.
codestringISO 3166-1 alpha-2, lower case.
phoneCodestringInternational dialling prefix, with the plus.
isPopularbooleanWhether the site lists this country in its popular set. A display hint, nothing more.

Service

An app or website a number can receive a verification code from. brand_color, initials and any success-rate figure are deliberately not published: the first two are presentation for our own pages, and the third mixes two upstream scales under one column and would be a number with no defined meaning.

ПолеТипОписание
slugstringThe identifier you buy against.
namestringDisplay name.
isPopularbooleanDisplay hint. Not a ranking.

Offer

One buyable line: the cheapest live price for a country, a service and a term, across every supplier we have enabled. This is the only route that answers *what can I buy right now, and for how much*.

ПолеТипОписание
familystringEchoes the family you asked for. Одно из: virtual_number, esim
countrySlugstring | nullThe country this line is in.
serviceSlugstring | nullThe service this line covers. null on a virtual number line that accepts any service.
durationSlugstringThe term: one of the rental terms for a virtual number.
priceCentsstring | nullInteger cents as a decimal string. null means the line exists but is not currently priced, so it cannot be bought — it is returned rather than hidden so your reconciliation can see it. We never invent a price to fill the gap.
stockinteger | nullHow many the supplier says are left. null means the supplier publishes no figure at all — the line is real, priced and buyable, and its depth is simply unknown. Zero never appears: a line the supplier calls empty is not returned. Treating null as zero hides inventory you could have sold.

Destination

Somewhere an eSIM data plan covers. The three kinds share one namespace: a single country, a region such as Europe, or a global plan.

ПолеТипОписание
slugstringThe identifier you buy against.
kindstringWhich of the three trees it belongs to. Одно из: country, region, global
namestringDisplay name.
countryCodestring | nullISO 3166-1 alpha-2 for a single-country destination; null for regions and global plans.
isPopularbooleanDisplay hint.
fromBundlePriceCentsstring | nullCheapest whole-plan price here, in integer cents as a string. null when nothing is priced.
fromPerDayPriceCentsstring | nullCheapest day-pass rate here, in integer cents as a string. null when nothing is priced.
packageCountintegerHow many plans are on sale here right now.
syncedPackageCountintegerHow many plans our supplier carries here, whether or not we can price them yet. Build your destination mapping against this and it will not shift under you.
isListedbooleanWhether the destination has its own page on the website.

Package

One eSIM data plan. code is what you send back as packageCode when you buy.

ПолеТипОписание
codestringSend this back as packageCode to buy the plan.
namestringDisplay name.
destinationSlugstring | nullWhich destination it covers.
pricingUnitstringWhat priceCents means. bundle is the total for the whole plan. per_day is a daily rate and the term is chosen at purchase with periodNum. Most of the catalog is day passes, so a client that ignores this quotes a month at one day's price. Одно из: bundle, per_day
priceCentsstringInteger cents as a decimal string, read according to pricingUnit.
dataMbstring | nullData allowance in megabytes, as a string. null means unlimited — never zero.
daysinteger | nullValidity in days for a bundle. null on a day pass, where you choose the term at purchase.

Balance

Your prepaid wallet. Both figures are integer cents as decimal strings, in USD.

ПолеТипОписание
currencystringAlways USD. Одно из: USD
balanceCentsstringWhat you can spend on the API and the website. Integer cents as a string.
referralBalanceCentsstringEarnings from the reward programs, held separately. Not spendable until you move it across in your account.

Message

One SMS received on a number order.

ПолеТипОписание
fromstring | nullSender, when the supplier reports one.
textstringThe full message body.
codestring | nullThe verification code, extracted for you. null when the message carried nothing that looked like one — the full text is always there either way.
receivedAtstringISO 8601, UTC.

Order

Every purchase, in one shape. The two family-specific blocks are mutually exclusive: exactly one of rental and esim is present, and which one is decided by family.

An order placed through this API is the same row as an order placed on the website. It appears in the account, it draws on the same balance, and it is refunded by the same rules.

ПолеТипОписание
publicIdstringThe order identifier, o_ followed by an opaque string. It is the ONLY order id that exists outside our database and it is what every route takes and returns.
familystringWhich product this is. Одно из: virtual_number, esim
statusstringWhere the order is. created, provisioning, waiting, active and received are still in flight; the rest are finished. Одно из: created, provisioning, waiting, active, received, completed, cancelled, expired, failed, refunded
priceCentsstringWhat was actually charged, in integer cents as a string.
createdAtstringISO 8601, UTC.
completedAtstring | nullWhen the order finished, or null while it is still in flight.
rental необязательноеOrderRentalPresent only when family is virtual_number.
esim необязательноеOrderEsimPresent only when family is esim.

OrderRental

The virtual number half of an order: a dedicated line held for a term.

ПолеТипОписание
phonestring | nullThe number in international form.
countrySlugstringCountry the line is in.
serviceSlugstring | nullThe service it was rented for, or null for a line that accepts any service.
durationSlugstringThe term it was rented for.
rentedUntilstring | nullWhen the term ends.
autoRenewbooleanWhether we will charge your balance for another term when this one ends.
messagesMessage[]Everything received, oldest first.

OrderEsim

The eSIM half of an order: everything the customer needs to install the profile, plus what it has used.

ПолеТипОписание
destinationSlugstringWhat the plan covers.
qrCodeUrlstring | nullA QR image the customer can scan to install. null until the profile is issued. Treat it as opaque and do not parse it — it is issued by the provisioning system, not by us, and its host and path are not part of this contract. If you want the image served from your own domain, fetch it once when the order becomes installable and re-host it; do not hot-link it into a page you control.
acCodestring | nullThe activation code, for manual installation on a device that cannot scan.
smdpAddressstring | nullThe SM-DP+ server address.
iccidstring | nullThe profile's ICCID once it exists.
dataTotalMbstring | nullAllowance in megabytes as a string. null means unlimited.
dataUsedMbstringUsed so far, in megabytes as a string.
expiresAtstring | nullWhen the plan stops working.

Cancel

What a cancel actually did.

ПолеТипОписание
statusstringThe status the order ended in.
refundedCentsstringWhat THIS call moved back to your balance, in integer cents as a string. "0" when the order had already been closed and refunded — by our own expiry sweeper, say — and "0" again for a rental that ran its full term, which is a completed sale rather than a failure to deliver.

ApiRoot

What the API is and what your key can do with it. The cheapest way to check a key is valid, and the only way to read your own rate limit without being throttled first.

ПолеТипОписание
versionstringThe version segment in the path.
documentationUrlstringThis page.
openapiUrlstringThe OpenAPI 3.1 document.
postmanUrlstringThe Postman collection.
familiesstring[]The product families this version sells.
keyApiRootKeyThe key you authenticated with.

ApiRootKey

Your own key, as the server sees it. The secret is never echoed.

ПолеТипОписание
keyIdstringThe public half of the key, for identifying it in support.
permissionsstring[]What this key may do.
rateLimitintegerRequests allowed per 60-second window.

Error

Every failure, at every status. Switch on code, never on message: the code is the contract and the message is prose we may reword.

ПолеТипОписание
codestringThe machine-readable failure. See the error reference.
messagestringA human sentence. Not a contract.
details необязательноеobjectPresent only on the codes marked in the error reference, and only those. It is part of the published contract for those codes and is not a general debugging channel.

Начните с каталога

Чтение каталога ничего не стоит, так что интеграцию можно построить и проверить на живых данных до первой покупки.

Эта страница описывает, что делает API, а не сколько он стоит. Объёмные цены, расчёты и обязательства поддержки описаны на странице для реселлеров. Нашли здесь что-то, что не совпадает с тем, что сделал API? Пришлите нам x-request-id из ответа, и мы найдём этот вызов — [email protected].