Placing orders

Create an order, then track it to its terminal status
View as Markdown

Placing an order is a single call to POST /api/v1/orders. It charges your account balance and returns 201 immediately with status: "pending". Fulfillment happens asynchronously — the final result arrives on your webhook and is always readable from GET /api/v1/orders/{id}.

Item types

Every catalog item has a product type that determines what you must send:

Product typeWhat it isquantityfields
keyGift-card codes / license keysInteger count, default 1Not used
topupBalance/currency top-upsDecimal amount, requiredRequired
serviceManual/game servicesOmit (always 1)Required

You discover an item’s item_id, product type, quantity bounds and field definitions from the catalog export — a separate data feed, not part of this API. The API does not expose a catalog-browsing endpoint.

Request fields

  • item_id (integer, required) — the numeric item id from the catalog.
  • quantity (number) — see the item-type table above. Must fall within the item’s min/max range.
    • key: an integer count (defaults to 1).
    • topup: a decimal amount (required).
    • service: omit it — it is always 1.
  • merchant_order_id (string, optional) — your own reference id, unique per account. Use it for idempotency and to correlate webhook events with your records.
  • fields (object) — required for topup and service items only, keyed by the item’s field names. key items take no fields.

Field values: text vs choice

Each item field is either a text field or a choice field:

  • Text field → send the value as a string (e.g. "player_id": "123456789").
  • Choice field → send the numeric choice id, not the label (e.g. "server": 17, where 17 is the id of the “Europe (EU-West)” choice).

In responses, submitted fields are echoed back with choice values rendered as their human-readable display name (e.g. "server": "Europe (EU-West)"), even though you submitted the numeric id. This is display-only — keep sending numeric choice ids on create.

Creating an order

Key item (gift card / code)
$curl -X POST https://api.voodoo.center/api/v1/orders \
> -H "Authorization: Bearer <access_token>" \
> -H "Content-Type: application/json" \
> -d '{
> "item_id": 4090,
> "quantity": 2,
> "merchant_order_id": "po-10231"
> }'
Top-up item (fields; server is a choice id)
$curl -X POST https://api.voodoo.center/api/v1/orders \
> -H "Authorization: Bearer <access_token>" \
> -H "Content-Type: application/json" \
> -d '{
> "item_id": 4211,
> "quantity": 10,
> "merchant_order_id": "po-10232",
> "fields": { "player_id": "123456789", "server": 17 }
> }'
Service item (no quantity)
$curl -X POST https://api.voodoo.center/api/v1/orders \
> -H "Authorization: Bearer <access_token>" \
> -H "Content-Type: application/json" \
> -d '{
> "item_id": 5000,
> "merchant_order_id": "po-10233",
> "fields": { "account_email": "[email protected]" }
> }'

A successful create returns 201 with the order in pending:

201 Created
1{
2 "id": "0190f8a1-6b2c-7e33-9a10-4c1d2e3f5a6b",
3 "item": 4211,
4 "item_name": "Sample Game Top-up",
5 "item_product_type": "topup",
6 "quantity": 10,
7 "delivered_quantity": 0,
8 "price": 12.50,
9 "refund_amount": 0,
10 "fields": { "player_id": "123456789", "server": "Europe (EU-West)" },
11 "status": "pending",
12 "error": "",
13 "error_message": "",
14 "codes": [],
15 "merchant_order_id": "po-10232",
16 "source": "api",
17 "created_by": { "id": "0190f8a0-1111-7000-8000-000000000001", "email": "[email protected]" },
18 "created_at": "2026-07-05T12:00:00Z",
19 "completed_at": null
20}

The order lifecycle

An order starts pending, may briefly be processing, and then settles on one of three terminal statuses:

Terminal statusMeaningKey fields
completedFully deliveredcodes (key items), delivered_quantity
partialSome units delivereddelivered_quantity, refund_amount
failedNothing delivered, charge refundederror, error_message, refund_amount
  • codes — delivered key strings, for key items.
  • refund_amount — the amount refunded for undelivered units on a partial or failed order.
  • error / error_message — set when status is failed (e.g. error: "OUT_OF_STOCK").

An item being out of stock at create time is rejected up front as a 400 validation error (“Item is not available”). If stock or fulfillment fails after the order is accepted, the order settles as a terminal status: "failed" (surfaced on your webhook) — not an HTTP error. See Errors.

Reading an order

Fetch the current state of any of your orders with GET /api/v1/orders/{id}:

Get an order
$curl https://api.voodoo.center/api/v1/orders/0190f8a1-6b2c-7e33-9a10-4c1d2e3f5a6b \
> -H "Authorization: Bearer <access_token>"
Completed key order
1{
2 "id": "0190f8a1-6b2c-7e33-9a10-4c1d2e3f5a6b",
3 "item": 4090,
4 "item_name": "Sample Gift Card 10 USD",
5 "item_product_type": "key",
6 "quantity": 2,
7 "delivered_quantity": 2,
8 "price": 18.00,
9 "refund_amount": 0,
10 "fields": {},
11 "status": "completed",
12 "error": "",
13 "error_message": "",
14 "codes": ["ABCD-1234-EFGH-5678", "IJKL-9012-MNOP-3456"],
15 "merchant_order_id": "po-10231",
16 "source": "api",
17 "created_by": { "id": "0190f8a0-1111-7000-8000-000000000001", "email": "[email protected]" },
18 "created_at": "2026-07-05T12:00:00Z",
19 "completed_at": "2026-07-05T12:00:07Z"
20}

GET /api/v1/orders/{id} returns 404 (code: "not_found") for an order that does not exist or is not owned by your account. There is no list-orders endpoint in the API — browse orders in the dashboard.

Webhook vs polling

Prefer the webhook: Voodoo Center pushes the terminal event to your URL as soon as the order settles — no polling needed. Polling GET /api/v1/orders/{id} is a fine fallback (e.g. if a webhook delivery was missed), but the webhook is the lower-latency, lower-load path.