Octopus Cards

Create Order

Place a voucher order and receive gift card codes

POST /api/v1/orders

Create a new voucher order. The platform validates your request, checks wallet balance, deducts funds, and fulfills the order — either immediately (sync) or in the background (async).

Request

curl -X POST "{{host}}/api/v1/orders" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": 123,
    "denomination": 50.00,
    "quantity": 5,
    "ref": "MY_ORDER_001",
    "client_reference": "CAMPAIGN_Q1_2025",
    "email": "recipient@example.com"
  }'
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

type CreateOrderRequest struct {
    ProductID       int     `json:"product_id"`
    Denomination    float64 `json:"denomination"`
    Quantity        int     `json:"quantity"`
    WalletID        *int    `json:"wallet_id,omitempty"`
    Ref             string  `json:"ref,omitempty"`
    ClientReference string  `json:"client_reference,omitempty"`
    Email           string  `json:"email,omitempty"`
}

type Voucher struct {
    CardNumber             *string `json:"card_number"`
    PinCode                *string `json:"pin_code"`
    ClaimURL               *string `json:"claim_url"`
    ExpiresAt              *string `json:"expires_at"`
    VoucherReferenceNumber *string `json:"voucher_reference_number"`
}

type OrderResponse struct {
    ID                int       `json:"id"`
    ProductID         int       `json:"product_id"`
    ProductName       string    `json:"product_name"`
    Denomination      float64   `json:"denomination"`
    Quantity          int       `json:"quantity"`
    Amount            float64   `json:"amount"`
    Discount          float64   `json:"discount"`
    Ref               string    `json:"ref"`
    ClientReference   string    `json:"client_reference"`
    WalletID          int       `json:"wallet_id"`
    Status            string    `json:"status"`
    BaseCurrency      string    `json:"base_currency"`
    DeductionCurrency string    `json:"deduction_currency"`
    Message           string    `json:"message"`
    Email             string    `json:"email"`
    Vouchers          []Voucher `json:"vouchers"`
}

func main() {
    body, _ := json.Marshal(CreateOrderRequest{
        ProductID:    123,
        Denomination: 50.00,
        Quantity:     5,
        Ref:          "MY_ORDER_001",
    })

    req, _ := http.NewRequest("POST", "{{host}}/api/v1/orders", bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer <token>")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    var order OrderResponse
    json.NewDecoder(resp.Body).Decode(&order)

    fmt.Printf("Order #%d%s (%s)\n", order.ID, order.Status, order.Message)
    for _, v := range order.Vouchers {
        if v.CardNumber != nil {
            fmt.Printf("  Code: %s / PIN: %s\n", *v.CardNumber, *v.PinCode)
        }
        if v.ClaimURL != nil {
            fmt.Printf("  URL: %s\n", *v.ClaimURL)
        }
    }
}

Request Parameters

KeyTypeRequiredDescription
product_idintegerYesProduct ID from the catalog. Must be an active, non-blacklisted product.
denominationnumberYesFace value per voucher. Must be greater than 0 and fall within an available denomination range for the product.
quantityintegerYesNumber of vouchers to order. Minimum 1, maximum is your client's bulk limit (check max_quantity from the Charges endpoint). Absolute cap: 5,000 per request.
wallet_idintegerNoWallet to debit. If omitted, the platform uses your wallet matching the product's currency. Specify this for cross-currency orders (triggers FX conversion).
refstringNoYour unique reference code for this order. If omitted, a UUID is generated. Must be unique — duplicate references are rejected.
client_referencestringNoYour internal tracking reference (max 255 characters). Does not need to be unique.
emailstringNoRecipient email address. Used for voucher delivery notifications. Must be a valid email format.

Response (Sync — quantity ≤ 5)

When quantity is 5 or fewer, vouchers are delivered immediately:

{
  "id": 1234,
  "product_id": 123,
  "product_name": "Steam Wallet Card",
  "denomination": 50.00,
  "quantity": 5,
  "amount": 250.00,
  "discount": 8.75,
  "ref": "MY_ORDER_001",
  "client_reference": "CAMPAIGN_Q1_2025",
  "wallet_id": 1,
  "status": "DELIVERED",
  "base_currency": "USD",
  "deduction_currency": "USD",
  "message": "Order created successfully",
  "email": "recipient@example.com",
  "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 (Async — quantity > 5)

For larger orders, the response returns immediately with PENDING status and no vouchers:

{
  "id": 1235,
  "product_id": 123,
  "product_name": "Steam Wallet Card",
  "denomination": 50.00,
  "quantity": 100,
  "amount": 5000.00,
  "discount": 175.00,
  "ref": "BULK_ORDER_001",
  "client_reference": "CAMPAIGN_Q1_2025",
  "wallet_id": 1,
  "status": "PENDING",
  "base_currency": "USD",
  "deduction_currency": "USD",
  "message": "Order created successfully",
  "email": "recipient@example.com",
  "vouchers": []
}

Poll GET /api/v1/orders/1235 to check when delivery is complete.

Response (Cross-currency order)

When paying from a different currency wallet:

{
  "id": 1236,
  "product_id": 456,
  "product_name": "Google Play Gift Card (UK)",
  "denomination": 25.00,
  "quantity": 2,
  "amount": 50.00,
  "discount": 1.50,
  "ref": "FX_ORDER_001",
  "wallet_id": 2,
  "status": "DELIVERED",
  "base_currency": "GBP",
  "deduction_currency": "EUR",
  "message": "Order created successfully",
  "vouchers": [
    {
      "card_number": "GPLAY-XXXX-XXXX",
      "pin_code": null,
      "claim_url": "https://play.google.com/redeem?code=XXXX",
      "expires_at": "2027-06-01T00:00:00Z",
      "voucher_reference_number": null
    }
  ]
}

base_currency is the product's currency (GBP). deduction_currency is the wallet's currency (EUR). The FX conversion details are recorded in the wallet transaction.

Response Fields

KeyTypeDescription
idintegerUnique order identifier
product_idintegerProduct that was ordered
product_namestringProduct display name
denominationnumberFace value per voucher
quantityintegerNumber of vouchers ordered
amountnumberTotal face value (denomination × quantity)
discountnumberTotal discount applied
refstringOrder reference code (yours or auto-generated UUID)
client_referencestringYour internal reference (if provided)
wallet_idintegerWallet that was debited
statusstringOrder status — see Order Statuses
base_currencystringProduct's currency code
deduction_currencystringWallet's currency code (differs from base_currency in cross-currency orders)
messagestringHuman-readable status message
emailstringDelivery email (if provided)
vouchersarrayDelivered vouchers (empty if PENDING)

Voucher Fields

KeyTypeDescription
card_numberstring or nullVoucher code / gift card number
pin_codestring or nullPIN for the voucher
claim_urlstring or nullRedemption URL (for URL-based delivery)
expires_atstring or nullVoucher expiry date (RFC 3339)
voucher_reference_numberstring or nullSupplier reference for the voucher

What Happens Step by Step

  1. Validate — request fields, product existence, denomination range
  2. Check duplicates — reject if ref was already used
  3. Select wallet — use wallet_id or find the wallet matching the product's currency
  4. Calculate charges — apply discounts and FX conversion (same as the charges endpoint)
  5. Check balance — verify wallet has enough funds
  6. Debit wallet — atomically deduct the total payable amount and create a transaction record
  7. Create order — insert the order record
  8. Fulfill — for sync orders (≤ 5 qty), fetch voucher codes immediately. For async (> 5 qty), queue for background processing.
  9. Respond — return the order with vouchers (sync) or pending status (async)

Errors

400 Bad Request — Missing or invalid required fields.

{
  "error": {
    "name": "ValidationException",
    "code": "VALIDATION_FAILURE",
    "message": "Invalid product_id: required"
  }
}

400 Bad Request — The reference code has already been used.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Duplicate reference code"
  }
}

Each ref must be unique across all your orders. Use a different reference or omit it to auto-generate.

404 Not Found — Product does not exist or is not available to your client.

{
  "error": {
    "name": "NotFoundError",
    "code": "NOT_FOUND",
    "message": "Product not found"
  }
}

400 Bad Request — The requested denomination is not available for this product.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Denomination not available for this product"
  }
}

Check the product's available_denominations to see valid ranges.

400 Bad Request — Quantity exceeds your limit.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Invalid quantity, allowed max quantity: 100"
  }
}

Check max_quantity from the Charges endpoint to see your limit.

404 Not Found — No matching wallet found.

{
  "error": {
    "name": "NotFoundError",
    "code": "NOT_FOUND",
    "message": "Wallet not found"
  }
}

Either the specified wallet_id does not exist, or you have no wallet in the product's currency.

400 Bad Request — Wallet balance is too low.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "Insufficient funds in your wallet"
  }
}

Use the Charges endpoint to check the exact amount required, then top up your wallet.

400 Bad Request — No exchange rate available for the currency pair.

{
  "error": {
    "name": "BadRequestError",
    "code": "BAD_REQUEST",
    "message": "forex rate not available for EUR to GBP conversion"
  }
}

Contact your account manager to enable FX conversion for this currency pair.

401 Unauthorized — Missing or invalid JWT token.

{
  "error": {
    "name": "UnauthorizedError",
    "code": "UNAUTHORIZED",
    "message": "Authorization header required"
  }
}

On this page