Quick start
1. Get an API key
Create a key in the Dashboard. While creating it you choose two things that will shape every later call:
- Scopes — which sections the key may touch (
product:read,orders:write, …). - IP allowlist — optional. If set, requests from other addresses get HTTP 403.
Copy the token immediately: it is stored hashed and cannot be shown again.
2. Verify the key
GET /whoami is the cheapest possible authenticated call. It answers "is my key
working, what can it do, and which IP do you see me from?".
- cURL
- Python
- JavaScript
curl https://approute.io/api/v1/whoami \
-H "X-API-Key: $APPROUTE_API_KEY"
from approute_public_api_sdk import Client
client = Client(api_key="...", base_url="https://approute.io/api/v1")
print(client.whoami())
import {Client} from '@approute/public-api-sdk';
const client = new Client({apiKey: '...', baseUrl: 'https://approute.io/api/v1'});
console.log(await client.whoami());
A healthy response:
{
"status": "SUCCESS",
"statusCode": 0,
"statusMessage": "OK",
"traceId": "0f2c...",
"data": {
"clientIp": "203.0.113.42",
"scopes": ["product:read", "orders:write"]
},
"errors": null
}
:::tip Allowlist debugging
data.clientIp is the address we resolve for you, after proxies. If your
key has an IP allowlist and you are getting 403, compare it against what you put
in the allowlist — a dual-stack host that quietly egresses over IPv6 is the
usual culprit.
:::
3. Read the catalog
curl "https://approute.io/api/v1/services?limit=5" \
-H "X-API-Key: $APPROUTE_API_KEY"
Catalog reads need product:read. The envelope status on any list endpoint is
always SUCCESS — it means "request served", nothing about the items inside.
4. Dry-run an order
Before spending money, send the same purchase with checkOnly: true. You get
back price and availability without creating an order.
curl -X POST https://approute.io/api/v1/orders \
-H "X-API-Key: $APPROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ordersType": "dtu",
"checkOnly": true,
"orders": [{"denominationId": "...", "amount": 1}]
}'
:::warning Content-Type is enforced
POST, PUT and PATCH without Content-Type: application/json are rejected
with HTTP 415 before any validation runs.
:::
5. Place the real order
Flip checkOnly to false and add a referenceId — your own unique string for
this purchase. It is what makes the call safe to retry.
curl -X POST https://approute.io/api/v1/orders \
-H "X-API-Key: $APPROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ordersType": "dtu",
"checkOnly": false,
"referenceId": "order-2026-07-27-0001",
"orders": [{"denominationId": "...", "amount": 1}]
}'
Then branch on data.status, not on the HTTP code:
data.status | What it means | What to do |
|---|---|---|
SUCCESS | Delivered | Done |
IN_PROGRESS | Still working (HTTP 202 + Retry-After) | Poll GET /orders |
CANCELLED | Failed | Read statusMessage / errors |
Re-sending the same referenceId returns the original result with
statusCode: 2 — no second purchase. See Idempotency.
6. Poll and retrieve the result
Use the original API key with orders:read and the same referenceId. For a
shop order, unhide=true returns full voucher codes when they become available;
DTU orders return their status and top-up result without voucher codes.
curl -i --get https://approute.io/api/v1/orders \
-H "X-API-Key: $APPROUTE_API_KEY" \
--data-urlencode 'referenceId=order-2026-07-27-0001' \
--data-urlencode 'unhide=true'
Inspect data.page.items[].status. The list's outer status: SUCCESS means the
read succeeded; an item may still be IN_PROGRESS.
- You can keep
unhide=truethroughout polling. The default limit is 60 requests / 60 seconds per order forIN_PROGRESSandPARTIALLY_COMPLETED, shared by workers reading the same order. SUCCESSandCANCELLEDuse a separate 1 request / 60 seconds per order allowance. Earlier pending polls do not spend it. Repeated completed reads, including cached codes, consume this same allowance.- Save the result and stop polling on
SUCCESS,CANCELLEDorPARTIALLY_COMPLETED. The latter is terminal partial delivery and may already contain codes despite using the pending budget. - On 429, wait for
Retry-Afterbefore retrying the same order. A lost completed response may require waiting for the next window.
For status-only reads, omit unhide or set it to false; codes are masked and
the filtered account-wide budget applies. See Rate limits
for effective settings, checkout scopes and the additional admission limit.
Where to go next
- Authentication — scopes, allowlists, transaction limits.
- Response envelope — the three status surfaces, in detail.
- Errors and retries — which failures are worth retrying.
- API Reference — every endpoint, with "Try it".