Use this guide when wiring a test connection: it defines the public host, authentication, and how to interpret 401/403 so you do not mistake configuration for a successful API response.
Example#
curl -s -X GET "https://api.avrix.io/api/seller/v1/whoami" \
-H "Authorization: Bearer $AVRIX_API_KEY"One host model#
Integrators use a single public Seller API host. The API key environment
(sandbox vs production) selects behaviour — not a separate hostname.
| Dimension | Sandbox key | Production key |
|---|---|---|
| Base URL | https://api.avrix.io | https://api.avrix.io |
| Console | https://app.avrix.io | https://app.avrix.io |
| Catalog | Shared fixture catalog, magic SKUs | Your real agreements and allocations |
| Key material | Test keys | Real keys |
| Commercial evidence | Minimal (schema + optional strict mode) | Full set enforced |
All Seller API routes are rooted at /api/seller/v1/ (versioned); unversioned paths redirect
there. Health check: GET /api/seller/v1/health.
Environment matrix (keys)#
| Rule | Detail |
|---|---|
| Key determines environment | A sandbox key never acts as production inventory; a production key never accepts magic SKUs. |
| Same host | Call https://api.avrix.io for both; mismatch returns KEY_ENVIRONMENT_MISMATCH. |
| Support | When opening a ticket, include requestId / X-Request-Id and whether the key was sandbox or production. See Monitoring and support. |
Troubleshooting — wrong key vs wrong config#
| Symptom | Likely cause | Fix |
|---|---|---|
401 Invalid API key | Wrong secret, revoked/rotated key, or typo | Recreate / re-copy from Settings → Integrations → API Keys |
401 API key expired | The key's expires_at has passed (keys minted via POST /keys inherit the parent key's expiry) | Create a new key; console-created keys have no expiry |
| 401 KEY_ENVIRONMENT_MISMATCH | Sandbox key used where production posture is required, or the reverse | Check environment on GET /whoami |
| 403 IP_NOT_ALLOWED | Egress IP not on the key allowed_ips list | Add your IP/CIDR in Settings → Integrations → API Keys, or test from an allowed network |
| Key works from your laptop but not from your server | Allowlist and/or wrong secret in that environment | Run GET /api/seller/v1/whoami-ip from the same egress |
Sandbox mode#
Sandbox lets you exercise deterministic catalog SKUs and fulfillment paths without touching your
live inventory. Sandbox is enabled per deployment; GET /api/seller/v1/whoami returns a
sandboxCatalog block when it is available to your key.
To use it:
- Create a Sandbox key in Settings → Integrations → API Keys (environment toggle).
- Call
https://api.avrix.iowith the sandbox key —X-Avrix-Sandbox: trueis optional when sandbox is enabled (header still supported for explicit debugging). - Exercise the shared sandbox catalog with deterministic magic SKUs — your real company inventory is never touched.
GET /whoami returns sandboxCatalog (magic SKUs, tenantReady, headerRequired) when using
a sandbox key. The tenantReady field is part of the API response shape — when true, the shared
sandbox catalog is ready for requests.
Console Settings → Integrations → API Keys → Sandbox developer tools runs the golden path and shows per-key request logs and webhook deliveries.
Shared sandbox catalog caveats
All sandbox keys route to the same shared sandbox catalog, allocations, and key pools:
| Concern | Behavior | Mitigation |
|---|---|---|
| Key pool contention | Heavy parallel testing on SANDBOX-STD-001 can exhaust the finite pool → 409 NO_AVAILABLE_KEYS until pools are replenished | Default to SANDBOX-ALWAYS-001 for CI, Postman, and load tests |
| Order isolation | GET /orders is scoped per API key — integrators cannot read another sandbox key's orders by reference | Use unique orderReference values |
| Catalog visibility | All integrators see identical products, availability, and allocation counts | Expected for the shared sandbox catalog; webhooks remain per-key |
Magic SKUs (sandbox catalog only)
| SKU code | Scenario |
|---|---|
SANDBOX-STD-001 | Standard happy-path fulfillment |
SANDBOX-ALWAYS-001 | Large replenished pool (never runs dry) — default for Postman / smoke tests |
SANDBOX-EUR1-001 | €1.00 EUR catalog price for live PSP dogfood (your processor’s test mode); large replenished pool — use countryCode DE / FR / NL and expectedUnitPriceCents 100 (prefer preview-chained) |
SANDBOX-NOKEYS-001 | 409 NO_AVAILABLE_KEYS |
SANDBOX-TERR-BLOCK-001 | Territory blocked (price list DE-only; use US in tests) |
SANDBOX-PRICE-MIS-001 | Catalog price $59.99 — wrong expectedUnitPriceCents → mismatch |
SANDBOX-NOALLOC-001 | Share without allocation → sellable: false, readiness NO_ALLOCATION |
SANDBOX-NOSHARE-001 | No seller share — not in partner catalog; order by skuCode fails entitlement |
SANDBOX-LIFECYCLE-001 | Shared/allocated but product not live → PRODUCT_NOT_SELLABLE |
SANDBOX-LOWSTOCK-001 | Pool pinned at 3 keys — test partial availability and bulk strict semantics; quantity above stock → 409 NO_AVAILABLE_KEYS |
SANDBOX-PREORDER-001 | Pre-order lifecycle (releases 2027-03-25) — visible in catalog with sellable: false; commits → PRODUCT_NOT_SELLABLE |
SANDBOX-WHOLESALE-MIS-001 | Wholesale-model allocation priced 2500 — any other expectedUnitPriceCents → WHOLESALE_PRICE_MISMATCH |
SANDBOX-CHURN-001 | Rotates live ↔ delisted every 6 hours — poll GET /catalog/changes for added / removed tombstones |
Sandbox keys also accept two TEST-NET consumer IPs (listed on GET /whoami → sandboxCatalog.magicConsumerIps) so you can exercise geo failures without production geo data. Production keys still reject these as reserved (CONSUMER_IP_INVALID).
| Consumer IP | Expected outcome on preview / reserve / hold / commit |
|---|---|
203.0.113.66 | 422 CONSUMER_GEO_MISMATCH |
203.0.113.99 | 422 CONSUMER_IP_HIGH_RISK |
The sandbox catalog is seeded production-real so your parsing code sees the same shapes as live
data: localized store descriptions in six languages, genres and age ratings, cover art and
screenshots, per-country price lists (USD, GBP, EUR, PLN, BRL, JPY, CAD, AUD), Steam external
references, and multi-platform releases. Flagship products also expose per-platform SKUs
(SANDBOX-STD-PS5-001, SANDBOX-STD-XSX-001, SANDBOX-ALWAYS-EPIC-001, SANDBOX-ALWAYS-PS5-001,
SANDBOX-ALWAYS-SWITCH-001, SANDBOX-EUR1-EPIC-001, SANDBOX-PREORDER-PS5-001) so multi-platform
catalog sync can be exercised end to end.
Magic order references (sandbox_order_*) and payment refs (sandbox_pay_*) are rejected on
production keys with 422 SANDBOX_MAGIC_VALUE_REJECTED.
Per-key environment#
Each API key carries an environment of production or sandbox (default production).
Mismatched key environment vs request returns HTTP 401 with code: "KEY_ENVIRONMENT_MISMATCH"
and details: { keyEnvironment, requestEnvironment }.
Token prefix (recognition only)
New and rotated secrets encode key type in the token itself. Auth looks keys up by hash; the
Console environment badge and GET /whoami remain authoritative.
| Key type | Example prefix |
|---|---|
Live (production) | avrix_sk_live_… |
| Sandbox | avrix_sk_sbx_… |
Pre-existing keys may still show the legacy avrix_sk_… shape until rotated. The console
environment badge remains the source of truth when the prefix is ambiguous.
Matching rules on https://api.avrix.io:
| Sandbox availability | X-Avrix-Sandbox: true? | Allowed key environment(s) |
|---|---|---|
| Sandbox enabled | no | sandbox keys allowed without header |
| Sandbox disabled | no | production only |
| Sandbox enabled | yes | sandbox only |
Webhook isolation (shared sandbox catalog)
Sandbox webhook endpoints are scoped by API key. Each integrator only lists, receives, and mutates webhooks created with their sandbox key.
Isolation is enforced fail-closed. Commerce events (order.*, keys.*, allocation.*,
refund.*, invoice.*, report.*) are delivered only to endpoints belonging to the API key that
caused them; if an event cannot be attributed to a key, it is dropped rather than fanned out.
Shared-catalog events (product.*, sku.*, release.*, contract.*) describe the shared sandbox
catalog and are delivered to all sandbox endpoints subscribed to them.
Two consequences in sandbox only (production deliveries behave normally):
- Platform-driven events with no originating key — for example the pre-order fulfilment cron —
are not delivered. Poll
GET /ordersto observe those transitions in sandbox. - Vendor-console actions against the shared sandbox catalog (for example a manual allocation edit)
do not emit
allocation.updated. Pulls and returns made with your own sandbox key DO emitallocation.updated(reasonspulled/returned), and share-scope changes emitsku.sellability_changed, so a store can exercise both handlers in sandbox.
Simulate headers (sandbox only)
| Header | Effect |
|---|---|
X-Avrix-Sandbox-Simulate: rate_limit | Returns 429 RATE_LIMIT_EXCEEDED (Retry-After: 2) |
X-Avrix-Sandbox-Simulate: hot_path_timeout | Returns 504 HOT_PATH_TIMEOUT (Retry-After: 1); nothing pulled |
X-Avrix-Sandbox-Simulate: key_decrypt_failed | Returns 503 KEY_DECRYPT_FAILED (Retry-After: 5); nothing pulled |
X-Avrix-Sandbox-Simulate: billing_record_failed | Returns 503 BILLING_RECORD_FAILED (Retry-After: 5); nothing pulled |
X-Avrix-Sandbox-Simulate: internal_error | Returns 500 INTERNAL_ERROR |
X-Avrix-Sandbox-Simulate: idempotency_in_flight | Returns 409 IDEMPOTENCY_REQUEST_IN_FLIGHT (Retry-After: 5) |
X-Avrix-Sandbox-Simulate: webhook_delay_30s | Delays the first webhook delivery attempt by 30 seconds |
Write-path modes (rate_limit on checkout, hot_path_timeout, key_decrypt_failed,
billing_record_failed, internal_error, idempotency_in_flight) apply to
POST /orders, POST /orders/bulk, POST /orders/commit, and POST /keys/pull.
They fail before inventory work — retry the same Idempotency-Key without the
header. rate_limit also applies to other sandbox routes.
This blocks two common foot-guns: deploying a sandbox key into production traffic, and pointing a production key at a sandbox-only posture without understanding the key environment.
Production key vs sandbox key (integrationOrderContext)#
Key environment | Commercial evidence on orders | Typical use |
|---|---|---|
production (console default) | Required — net/tax/PSP/checkout fields on POST /orders, bulk, keys pull/export | Rehearsal before go-live and live traffic |
sandbox | Schema + optional global strict mode only | Early smoke tests with minimal integrationOrderContext |
See Integration recipes for how to send integrationOrderContext on
store orders.
Quick sandbox path (self-service)#
- Confirm sandbox is enabled —
GET /api/seller/v1/whoamireturnssandboxCatalogwithtenantReady: true. - In the console: Settings → Integrations → API Keys → Create key → Environment: Sandbox.
- Call
https://api.avrix.iowithAuthorization: Bearer <sandbox_key>(optionallyX-Avrix-Sandbox: true). - Golden path:
GET /health→GET /whoami→GET /products→POST /orders/preview→POST /orders→GET /orders. Start with Quickstart for the full narrative (~15 minutes).
Magic SKU SANDBOX-ALWAYS-001 is the recommended default for smoke tests; SANDBOX-STD-001
is fine for light manual trials. For live PSP dogfood (~€1 catalog bind), use
SANDBOX-EUR1-001 with countryCode DE / FR / NL.
Authentication#
-
In the Avrix console, open Settings → Integrations → API Keys (seller company).
-
Create a key and assign scopes for the recipes you need (see Scope matrix).
-
Send every request with:
HTTPAuthorization: Bearer <your_seller_api_key>
Keys with null or empty scopes receive 403 INSUFFICIENT_SCOPE — they are not full access.
Always create keys with the scopes you need.
Scopes and 403#
| Symptom | Likely cause | What to do |
|---|---|---|
| 403 with message about missing scope | Key does not include required scope (e.g. seller:keys:pull for orders). | Create a new key with the right scopes. See Scope matrix. |
| 401 | Missing/invalid Authorization, wrong key format, expired, or revoked key. | Verify Bearer prefix, no extra quotes, key environment. See Error playbook. |
| 403 from IP allowlist | Key has allowed_ips configured and your egress IP is not listed. | Add your IP in Settings → Integrations → API Keys. See IP allowlist. |
Do not treat a 401/403 as “success” or mock a JSON body to continue development — fix auth, scopes, or network first.
Correlation and support#
- Every response includes
X-Request-Idwhen the API sends standard headers. Always log it when reporting issues. - Error bodies may include
requestId,code,hint, anddoc_url. Prefer first failing request in a trace when debugging (not only the last error). - Escalation: Monitoring and support.
Optional: CORS#
If browser CORS origins are configured on the deployment, browser clients receive CORS headers. Server-to-server (Postman, curl, backend) does not require CORS.
Rehearsing the commercial refusals#
The sandbox contract is deliberately configured with the suggested term values, so it behaves like a newly signed agreement: vat_basis: purchaser_country_capped, permitted_sale_currencies: primary_only, permitted_sales_channels: own_storefront, geo_mismatch_policy: warn.
That makes four of the commercial refusals reproducible here with nothing but a request body, and three of them unreachable. Both halves are worth knowing.
Reproducible in sandbox#
| To see | Send |
|---|---|
SALES_CHANNEL_REQUIRED | An order with no integrationOrderContext.salesChannel. |
SALES_CHANNEL_NOT_PERMITTED | salesChannel: "marketplace:steam". Any marketplace:<slug> is outside own_storefront. |
FOOTPRINT_COUNTRY_NOT_DECLARED | salesCountryCode: "MX" (or SE, NO, CH). The sandbox store declares US, CA, GB, DE, FR, NL, PL, ES, IT, HU, BR, JP and AU, so anything else is outside its footprint. |
CURRENCY_NOT_PERMITTED_BY_TERM | A currencyCode that is not the SKU's own currency for that country, for example EUR on a USD-priced country. Under primary_only only each country's own currency is permitted. |
Not reproducible in sandbox, and why#
CURRENCY_NOT_PRICED_FOR_COUNTRY, SRP_MISMATCH_FOR_CURRENCY and CONSUMER_GEO_MISMATCH_POLICY_REFUSED cannot be triggered here. All three need contract values the sandbox does not use: the first two need an agreement permitting more than each country's own currency, and the third needs a geo_mismatch_policy of refuse rather than warn.
We have not added fixtures for them, because a sandbox contract configured to demonstrate every refusal would no longer resemble the agreement you are actually going to sign, and rehearsing against a contract nobody has is worse than not rehearsing. Handle the codes from the error playbook; each states what causes it and what to do.
The same is true of the two advanced settlement modes. A permitted_sale_currencies of custom (a named currency list per country) and a permitted_sales_channels of own_storefront_plus_named_marketplaces (specific marketplaces by slug) are not configured on the sandbox agreement, so the refusals they produce cannot be rehearsed here either. When your own agreement states one of them, GET /whoami names the currencies and channels it permits, and the Commercial Schedule you signed lists them in full.
GET /whoami reports which of these apply to you, so you can tell before you send an order.
What sandbox gives you#
Required declarations on API catalog orders. The sandbox enforces the same tax declaration as production, so an integration cannot pass here and fail after go-live. Every API catalog order must state integrationOrderContext.salesCountryCode, integrationOrderContext.salesPriceGrossMinor, integrationOrderContext.salesTaxRatePercent and integrationOrderContext.salesChannel. Refusal codes are per field: salesTaxRatePercent or salesPriceGrossMinor missing (including JSON null) → 422 TAX_DECLARATION_REQUIRED; salesCountryCode missing → 422 SALES_COUNTRY_REQUIRED (catalog price guard; unchanged); salesChannel missing → 422 SALES_CHANNEL_REQUIRED, and a channel your agreement does not permit → 422 SALES_CHANNEL_NOT_PERMITTED. Any missing required field is refused before a key is delivered, so nothing is consumed and the call can be retried. GET /whoami lists the fields at data.requiredDeclarations for every key; sandbox responses also repeat them under data.sandboxCatalog.requiredDeclarations. The Content Owner's share is computed on the price net of the consumer VAT you declare, so a sale that does not state its tax cannot be settled; see INTEGRATION_ORDER_CONTEXT.md.
Sandbox already provides a ready path for the first hour:
- A shared sandbox catalog with sandbox API keys (create yours in Console).
- An active connection and BMA with shared SKUs and priced API allocations.
- Deterministic magic SKUs (including negative paths) and available inventory pools.
- Support for a fake buyer checkout context:
salesCountryCode, retail currency, gross/net/tax minors, PSP reference, checkout session id, and payment method family. - Per-key webhooks capable of receiving
test.ping,order.fulfilled,order.reserved,order.keys_returned, and related events you subscribe to.
Magic SKUs, magic payment references, and bypass fixtures are sandbox-only. Production requests containing test namespaces or magic values are rejected server-side rather than silently mapped to real inventory.
Public documentation site#
Error hints may reference https://docs.avrix.io/seller-api/.... Interactive OpenAPI docs are
at /api/seller/v1/docs on https://api.avrix.io.
Related#
- Quickstart — sandbox-first golden path (~15 minutes)
- Scope matrix — scopes paired with sandbox keys
- IP allowlist — optional egress restrictions
- Monitoring and support
- Error playbook — auth, scope, and environment error codes