Game keys are highly sensitive. This page defines where plaintext appears, where it never appears, and how to handle deferred reveal.
Example#
curl -s -X POST "https://api.avrix.io/api/seller/v1/orders" \
-H "Authorization: Bearer $AVRIX_API_KEY" \
-H "Idempotency-Key: store-order-10432" \
-H "Content-Type: application/json" \
-d '{
"skuCode": "SANDBOX-ALWAYS-001",
"quantity": 1,
"orderReference": "store-order-10432",
"expectedUnitPriceCents": 5999,
"countryCode": "NL",
"deliveryMode": "key",
"integrationOrderContext": {
"schemaVersion": 1,
"salesCountryCode": "NL",
"currencyCode": "EUR",
"salesPriceGrossMinor": 7999,
"priceIncludesTax": true,
"salesTaxRatePercent": 21,
"salesChannel": "web",
"salesTaxAmountMinor": 1388,
"paymentProcessorReference": "pay_3QxK2mB9",
"checkoutSessionId": "cs_sandbox_10432",
"consumerIp": "198.51.100.42"
}
}'Custody rules#
| Surface | Plaintext keys? |
|---|---|
| POST /api/seller/v1/orders (default keys mode) | Yes — persist immediately |
POST /api/seller/v1/keys/pull / POST /keys/export | Yes — persist immediately |
Webhooks (including order.fulfilled) | Never |
| GET /api/seller/v1/orders | Never — aggregates only |
| Browser, mobile app, CDN logs, support tickets | Never — server vault only |
Persist before buyer response#
On the first successful mutation response:
- Write each plaintext from
keys[]with its matching UUID fromkeyIds[]to your durable entitlement store. - Only then mark the storefront order fulfilled and respond to the buyer.
- Treat
order.fulfilledas a correlation signal — it will not re-send key codes.
If you lose the HTTP response without persisting keys, recovery options are limited: idempotent replay of the same Idempotency-Key (returns the stored response), export paths when scoped, or support with X-Request-Id / orderReference. Webhook replay recovers metadata only.
Immediate fulfilment (default)#
{
"data": {
"type": "fulfilled",
"skuCode": "SANDBOX-ALWAYS-001",
"keys": ["AAAA-BBBB-CCCC-DDDD"],
"keyIds": ["8f21a9b0-1234-5678-9abc-def012345678"],
"pulled": 1,
"orderReference": "store-order-10432",
"activation": { "type": "worldwide", "countries": null }
}
}
activation is the SKU's guaranteed activation set (same object as GET /allocations), returned
with the keys on POST /orders, POST /keys/pull and POST /orders/{orderId}/fulfill so you can
show "activates in" without another call. It is the intersection across inventory batches, not
the batch this key came from — see Territory enforcement.
deferredReveal#
Deferred reveal happens when you send immediateFulfilment: false on POST /orders (or
POST /keys/pull). Inventory is assigned (pulled updated) but plaintext is omitted from the
HTTP body and data.deferredReveal is true:
{
"data": {
"type": "fulfilled",
"deferredReveal": true,
"keys": [],
"keyIds": ["k_8f21..."],
"pulled": 1,
"orderReference": "store-order-10432"
}
}
GET /orders never returns plaintext keys (aggregates only). Webhooks never carry plaintext
either. OpenAPI may include keyIds on order.fulfilled for correlation, but the durable rule
is:
Persist
keyIdsfrom the POST /orders response (authoritative for refunds / returns). Do not rely on webhooks or GET /orders as the source ofkeyIds.
Partner action:
- Treat the order as committed; store
keyIdseven whenkeys[]is empty. - Use your recovery / export workflows if codes were not in this response.
- Do not assume
order.fulfilledincludes plaintextkeys[]. - Do not assume a second POST /orders with the same idempotency key will add keys — replay returns the stored response (still empty if that was the first outcome).
Modes compared#
| Response | Keys in HTTP body | keyIds | Later delivery |
|---|---|---|---|
fulfilled (default) | keys[] populated | Persist from POST | Optional webhook for correlation |
fulfilled + deferredReveal | Empty keys[] | Persist from POST | No webhook plaintext — use recovery / export |
| Preorder / reserved | None | When later fulfilled | Later fulfilment + order.preorder_fulfilled |
Standalone pull and export#
Order commit (POST /orders) is the storefront golden path. Two sibling mutation routes pull from an allocation without the full order aggregate:
| Method | Path | Purpose |
|---|---|---|
POST | /api/seller/v1/keys/pull | Pull keys as JSON (same contract checks as orders). Use Idempotency-Key. |
POST | /api/seller/v1/keys/export | Pull and return a ZIP archive (plaintext file inside) when keys are delivered immediately. Pre-order SKUs return JSON with reserved counts instead of a ZIP. Use Idempotency-Key. |
Both accept allocation / SKU + quantity bodies and optional integrationOrderContext. Scope:
seller:keys:pull. Prefer POST /orders for Profile B store checkout so you get aggregates,
webhooks, and finance lines in one flow.
API key lifecycle (create/rotate/revoke) is separate — see API key management.