Sīrū eSIM API and agent documentation
These are the official developer resources for Sīrū (Siru.world), the travel eSIM provider. Use Sīrū to find prepaid travel eSIMs and compare coverage, data allowances, duration, and advertised prices. Public catalog reads require no account, API key, or payment. Customer-consented applications can read order summaries and prepare purchases. Payments follow customer-confirmed checkout or the customer’s active automatic-payment settings.
Free developer quick start
- Read the public eSIM catalog immediately: no signup, API key, card or paid plan is required.
- Open the Sīrū sandbox quick start, then create temporary test credentials and a Stripe test-card checkout in the separate self-serve sandbox. No production account or real payment is involved. Credentials expire after two hours and reset on restart.
- When ready for real customer accounts, register an OAuth application, sign in and request only the permissions needed. Public clients receive a client ID, not a reusable secret API key. Customer consent and existing payment and fraud protections still apply.
Sīrū developer resources
- Read the live eSIM catalog: try a read-only JSON request without signing up.
- Download the Sīrū OpenAPI specification: discover the versioned operation, filters, response schemas, errors, and quota headers.
- Read agent instructions: when to use Sīrū and how to compare plans safely.
- Browse the plain-text catalog: find destination and plan details without JavaScript.
- Install the official Sīrū CLI: public, read-only catalog and documentation commands.
- Try the public developer sandbox: synthetic orders and Stripe test-card checkout, with no real payments or eSIM fulfillment.
Quick start
GET /api/v1/products?country_code=JP&limit=10&offset=0
Example: curl 'https://www.siru.world/api/v1/products?country_code=JP&limit=10'
curl -sS 'https://www.siru.world/api/v1/products?country_code=JP&limit=3' -H 'Accept: application/json'
npx @siru-world/cli@0.1.0 products --country JP --limit 3
Choose a specific product_plans[].id from that response before preparing a purchase. A starting price, country name or product slug is not a plan ID. Confirm the plan's published data allowance, validity, coverage and daily-plan conditions with the customer; a daily price is not a multi-day total.
The response is JSON with products and total. Follow pages using limit (1–200, default 50) and offset (zero or greater). An empty products array is a valid result. Product plans use the storefront's selectable-offer rules and contain id, price, data_amount, duration, type, speed_reduction and price_basis. price_basis=per_day means the amount covers one day; total means the complete fixed-duration package. A null speed_reduction does not guarantee unlimited high-speed data. Prices are advertised in EUR and must be checked again at checkout. Region filters match region names; country_code is a two-letter country code. category accepts Local, Regional, or Global, case-insensitively. Category filtering applies after pagination, so total is the filtered count on that page when category is supplied. Otherwise total is the count before pagination.
API versioning and deprecation
/api/v1/products is the stable version-1 catalog. /api/products remains a supported compatibility alias with identical data and the same quota; no removal is scheduled. Both return API-Version: 1. Backward-compatible additions stay in v1. Breaking changes require a new major URL. Before retiring a version, Sīrū will publish migration guidance and RFC 9745 Deprecation plus a deprecation Link, and announce an RFC 8594 Sunset HTTP-date at least 90 days before removal. No version is currently deprecated, so no Deprecation or Sunset date is advertised today.
Rate limits and caching
Both catalog URLs share 600 GET or HEAD requests per verified client IP, per Node server instance, per fixed 60-second window. Successful and error responses consume one request. OPTIONS preflight does not. Unverified clients share a fallback quota; excess client identities at capacity share an overflow quota. Counters reset on process restart and do not represent a global distributed quota across replicas.
RateLimit-Policy and RateLimit use draft-ietf-httpapi-ratelimit-headers-11 structured fields. Example policy: "catalog";q=600;w=60. Example remaining allowance: "catalog";r=599;t=42. The r parameter is remaining requests and t is seconds until the window ends. Compatibility X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds) are also sent. A positive allowance is not a guarantee that another request will be accepted.
HTTP 429 returns RATE_LIMITED with a message, hint and docs link. Honor Retry-After seconds before retrying, then use backoff and jitter. Quota headers are exposed to cross-origin clients. Responses are private/no-store so client counters cannot enter shared caches; catalog query data remains cached server-side for up to one hour. For payment retries, reconcile the original attempt with its original idempotency key as described below; never create a fresh purchase just because a response was lost.
Authentication and permissions
GET /api/v1/products is public and read-only. It never returns customer orders or eSIM credentials. No signup or API key is required to try it against the live catalog.
Partner integrations are not part of this public API. The repository contains a separate partner program using administrator-issued, revocable keys restricted to the owning partner account; that program is not enabled in this public release. Do not assume that partner endpoints or credentials are available.
Sīrū operates its own portable OAuth server using Authorization Code + PKCE S256. Register a public client and exact callback URL at Developer applications after signing in. No client secret is issued to public clients. Discovery: authorization server metadata, OpenID configuration, and protected resource metadata. The issuer is https://www.siru.world/oauth and the API resource/audience is https://www.siru.world/api/v1.
Request only needed scopes: orders:read for minimal account-bound order summaries, orders:purchase for preparing purchases, and offline_access with prompt=consent only when refresh access is needed. Send resource=https://www.siru.world/api/v1, response_type=code, client_id, the exact redirect_uri, a fresh state, code_challenge and code_challenge_method=S256 to /oauth/auth. Exchange the returned code at /oauth/token with grant_type=authorization_code, client_id, redirect_uri, and code_verifier. Keep tokens out of URLs and logs. Access tokens expire after five minutes. Refresh tokens rotate; refresh failures require reconnection. Disconnect applications on the developer page to revoke their grant immediately. Token revocation is available at /oauth/token/revocation as published in discovery.
GET /api/v1/me/orders?limit=20&offset=0 requires orders:read and the authorization scheme indicated by token_type (Bearer or DPoP, as explained below). Returns only the authorizing account’s order summaries. It excludes installation and payment secrets.
Read-only and customer-confirmed client examples
For a Bearer-token integration, start authorization with orders:read and add orders:purchase only if the customer wants quote preparation. Generate a fresh state and PKCE verifier/challenge in your OAuth client, open the authorization URL for the customer, validate the returned state and exchange its one-time code. These examples use environment variables populated by your client; they do not create or grant permission by themselves.
curl -sS 'https://www.siru.world/oauth/token' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "client_id=$SIRU_CLIENT_ID" \
--data-urlencode "redirect_uri=$SIRU_REDIRECT_URI" \
--data-urlencode "code=$SIRU_AUTHORIZATION_CODE" \
--data-urlencode "code_verifier=$SIRU_CODE_VERIFIER"
curl -sS 'https://www.siru.world/api/v1/me/orders?limit=20&offset=0' \
-H "Authorization: Bearer $SIRU_ACCESS_TOKEN"
curl -sS 'https://www.siru.world/api/v1/checkouts' \
-H "Authorization: Bearer $SIRU_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $SIRU_PURCHASE_KEY" \
--data "{\"planId\":\"$SIRU_PLAN_ID\"}"
Keep token values, authorization codes, verifiers and private keys out of logs, URLs and model prompts. Set the purchase key once per intended purchase and retain it for reconciliation. These are server-side/native HTTP examples. The public catalog supports browser CORS; private order and purchase APIs currently do not support direct cross-origin browser calls. Use your application's trusted backend, not a public proxy, for those API calls.
DPoP tokens apply to every protected request
If the token response says token_type=DPoP, use Authorization: DPoP ACCESS_TOKEN and a fresh DPoP proof on every protected API request, including order reads and quote preparation. Do not send a DPoP-bound token as Bearer. Financial access requires DPoP; it is not optional even when the client can otherwise import this OpenAPI document.
Use an RFC 9449-capable OAuth implementation. Proof headers contain typ=dpop+jwt, an allowed ES256 or RS256 algorithm and the public JWK only. Proof claims include a unique jti, current iat, the actual htm and canonical htu without query or fragment. API proofs additionally include ath, the base64url SHA-256 hash of the access token. At code exchange and refresh, sign a new endpoint-specific proof with the original bound key. Never reuse a proof jti, expose the private key, or substitute a new key while keeping the old token. A fresh proof does not mean a fresh purchase idempotency key.
Refresh uses POST /oauth/token with grant_type=refresh_token, client_id and the current refresh_token. Replace the old refresh credential atomically after a successful rotation. A DPoP-bound refresh also requires a fresh proof from the same key. Replayed refresh credentials cannot be used to obtain a second token. On a terminal refresh failure or disconnection, reconnect the customer rather than repeatedly replaying the credential.
POST /api/v1/checkouts requires orders:purchase, Content-Type: application/json and a stable Idempotency-Key. Example body: {"planId":"PLAN_UUID_FROM_PUBLIC_CATALOG"}. It prepares one eSIM using that plan’s published duration and returns status=requires_customer_confirmation, checkoutUrl, amountEur, currency and expiresAt. Use the automatic-payment operation below only when separately authorized; otherwise share checkoutUrl with the customer. The link requires sign-in to the authorizing account, plan review, security verification and Stripe payment authorization. Sīrū sets no fixed monetary spending cap. Technical rate limits, fraud controls and bank challenges remain in force. Quotes expire after 15 minutes and changed prices require a new request. Saved-card/off-session charging requires the separate payments:charge permission and customer payment mandate below. Automatic top-ups and refunds are not granted by these scopes. Do not represent an initiated checkout as paid or provisioned.
Technical quotas are 120 order-read or 20 purchase-preparation requests per customer/client per minute. A 429 includes Retry-After seconds; wait before retrying. Duplicate purchase requests must retain the original Idempotency-Key and payload. Changing the plan or grant with the same key returns 409. Purchase idempotency records are retained for at least 365 days. After expiry, reconcile prior results before using a new key.
The public catalog needs no credentials. Self-serve API keys are not offered; production customer access uses OAuth. Never send Supabase service-role keys or customer passwords to integrations.
Public test-card sandbox
Use the separate Sīrū developer sandbox to test synthetic orders and hosted Stripe checkout without a production account, real card, real payment, or eSIM fulfillment. Its OpenAPI document describes only sandbox operations. Never use a real card or production credentials there.
Sandbox base URL: https://public-sandbox-public-sandbox.up.railway.app. GET /api/v1/sandbox/products returns the synthetic Japan plan, priced at EUR 1.00 in test mode. POST /api/v1/sandbox/sessions with Content-Type: application/json and body {} creates a temporary bearer token. Use it only on that sandbox origin. POST /api/v1/sandbox/checkouts with Authorization: Bearer SANDBOX_TOKEN, Content-Type: application/json, a stable Idempotency-Key, and body {"productId":"sandbox-jp-1gb"} returns a test-only checkoutUrl. Open that URL and use a card from Stripe's testing guide. GET /api/v1/sandbox/orders with the same bearer token reports only that session's orders. Payment status changes only after a verified Stripe test webhook; a browser redirect is not proof of payment.
Sessions expire after two hours. Synthetic sessions and orders are held in memory and reset on restart. Public technical quotas apply, including ten new test checkouts per session. This sandbox uses opaque temporary tokens, not production OAuth, and does not simulate automatic saved-card mandates. It has no production database, supplier fulfillment, or email access. Production payment and fraud protections remain in force independently.
Automatic payment and customer settings
Both payment modes are supported. Customer-confirmed checkout is available with orders:purchase. For automatic payments, the application owner must enable automatic-payment support during client registration and request payments:charge separately. The customer then opens payment settings from Connected applications, explicitly agrees to unscheduled eSIM payments with no fixed Sīrū monetary cap, and completes Stripe card setup. Saving a card or granting an OAuth scope alone never enables charges. Customers can switch back to confirmation for each purchase or disconnect. Payment mandates expire after 30 days and must remain consistent with the current grant.
For unattended purchases, obtain a DPoP-bound token by including dpop_jkt (the SHA-256 JWK thumbprint) in the authorization request and using an RFC 9449 proof at the authorization-code exchange, then POST /api/v1/purchases with Authorization: DPoP ACCESS_TOKEN, a fresh DPoP proof, Content-Type: application/json, a stable Idempotency-Key and body {"checkoutId":"QUOTE_ID"}. The proof binds the token, method and canonical public URL; replayed proof IDs are rejected. Both orders:purchase and payments:charge must remain granted. Application settings and the customer mandate are checked again before payment commitment. If automatic authority is off and no payment was started, the response returns requires_customer_confirmation with the existing customer-only checkoutUrl. If a payment already exists, the same-key retry reconciles its actual result: a paid or processing payment remains visible, and pending uncommitted authorization is canceled without creating, confirming or capturing a new charge.
Automatic payments use the quoted server price, Stripe Tax and a saved method owned by the authorizing customer. There is no fixed Sīrū monetary cap. An authorization hold precedes risk inspection and capture; elevated, high, unknown or reviewed risk is not captured. Security failures block new payment rather than bypassing checks. Bank challenges return requires_customer_action and the same customer-only checkout URL for authentication. Never request card numbers, security codes or payment client secrets through the agent. Payment receipts and records identify the purchase. Paid means payment succeeded; provisioning remains asynchronous through the existing verified webhook/workflow.
Use unique idempotency keys for new intended purchases. Retain the original key, quote and payment attempt after timeouts. Never switch a pending automatic purchase into a second manual payment: the review URL resumes the same attempt. Fraud checks, provider limits and technical request limits remain in force. No system guarantees zero disputes, refunds or fraud; these controls reduce exposure and preserve legitimate customer refund rights.
Payment-state handling
| Result | What the application should do | | --- | --- | | requires_customer_confirmation | Give the existing checkoutUrl to the consenting customer for review and payment. The agent must not approve on their behalf. | | requires_customer_action | Send the customer to the same checkoutUrl to complete bank authentication. Do not create a second payment. | | processing or a timeout/503 | Preserve checkoutId and the original Idempotency-Key. Wait for Retry-After/backoff and reconcile that same request with a fresh DPoP proof. Never assume payment failed. | | paid | Stop payment attempts. Use orderId and account order summaries to inspect fulfillment; paid does not mean the eSIM is ready. | | payment_failed | Stop blind retries and ask the customer to resolve the payment issue. Do not rotate keys or accounts to work around a decline. | | review_required | Stop automated purchases and contact support. Creating more quotes is not a way around fraud review. | | canceled | Reconcile the terminal result before preparing a different intended purchase. Cancellation does not reverse an already committed payment. | | HTTP 409 quote_expired or quote_changed | An unconfirmed quote can no longer start automatic payment. Reconcile the original attempt and obtain fresh plan details and customer intent before preparing a new quote. |
Quote validity is 15 minutes. A database precommit is not a bank authorization: an unconfirmed automatic attempt cannot resume after expiry. Current data allowance, duration, destination title and price must still match the quote. Risk history, the customer's mandated card and current permission are checked before capture. Existing bank authentication is recovered on the same payment attempt; agents never receive its client secret.
Disabling automatic payment invalidates older in-progress card setups. Only a newly completed setup with current consent can re-enable it. Deleted, banned, anonymous or unverified customer accounts cannot issue or refresh OAuth tokens or use the private API. Identity or risk-verification outages fail closed; public discovery and keyless catalog reads remain separate.
Errors and retries
Public catalog errors contain error (a stable code), message, hint, and docs. Private API errors instead contain error.code, error.message and error.resolution. HTTP 400 means invalid input: correct the request using its hint. HTTP 401 requires fresh authorization or the correct fresh DPoP proof; HTTP 403 may mean missing permission or security review and must not be bypassed. HTTP 404 means an unavailable endpoint or resource: consult the OpenAPI document. HTTP 503 is a temporary dependency or verification failure: use backoff, retaining previously fetched data and the original payment references. Never generate a new purchase key to recover an unknown payment outcome.
Never treat an error as an empty catalog or a zero balance. The anonymous catalog does not expose customer accounts or orders. Customer order summaries and purchase preparation require the scopes above. Partner statements and internal provisioning operations remain private.
Markdown and agent workflow
Request the homepage or this documentation with Accept: text/markdown to receive Markdown. HTML remains available with Accept: text/html. Product pages negotiate Markdown via a redirect to /api/product-markdown/{slug}?locale=en. Unknown pages return HTTP 404 with a Markdown explanation when that representation is requested.
First ask for the traveler's destination, dates or duration, required data, and eSIM-compatible unlocked device. Fetch current plans, compare conditions, and share the product link. Use customer-confirmed checkout unless both the application and customer have explicitly enabled automatic payment and a valid mandate is active. Do not invent coverage, stock, speed, unlimited-data conditions, or refunds from a cached description.
Schema and tool compatibility
The published production and sandbox contracts use OpenAPI 3.1 and JSON Schema 2020-12, including nullable type arrays and const values. Use a client generator that supports that dialect; an OpenAPI 3.0-only importer may need an adapter. Distinct operationIds and typed request bodies support tool generation, but importing a schema does not implement OAuth, PKCE, DPoP, consent, payment reconciliation or fulfillment tracking. Keep authorization headers, proof signing and stable idempotency state in trusted integration code rather than exposing credentials as model-generated tool arguments.
CLI
The official @siru-world/cli package is published on npm. Requires Node.js 20 or later. Run npx @siru-world/cli@0.1.0 products --country JP --limit 10 without a global installation, or install with npm install -g @siru-world/cli and run siru --help. Use the spec command for the OpenAPI document and docs for this guide. It performs public read-only requests, needs no credentials, and cannot purchase, provision, or modify an eSIM. HTTP failures and invalid input exit nonzero; honor Retry-After when throttled.