Orders
Create voucher orders, track delivery, and retrieve voucher codes
Orders are how you purchase digital gift cards through the Octopus Cards API. You submit an order with a product, denomination, and quantity - Octopus Cards handles fulfillment and delivers voucher codes, PINs, or claim URLs back to you.
For gaming top-ups and mobile recharges, see Topup Orders. For eSIM purchases, see eSIM Orders.
Order Lifecycle
1. Calculate charges POST /api/v1/products/:id/charges
2. Create order POST /api/v1/orders
3. Poll for delivery GET /api/v1/orders/:id
4. Retrieve vouchers (included in order response)Sync vs Async Delivery
Orders are fulfilled in one of two modes depending on quantity:
| Mode | Condition | Behaviour |
|---|---|---|
| Synchronous | Quantity ≤ 5 | Vouchers are returned directly in the create order response |
| Asynchronous | Quantity > 5 | Order is created with status PENDING. A background job processes the order and delivers vouchers. Poll the order endpoint to check status. |
For async orders, the response returns immediately with status: PENDING and an empty vouchers array. Use GET /api/v1/orders/:id to check when delivery is complete.
Order Statuses
| Status | Description |
|---|---|
PENDING | Order placed, vouchers are being processed |
DELIVERED | All vouchers have been successfully delivered |
PARTIALLY_DELIVERED | Some vouchers delivered, others still pending or failed |
FAILED | Order could not be fulfilled |
CANCELLED | Order was cancelled |
Status Transitions
Orders follow a predictable lifecycle. The diagram below shows all valid transitions:
┌──────────┐
│ PENDING │
└────┬─────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌───────────┐ ┌────────┐ ┌──────────┐
│ DELIVERED │ │ FAILED │ │CANCELLED │
└───────────┘ └────────┘ └──────────┘
▲
│
┌──────────┴───────────┐
│ PARTIALLY_DELIVERED │
└──────────────────────┘| From | To | When |
|---|---|---|
PENDING | DELIVERED | All vouchers fulfilled successfully |
PENDING | PARTIALLY_DELIVERED | Some vouchers delivered, others still in progress |
PENDING | FAILED | Fulfillment failed entirely |
PENDING | CANCELLED | Order cancelled before fulfillment |
PARTIALLY_DELIVERED | DELIVERED | Remaining vouchers fulfilled |
PARTIALLY_DELIVERED | FAILED | Remaining vouchers could not be fulfilled |
DELIVERED, FAILED, and CANCELLED are terminal statuses - once an order reaches one of these, it will not change again. PARTIALLY_DELIVERED is the only non-terminal status besides PENDING.
Voucher Delivery
When an order is DELIVERED or PARTIALLY_DELIVERED, the vouchers are included in the order response. Each voucher may contain:
| Field | Description |
|---|---|
card_number | The voucher code (e.g. gift card number) |
pin_code | PIN associated with the voucher (if applicable) |
claim_url | A URL to redeem the voucher (for URL-based delivery) |
expires_at | Expiry date of the voucher (RFC 3339) |
voucher_reference_number | Reference code for the voucher |
Not all fields are present on every voucher - it depends on the product's delivery mode:
| Delivery Mode | Fields Present |
|---|---|
Code with PIN | card_number, pin_code, expires_at, voucher_reference_number |
URL | claim_url, pin_code (optional), expires_at |
Voucher codes and PINs are encrypted at rest and decrypted on-the-fly when you retrieve the order. Store them securely on your side - they represent real monetary value.
Balance & Wallet Checks
Before an order is created, Octopus Cards verifies:
- Wallet exists - you must have a wallet in the product's currency (or specify a
wallet_idfor cross-currency orders with FX conversion) - Sufficient balance - your wallet balance must be ≥ the total payable amount (from the charges endpoint)
- Atomic deduction - the wallet is debited in the same database transaction as the order creation, ensuring consistency
If the balance is insufficient, the order is rejected with a 400 error and no funds are moved.
Quantity Limits
| Limit | Description |
|---|---|
| Bulk limit | Your client account has a configurable maximum quantity per order. Check max_quantity from the charges endpoint. |
| Absolute max | Code-based orders are capped at 5,000 vouchers per request |
Identifying an order
Every order is identified by two values:
id(server-issued integer) — the canonical handle forGET /api/v1/orders/:id.client_reference(client-supplied string, optional, ≤ 255 ASCII chars) — your dedup + lookup key. Send the same value on two create requests and the second returns400 "Duplicate client_reference". Filter the list endpoint with?client_reference=…to fetch your order without keeping theidaround. See Idempotency.
GET /api/v1/orders
Returns a paginated list of all your orders, sorted by most recent first.
Request
curl "https://api.octopuscards.io/api/v1/orders?page=1&limit=25" \
-H "Authorization: Bearer <token>"Query Parameters
| Key | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-based) |
limit | integer | 50 | Items per page (1–10,000) |
client_reference | string | - | Filter by exact client_reference value (max 255 chars) |
Look up by client_reference
To find an order by the client_reference you supplied on create, pass it as a query parameter:
curl "https://api.octopuscards.io/api/v1/orders?client_reference=MY_ORDER_001" \
-H "Authorization: Bearer <token>"This is an exact match. The (client_id, client_reference) uniqueness constraint guarantees at most one result.
Response
[
{
"id": 1234,
"product_id": 123,
"product_name": "Steam Wallet Card",
"client_reference": "MY_ORDER_001",
"transaction_id": 5678,
"denomination": 50.00,
"quantity": 5,
"amount": 250.00,
"currency": "USD",
"status": "DELIVERED",
"email": "recipient@example.com",
"placed_at": "2025-01-15T14:30:00Z"
},
{
"id": 1235,
"product_id": 456,
"product_name": "Google Play Gift Card",
"client_reference": "BULK_ORDER_001",
"transaction_id": 5679,
"denomination": 25.00,
"quantity": 100,
"amount": 2500.00,
"currency": "GBP",
"status": "PENDING",
"email": "",
"placed_at": "2025-01-15T15:00:00Z"
}
]Response Headers
| Header | Description |
|---|---|
X-Page | Current page number |
X-Per-Page | Items per page |
X-Total-Count | Total orders |
X-Total-Pages | Total pages |
X-Page-Size | Items in current page |
X-Has-More | true if more pages exist |
Response Fields
| Key | Type | Description |
|---|---|---|
id | integer | Unique order identifier |
product_id | integer | Product that was ordered |
product_name | string | Product display name |
client_reference | string | Echo of the value you supplied on create (omitted if you didn't supply one) |
transaction_id | integer | Associated wallet transaction ID |
denomination | number | Face value per voucher |
quantity | integer | Number of vouchers ordered |
amount | number | Total face value |
currency | string | Product's currency code |
status | string | PENDING, DELIVERED, PARTIALLY_DELIVERED, FAILED, or CANCELLED |
email | string | Delivery email (if provided) |
placed_at | string | Order creation timestamp (RFC 3339) |
Errors
404 Not Found - No orders match the query.
{
"error": {
"name": "NotFoundError",
"code": "NOT_FOUND",
"message": "No Matching Result Found!"
}
}GET /api/v1/orders/:id
Returns full details for a single order, including all delivered voucher codes and PINs.
Request
curl "https://api.octopuscards.io/api/v1/orders/1234" \
-H "Authorization: Bearer <token>"Request Parameters
| Key | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Order ID (path parameter) |
Response
{
"id": 1234,
"client_reference": "MY_ORDER_001",
"transaction_id": 5678,
"product_id": 123,
"product_name": "Steam Wallet Card",
"denomination": 50.00,
"quantity": 5,
"discount": 8.75,
"amount": 250.00,
"status": "DELIVERED",
"placed_at": "2025-01-15T14:30:00Z",
"email": "recipient@example.com",
"base_currency": "USD",
"deduction_currency": "USD",
"message": "Your order has been delivered successfully.",
"vouchers": [
{
"card_number": "STEAM-XXXX-XXXX-XXXX",
"pin_code": "1234",
"claim_url": null,
"expires_at": "2027-03-25T00:00:00Z",
"voucher_reference_number": "VCH-001"
},
{
"card_number": "STEAM-YYYY-YYYY-YYYY",
"pin_code": "5678",
"claim_url": null,
"expires_at": "2027-03-25T00:00:00Z",
"voucher_reference_number": "VCH-002"
}
]
}Response Fields
| Key | Type | Description |
|---|---|---|
id | integer | Unique order identifier |
client_reference | string | Echo of the value you supplied on create (omitted if you didn't supply one) |
transaction_id | integer | Associated wallet transaction ID |
product_id | integer | Product that was ordered |
product_name | string | Product display name |
denomination | number | Face value per voucher |
quantity | integer | Number of vouchers ordered |
discount | number | Total discount applied |
amount | number | Total face value |
status | string | Current order status |
placed_at | string | Order creation timestamp (RFC 3339) |
email | string | Delivery email |
base_currency | string | Product's currency code |
deduction_currency | string | Wallet's currency code |
message | string | Human-readable status message |
vouchers | array | Delivered vouchers (empty if order is still PENDING) |
Voucher Fields
| Key | Type | Description |
|---|---|---|
card_number | string or null | Voucher code / gift card number |
pin_code | string or null | PIN for the voucher |
claim_url | string or null | Redemption URL (for URL-based products) |
expires_at | string or null | Voucher expiry date (RFC 3339) |
voucher_reference_number | string or null | Brand reference |
Vouchers are only included when the order status is DELIVERED or PARTIALLY_DELIVERED. For PENDING orders, the vouchers array is empty - poll this endpoint until delivery completes.
Errors
400 Bad Request - ID is not a valid integer.
{
"error": {
"name": "BadRequestError",
"code": "BAD_REQUEST",
"message": "No Matching Result Found!"
}
}PATCH /api/v1/orders/:id/notification-email
Attaches (or updates) the email address that will be notified when the order reaches a terminal status. Only accepted while the order is still in flight — once the order reaches a terminal state (DELIVERED, PARTIALLY_DELIVERED, FAILED, or CANCELLED), the email is locked.
Use this when your customer enters their email after placing the order (e.g. in a "where should we send the receipt?" follow-up screen).
Request
curl -X PATCH "https://api.octopuscards.io/api/v1/orders/9123456/notification-email" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"email": "customer@example.com"
}'Request Parameters
| Key | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Order ID (path parameter). |
email | string | Yes | Email address. Max 254 characters. |
Response
{
"success": true,
"message": "Notification email updated successfully"
}Response Fields
| Key | Type | Description |
|---|---|---|
success | boolean | Always true on success. |
message | string | Confirmation string. |
Errors
400 Bad Request — email missing, empty, or too long.
{
"error": {
"name": "ValidationException",
"code": "VALIDATION_FAILURE",
"message": "email is required"
}
}Other messages: "email is too long (max 254 characters)", "Invalid order ID", "Invalid request body".