Documentation and partner-facing notes for the Seller API guides. API contract changes follow the Versioning and compatibility page — this changelog tracks guide improvements, contract clarifications, and notable partner announcements. Prefer that page for deprecation / sunset dates; keep both in sync when a field or route is scheduled for removal.
2026-09-17#
Order references are settled with the delivery#
A store order reference is now judged inside the same transaction that hands
out the keys, under a lock held for your company and that reference. Two
allocations of one company can no longer both fulfil the same reference, and a
replay that asks for more than the original delivery is refused before any key
moves. Nothing changes on the wire: a completed reference still replays its
original keys, a partial top-up still answers 409 ORDER_REFERENCE_CONFLICT
with the same message, and the error playbook entry
still applies. Order references stay described on
Client order reference and idempotency.
Sandbox: one order-reference namespace per API key#
On the shared sandbox catalog every API key now has its own order-reference
namespace. Two integrators who both send ORDER-001 each receive their own
keys and each replays only their own delivery; a reference reused by another
key is never treated as a replay of yours. The same scope applies to refunds:
POST /refunds refuses key ids that were not delivered to the calling key
with the usual "Key not found for this seller or not pulled" answer,
GET /refunds lists only refunds of sales made with the calling key, and a
refund belonging to another key answers 404. Production keys are unchanged:
one company keeps one namespace. See Sandbox for the other
per-key isolation rules.
Previews and holds carry the settlement declarations#
The Seller API helpers (@avrix/seller-api-helpers) now let previewCheckoutLine
and createCheckoutHold send the same integrationOrderContext and
consumerIp an order sends, so a preview or hold is admitted on the same basis
as the order it precedes. Both take a single input object; callers of the older
positional form update the call at their next upgrade. What the API requires
has not changed. The quickstart and the checkout-hold example in
Order lifecycle now show complete bodies, and the
Integration order context guide explains the
three mandatory declarations.
2026-09-10#
whoami names a missing registered country#
GET /whoami integrationReadiness.blockers gains STORE_COUNTRY_MISSING
(recommended action register_company_country). It is raised when the seller
company has no registered country on file. Orders are still accepted, but a
sale whose buyer country cannot be confirmed settles against the store
country's rate, and without one such sales are held out of settlement and
appear on no statement. The country is recorded in the console, under Company
settings, and is now asked during commercial setup; the blocker exists for
companies that arrived by another route. It is listed before any catalog
blocker, because nothing in the catalog can fix it.
whoami names locked countries#
integrationReadiness.blockers also gains REGION_LOCKED (recommended action
review_publisher_region_locks), with a countries array naming the
countries you declare that a Content Owner has locked under an in-force API
agreement. Orders into those countries are refused until the owner lifts the
lock; the seller cannot. The same locks are now disclosed on the agreement
review before signature, with the agreement's currency rule and its five
settlement terms, so a store no longer meets them on the first order.
2026-09-09#
Refusal details are on the wire, and pre-orders meet the same checks#
Three changes to how an API-model order is refused, none to what is refused.
ApiError.details now carries what the refusing check knows. A footprint
refusal carries fixItUrl and the countryCode or currencyCode it saw. A
secondary-currency floor refusal carries floorMinor. A consumer geo refusal
carries declaredCountry and, when the buyer IP resolved, detectedCountry.
The playbook promised these for some time; the body now delivers them, on
POST /orders, POST /orders/bulk (per line, as details), POST /keys/pull
and POST /keys/export.
A pre-order placed through POST /orders on a title in its pre-order window
now meets every check a live order meets before it is accepted: the price
bind, your footprint, the agreement's permitted currencies and channels, the
Content Owner's region locks, consumer geo, and the tax declaration. It used to
meet none of them, and a problem surfaced at release, after your customer had
paid. A refused pre-order answers with the same code, status and body a
refused live order would, 422 for a commercial boundary, not the 409 this
path used to fall to.
PUT /me/footprint now requires at least one country and one currency, and writes both halves in one transaction. An empty footprint refuses every order, so the API refuses to store one, as the console always has.
GET /whoami data.footprint gains countriesWithoutReferenceRate: the
countries in your footprint for which the platform holds no consumer tax
reference rate yet. An order into one is accepted and the key delivered, but
the sale is held out of settlement until the rate is entered, so it will not
appear on a statement in the meantime. Check the list before selling somewhere
new in volume.
A checkout attestation (consumerGeoAttestId) is spent on one order. Presenting
it on a second order is refused as VALIDATION_FAILED with the message that
it was already used; a retry of the same orderReference may reuse it.
Your declared currencies are now checked on every order#
Your footprint has always had two halves, the countries and the currencies you
sell in. Only the countries were enforced. A sale in a currency you had not
declared went through on the strength of the Schedule and the catalog alone,
which is not what GET /me/footprint and the Commercial configuration screen
said would happen.
A sale currency absent from your footprint is now refused with
FOOTPRINT_CURRENCY_NOT_DECLARED on POST /orders, POST /orders/bulk,
POST /keys/pull and POST /keys/export. It is a 422, it is recoverable,
and the error details carry a fixItUrl to the screen that fixes it.
Both footprint halves are checked ahead of the Schedule and the catalog, because they are the only boundaries you can fix alone. The country is named before the currency when both are missing.
If you sell in currencies you have never declared, declare them before this
reaches your production keys. GET /whoami reports data.footprint with both
lists, and it is the fastest way to check.
Declaring a currency does not price it. The Schedule still has to permit it and the title still has to carry a price for the country, so this is the first of three checks, not a replacement for the other two.
Quotes and holds no longer need a tax declaration#
POST /orders/preview, POST /orders/hold and POST /orders/reserve no longer require
integrationOrderContext. Previously an API catalog quote that omitted the settlement declarations
was refused 422 TAX_DECLARATION_REQUIRED, which made price discovery circular — the preview is how
you learn the price you would have to declare — and forced a storefront to know its final tax before
it had created the payment session. The published preview request schema always listed only
quantity as required; the implementation now matches it.
Nothing about settlement changed. The declarations are still required wherever a key is delivered —
POST /orders, POST /orders/bulk, POST /orders/commit, POST /keys/pull and
POST /keys/export — in sandbox and production alike, still refusing with
TAX_DECLARATION_REQUIRED, SALES_COUNTRY_REQUIRED or SALES_CHANNEL_REQUIRED before anything is
delivered. A reservation taken without them can still be refused at commit, so send them as early as
you have them.
Send the context on a preview and it now returns the settlement object the response schema
documents — basis, applied rate and rate reason — so you can see how the sale would split before
charging. Omit it and you still get the price, without that object.
2026-09-08#
Contract: named currency lists and named marketplaces#
A Commercial Schedule can now state its permitted currencies as a named list rather than a rule, and can admit named marketplaces alongside your own storefronts. Both are additive: an agreement that states neither behaves exactly as before.
GET /whoami reports them in permittedCurrenciesByPublisher. A named list arrives as currencyMode: "custom" with a currencyDetail object mapping each permitted currency to the countries it may be sold in, or to "territory" for the whole granted territory. A currency the list does not name is refused with CURRENCY_NOT_PERMITTED_BY_TERM however the SKU is priced, and one named for other countries than the sale's is refused with details.reason: "currency_not_permitted_for_country".
Named marketplaces arrive as permittedChannels: "own_storefront_plus_named_marketplaces" with a marketplaceSlugs array. Declare one as salesChannel: "marketplace:<slug>", spelled as the array spells it; every other marketplace is still refused, and SALES_CHANNEL_NOT_PERMITTED now lists every channel that would have been accepted in details.permittedChannelList.
Read the boundaries before you send an order: Currency and FX explains both, and Integration order context covers the channel declaration.
2026-09-06#
Breaking: salesChannel is required on API catalog orders#
Every API catalog order must now declare integrationOrderContext.salesChannel: one of web, mobile_app, partner_embed, or marketplace:<slug>. Omitting it is refused with 422 SALES_CHANNEL_REQUIRED, and a channel your agreement's Commercial Schedule does not permit with 422 SALES_CHANNEL_NOT_PERMITTED. Under the default own_storefront, the first three are permitted and any marketplace:<slug> is refused.
This follows the Key Delivery article, which states the grant is to sell to an end user through a permitted channel and excludes resale, sub-distribution and third-party marketplace listing. An operator of a marketplace may hold the agreement in respect of its own first-party storefront.
Contract: commercial boundaries are enforced at order time#
Four new refusals, each with an entry in the error playbook: FOOTPRINT_COUNTRY_NOT_DECLARED, CURRENCY_NOT_PRICED_FOR_COUNTRY, CURRENCY_NOT_PERMITTED_BY_TERM and SRP_MISMATCH_FOR_CURRENCY. A fifth, CONSUMER_GEO_MISMATCH_POLICY_REFUSED, applies only where your agreement sets the geo-mismatch policy to refuse.
GET /whoami reports the boundaries that apply to you, so you can check before sending an order. Currency and FX is new and explains which currency a sale may be in and how a secondary currency is checked.
Contract clarification: secondary-currency sales reach the floor; declaration codes on production keys#
A POST /orders whose integrationOrderContext.currencyCode is not the catalog list currency is now a secondary-currency sale rather than a CURRENCY_MISMATCH: it is checked, in order, against your footprint, the Commercial Schedule's currency mode, the SKU's authored currencies and the reference-rate floor, and settles on your expectedUnitPriceCents in that currency. CURRENCY_MISMATCH keeps one meaning, a currency that disagrees with an active checkout hold. Separately, a production key that omits salesTaxRatePercent, salesPriceGrossMinor or salesChannel now receives the documented 422 TAX_DECLARATION_REQUIRED or 422 SALES_CHANNEL_REQUIRED instead of a generic VALIDATION_FAILED; sandbox keys already did.
Contract: every error code the API returns is named by the OpenAPI schema#
The ApiError.code enum listed 56 of the 84 codes that carry a published entry in the error playbook. The generated SDK derives its code union from that enum, so 28 codes could not be type-matched by an SDK consumer: the seven commercial boundary codes above, and 21 older ones including HOLD_NOT_FOUND, RESERVATION_GONE, HOT_PATH_TIMEOUT and SERVICE_UNAVAILABLE. All 84 are now in the enum and the union. TAX_DECLARATION_REQUIRED gains the playbook entry and doc_url it never had.
A check keeps the enum equal to the documented set from here on, so this cannot drift again unnoticed.
Additive: preview quotes the settlement, orders return warnings#
POST /orders/preview returns a settlement block computed by the same calculator the commit path uses, so you can show a customer what a sale settles at rather than a gross figure. POST /orders returns warnings[]: what the settlement noticed but did not refuse over. A warning never changes the status code.
Additive: whoami reports each Content Owner's commercial boundaries#
GET /whoami gains data.permittedCurrenciesByPublisher and data.geoPolicyByPublisher, one entry per in-force API agreement: the currency mode, the permitted channels, the geo-mismatch policy, and any per-SKU tightening the Content Owner has set. The last of those has no other route to you, because a Content Owner sets it alone, so previously the first sight of one was a 422 on a live order.
data.footprint is also now in the OpenAPI schema and the SDK types. It has been in the response since the footprint endpoints shipped, but the schema never described it.
Additive: read and write your selling footprint#
GET and PUT /api/seller/v1/me/footprint, behind a new seller:account:manage scope. Opt-in at key creation, and the one scope legacy keys do not receive: your footprint is what your sales are settled against, so an existing key does not silently gain the ability to change it.
Additive: per-transaction finance reads#
GET /finance/api-sales/{transactionId} returns one sale as the ledger recorded it. The finance CSV gains the settlement columns, appended so existing column positions do not move.
Contract: no rate-limit tiers; hold fallback; key recovery#
- Rate limits: the per-company tiers (
standard/high/premium/enterprise) are gone. Production keys are IP-allowlisted and are not rate limited by the API; sandbox keys share one flat abuse cap (600 writes / 1,200 reads / 12,000 cached reads per minute).GET /whoaminow reportsdata.key.rateLimitedinstead ofrateLimitTier. - Checkout holds: default TTL is 30 minutes (was 5) to match a hosted
PSP checkout. A lapsed hold no longer fails a paid order:
POST /orderswith an expired / releasedcheckoutHoldIdcommits as a plain order and reportscheckoutHold: { holdId, status: "expired", committedWithoutHold: true }.HOLD_MISMATCH/HOLD_NOT_FOUNDcarryrecommendedAction: fallback_to_post_orders_without_hold. - Pre-orders transact:
check_pull_contract_authoritynow uses the seller-aware sellability predicate, so a company withseller_api_preorder_enabledcan preview / hold / commit alaunchPhase: pre_orderproduct (reserved as a pre-order); readiness, availability and the transact paths finally agree. - Deprecation headers (mechanism): responses to requests pinned to a
contract date marked deprecated carry
Deprecation,SunsetandLinkonce a sunset date is published (off by default, per the versioning policy). - Checkout holds are capped by stock: the hold counter is seeded from
min(allocation headroom, keys in stock)minus in-flight holds. A SKU with few keys and a large ceiling no longer grants holds that cannot be committed, so buyers are refused at the hold instead of paying and being refunded afterNO_AVAILABLE_KEYS. - Territory reads never cache an error: a failed region-countries read is retried once and, if it still fails, left out of the cache instead of being served as "no territorial country coverage" for ten minutes.
- Order lookups at catalog scale:
GET /orders?orderReference=,GET /orders/{orderReadableId},POST /orders/recover-keysand thefirstOrderCompletedgate ofGET /integration/statusloaded the seller's whole grant list and filtered pulls with oneIN (…)over every grant id. Past a few hundred allocations that request was refused and every lookup answered404 NOT_FOUND. Lookups now start from the (indexed) order reference and verify only the grants it points at; grant scans are chunked. Found with a 500-product sandbox catalog. GET /eventskeyset cursor: responses carrymeta.nextCursor(keyset oncreatedAt,id) and thecursorquery parameter is honoured. Paging bysincealone could not cross a burst of more than one page of events sharing a timestamp (a poller re-read the same page forever);sincenow also keeps microsecond precision instead of truncating to milliseconds.meta.nextSinceis unchanged for older clients.- Paused agreements answer
CONTRACT_NOT_ACTIVE:POST /orders/preview,/orders/hold,/orders/reserve,/ordersand/keys/exportno longer report a SKU whose agreement is paused asSKU_NOT_FOUND/NOT_FOUND(400/404). They answer 403CONTRACT_NOT_ACTIVEwith the same code inpreviewError/holdError/reserveError/skuResolve, so a paused agreement is not mistaken for a catalog problem.GET /ordersand key recovery keep working for orders whose grant is paused. - Hot-drop commit parity:
POST /orders/commitnow returnskeyIds(key UUIDs, same order askeys) likePOST /orders, so refunds and key returns can target the delivered keys of hot-drop sales. - Key recovery: new
POST /orders/recover-keys(scopeseller:keys:pull) returns the live keys andkeyIdsalready sold under anorderReferencefor stores that lost thePOST /ordersresponse. Never pulls new inventory;GET /ordersstill never returns plaintext.
Fixes: order lookup consistency and call-log metering#
GET /orders?orderReference=right afterPOST /orders: the sandbox key stamp on the pulled keys is now written before the order response is sent. A store that looks the order up the moment checkout returns no longer gets404 NOT_FOUNDfor an order that exists (loop case D-03).- Usage metering: every terminal response of
POST /orders,/orders/bulk,/orders/cancel,/keys/pulland the other inline-auth routes now produces exactly oneapi_call_logrow; successful orders were previously missing fromGET /usageand the admin API activity views. Routes behind the auth wrapper no longer log error responses twice.
Fixes: price changes now reach sellers#
sku.pricing_updatedwas never sent. The publisher-side SKU price writer looked the product up through a column that does not exist, so since June no console price edit produced the webhook, thepricingfacet onGET /catalog/changes, or the seller pricing-cache drop. Fixed: the product is resolved through the same RPC the Seller API uses.- Release-level price edits now notify too. Prices written on the region-release grid
(console grid, CSV import, Steam price sync, price templates, copy-from-release)
cascade to every SKU under that release; sellers now receive one
sku.pricing_updatedper SKU and country written, the product is listed on/catalog/changeswithchangedFields: ["pricing"], andGET /products/{id}/pricingis served fresh. Tier and scheduled-tier changes are a follow-up.
Fixes: refund callback validation detail#
POST /refunds: a request that omitstypeis now refused withdetails.issues[0] = { field: "type", message: 'type is required: "refund" or "chargeback"' }instead of the generic "Invalid option" text that a wrong value also produces. A refund withoutrefundReference(or a chargeback withoutchargebackReference) keeps pointing at that field. Both shapes are documented as 422 examples on the route.
2026-09-05#
Clarification: tax declaration is required on every API catalog order#
GET /whoami now returns requiredDeclarations and requiredDeclarationsNote for every key, not only the sandbox catalog block. API catalog orders without salesTaxRatePercent and salesPriceGrossMinor are refused with 422 TAX_DECLARATION_REQUIRED before a key is delivered. INTEGRATION_ORDER_CONTEXT and the OpenAPI IntegrationOrderContext description match that; the fields are no longer described as optional audit extras.
Contract: finance sales carry the settlement the Platform applied (additive)#
GET /finance/api-sales and GET /finance/statements now return a
settlement block on every sale, plus entryType and
reversesTransactionId. Additive: no existing field changed meaning or shape.
Until now the only tax figure on a sale was taxMetadata, the echo of what
your store declared at order time. That is still there and still unvalidated,
but it is no longer the whole story. The Platform measures your declaration
against a dated reference rate, caps it, and settles the revenue share on the
net. settlement is what that produced:
appliedRatePercentanddeclaredRatePercent, so you can see when the cap bound;rateReason, which says WHY that rate applied.fallback_store_countrymeans the declared consumer country was not corroborated, so the sale settled at your own registered country rate;evidenceRank: 1 a checkout attestation, 2 the consumer IP agreeing with the declaration, 3 the declaration alone;vatMinorandnetMinor, the deduction and the base the share was computed on.
settlement is null for a sale that predates the settlement basis, or one
quarantined before it could be settled. It is stated as absent rather than as
zeroes, because a row of zeroes reads as "no tax was due".
Contract: a refund is now an entry, not a status (behaviour change)#
A refunded sale keeps its own row and gains a second row with negated amounts,
entryType: "sale_reversal" and reversesTransactionId naming the sale it
undoes. It is dated when the refund was declared, so it falls in the period the
money came back in rather than the one the sale was made in.
If you total sales yourself, read this. Statement buckets already account
for it: grossSalesCents counts sale entries only, refundedCents is what the
reversal entries took back, reinstatedCents is what won chargebacks restored,
and netSalesCents includes both movements. If you sum
salePriceCents across GET /finance/api-sales yourself, filter on
entryType == "sale" or the reversals will net out of your gross.
2026-09-03#
Contract: GET /activity is now the settlement feed#
GET /activityreturns settlement entries (sale+1,refund/chargeback−1, custody-onlypull+1, keylessreveal0) with a keysetcursor,from/to,kinds,promoId,counterpartyandorderReferencefilters andformat=csv. Each entry carries the counterparty, the promo reference, the buyer-country evidence and the economics, so month-end close needs one cursor instead of joining/finance/api-sales,/transactionsand/refunds.- The previous merged shape (
kind: transaction | webhook_delivery,payload) is still returned whenkindsis limited to those legacy kinds, for one release. Webhook deliveries live onGET /eventsandGET /webhooks/{id}/deliveries. - See Finance and settlement § One activity log.
Privacy: the buyer IP is resolved, then discarded#
integrationOrderContext.consumerIpis still required on production checkout and still drives the territory cross-check, VPN / hosting block and sanctions screening — but it is no longer stored. Sale, pull and fulfilment rows keep the resolved country (detectedCountryCode,countryMismatch) andconsumerIpHash, a peppered truncated sha256.GET /finance/api-salesand statements lines therefore returnintegrationOrderContext.consumerIpHashinstead ofconsumerIp; rows recorded before this change carryconsumerIpRedacted: true.- The address captured by the optional checkout-page attest pixel is cleared automatically 7 days after the attest expires.
- See Data handling.
Contract: PRODUCT_NOT_SELLABLE explains itself (additive)#
detailsnow carriesreason(PREORDER_NOT_OPEN|PREORDER_CLOSED|PREORDER_DISABLED|LIFECYCLE_NOT_LIVE),productId,launchPhase,preorderStart,preorderEnd,releaseDatewhere known, and arecommendedAction(wait_for_preorder_window,wait_for_release,request_preorder_capability,delist_sku) onPOST /orders/preview,POST /ordersandPOST /keys/pull.- A pre-order becomes its release in place, so the same
skuIdturns sellable at the instants given — no successor product to look up. Status codes are unchanged (404on immediate pulls,422on pre-order acceptance) and are now both stated in the error reference.
Contract: fix-it Console links in error details (additive)#
- Error responses whose cause the key-owner company can fix in the Console
now carry
details.consoleUrl(absolute, scoped to that company) anddetails.consoleAction(review_partner_contracts,review_allocations,manage_api_keys,complete_kyb,top_up_wallet,review_partner_catalog). See Error reference § The error envelope. POST /ordersandPOST /keys/pullno longer flatten territory, activation, price-guard and geo failures to{ pullError }: the structureddetailspreview already returned (skuId,countryCode,allowedCountryCodes,declaredCountry/detectedCountry, …) are now present on commit too.pullErroris unchanged.
Contract: declared vs detected buyer country on sale rows (additive)#
GET /finance/api-salesandGET /finance/statementslines carrydetectedCountryCode(resolved from the buyer IP by the geo gate at pull time),countryMismatchandwarnings(COUNTRY_MISMATCH) next to the declaredcustomerRegionCode.GET /transactionsrows carrysalesCountryCode,detectedCountryCode,countryMismatch,warnings.- The api-sales CSV gains three trailing columns:
customerRegionCode,detectedCountryCode,warning. - Values are null on rows recorded before this change or when geo ran in
offmode; the raw buyer IP is never returned.
Contract: activation set returned with the key (additive)#
POST /orders(fulfilled, keyless and pre-order bodies),POST /keys/pullandPOST /orders/{orderId}/fulfillnow carryactivation({ type, countries }), the sameSkuActivationobject already onGET /allocations,GET /products/{id}SKUs and preview lines. Show "activates in" at delivery time without a second lookup.- The value is the guaranteed intersection across the SKU's inventory
batches, not the batch the delivered key came from. It is resolved after the
pull commits and degrades to
{ "type": "unknown", "countries": null }if inventory metadata cannot be read, never failing a delivered order.
Contract: promo provenance on quoted and billed prices (additive)#
POST /orders/previewlines andPOST /orders/holdnow carryinitialUnitPriceCents(catalog list price),promo(sourceType,sourceId,discountPercent,priceEffect,startDate,endDate, ornull) andpromoResolution(applied|none|unavailable) next to the existingunitPriceCents, which stays the effective figure to echo asexpectedUnitPriceCents.GET /finance/api-salesandGET /finance/statementslines carryinitialSalePriceCentsandpromo(sourceType,sourceId) so a discounted line can be attributed to the share or deal that caused it.- Fix:
POST /orders/holdpreviously bound the list price while preview quoted the promo price, and a hold or price-commitment snapshot could have the promo applied a second time atPOST /orders. Holds now bind the effective price, snapshots are compared as-is, and the sale row records the price the store was quoted. - See Promotions § Promo in the price. The scheduled
discount calendar (
GET /promotions/calendar) is documented there as shipped rather than roadmap.
2026-08-30#
Contract — whoami.settlement.invoiceWithinDays is now always null (value change, schema unchanged)#
GET /whoamipreviously reportedinvoiceWithinDays: 7forautomated_invoicesettlement as a committed invoicing SLA. The platform does not automate invoice issuance: report approval accrues the share into the running balance and the vendor raises the commission invoice as a separate, deliberate act, so no invoicing deadline can honestly be committed. The field now returnsnullin every mode.- No wire-format change per
Versioning and compatibility: the field
is neither removed nor renamed, its schema has been nullable since launch
(
prepaid_walletandreport_onlyalready returnednull), and clients were already required to handle thenullcase. Treatnullas "no committed invoicing latency"; the field is reserved for a future committed SLA. If your month-end close automation branched on the number, key it offreport.ready/invoice.createdwebhooks instead of a fixed day count.
2026-08-29#
Contract — GET /partners reports paused agreements truthfully (additive)#
- Agreements can now be paused (suspended but signed, governing, and
resumable). The
contractssummary reports this state instead of misfiling it: a paused MCA returnsmcaStatus: "paused"(previously"missing"), and a paused BMA appears in the newpausedBmaTypesarray (previously listed underpendingBmaTypesas if never signed). - Both changes are additive per Versioning and compatibility: ignore the enum value and field if you do not handle them. While paused, pulls and orders under the agreement are refused; do not prompt a re-sign, the agreement resumes in place.
2026-08-24#
Contract — pre-order gates every commercial stage and fails closed#
POST /orders/holdandPOST /orders/reservenow apply the same lifecycle/pre-order gate as preview and commit: a pre-order-phase SKU (with the capability off) or a discontinued SKU rejects withPRODUCT_NOT_SELLABLEbefore any hold or reservation is taken, instead of only failing later at commit.- On the enabled path, acceptance is decided by the effective window
(
preorderStart/preorderEnd/preorderUnlocked) — outside the window the order rejects; it is never converted into a silent live pull. Commit now honours the capability the same way preview does. - Unverifiable pre-order state is a retriable 503
PREORDER_STATE_UNAVAILABLEon every path — including inside the key-pull transaction, which previously proceeded to reveal keys when the state lookup found nothing.Retry-Afteris now consistently 5s (the pull path used to say 2s while the docs and preview said 5s). whoami.capabilityDetails.preorder.verifiedAtis now stamped by the modernawaiting_releaseacceptance path, not only the legacy key-locking reserve.
Contract — price binding locks the price, not the offer#
- A
priceCommitmenttoken or checkout hold keeps honouring the previewed price for its window, but no longer lets a commit settle into a country the publisher just de-listed: if the sale country's price entry or territory is removed after preview, the bound commit fails closed (422CATALOG_PRICE_NOT_SET/ territory rejection). Unbound commits already failed closed on any price change (CATALOG_PRICE_MISMATCH). POST /orders/holdandPOST /orders/reservenow compare yourexpectedUnitPriceCentsecho against the promotion-adjusted price — the same figure preview returns — instead of the raw list price, so an active promotion no longer produces a spuriousCATALOG_PRICE_MISMATCH.- Platform territory edits invalidate cached territory snapshots immediately instead of after the cache TTL.
Contract clarification — the catalog delta feed covers unshare and partial delists#
- Unshare now emits on
GET /catalog/changes:removedwhen the revocation ends your catalog access to the product (it also drops fromGET /products/ snapshot), orupdatedwithchangedFields: ["availability"]when whole-catalog sharing keeps it browse-visible but its SKUs stopped being sellable. - Release- and SKU-level delists that leave the product live emit
updated(availability) so mirrors refetch instead of selling a dead offer. Full delist and archive keep emittingremoved. - Emission table added to Catalog and allocations.
- Unshare now lands immediately: cached catalog/allocation reads for the affected seller are invalidated and open checkout holds / hot-drop reservations on the revoked SKUs are cancelled best-effort (commit re-checks the share and fails closed regardless).
2026-08-20#
Contract — 2026-09-01 becomes the stamped default automatically on that date#
- The stamped default contract for unpinned requests flips to
2026-09-01at 2026-09-01 00:00 UTC — no deploy, no separate announcement. From then an omittedstrictonPOST /orders/bulkdefaults totrue(whole-cart atomicity). PinX-Avrix-Api-Version: 2026-04-29or sendstrict: falseexplicitly to keep partial success. POST /orders/bulkresponses now echometa.contractDateandmeta.strictso the effective behavior profile is visible in the payload, not only theX-Avrix-Api-Versionheader.- All bulk examples now lead with explicit
strict: true(and the example payloads were corrected to the real request shape:items, item-levelorderReference).
2026-08-17#
Contract — stock split, catalog export, lifecycle events#
GET /availabilityaddsdedicatedAvailable,sharedPoolAvailable(sum staysinventoryAvailable), plusbackorderEligible/backorderIneligibleReason.POST /catalog/exports→202 { exportId }; pollGET /catalog/exports/{exportId}for a gzip NDJSON artifact (checksum, expiry, signed URL).catalog.export_readyfires on completion.410 EXPORT_EXPIREDafter the artifact TTL.redemption.expiredfires when a keyless link passesexpiresAt.product.delisting_scheduledfires only whenarchived_atis in the future.refundPolicy(platform default,source: "platform") is echoed onGET /allocationsandGET /products/{id}as well asGET /whoami.GET /platformsaddsversionandupdatedAt— pollversionfor registry drift.
Contract — durable preorder/backorder fulfill and settlement promotions#
POST /orderscan accept a catalogpre_orderSKU against allocation headroom whenwhoami.capabilities.preorderEnabledis true (awaiting_release). Customer-orderbackorderOnUnavailablecreatesawaiting_stockonly whenbackorderEnabledis on and the vendor grant allowsrequest_mode_backorder; otherwiseBACKORDER_NOT_ALLOWED.- Inventory arrival emits
order.ready. Reveal keys withPOST /orders/{orderId}/fulfill(seller:orders:fulfill, alsoseller:keys:pull/seller:orders:writein v1). Cancel releases headroom. - Promotions add
priceEffect(list_pricedefault,settlement_price,both). Catalog pricing stays list. Preview/commit settlement drops only for settlement-affecting windows.GET /promotions/calendarlists live/upcoming windows.
Contract — consumer geo monitor/enforce and reserve/hold gates#
CONSUMER_GEO_UNAVAILABLEis now 503 withRetry-After(was 422). Retry the same Idempotency-Key. Business denials (CONSUMER_GEO_MISMATCH,CONSUMER_IP_HIGH_RISK,CONSUMER_IP_REQUIRED,CONSUMER_IP_INVALID) stay 422 and includedeclaredCountry/detectedCountry— never the raw buyer IP.- During monitor mode, successful preview / reserve / hold / commit responses may include
additive
geoWarning: { code, enforcementDate? }. POST /orders/reserveandPOST /orders/holdnow run territory + consumer-geo gates on API-model allocations. Hold acceptsintegrationOrderContext.GET /whoamiaddscapabilities.consumerGeoMonitorEnabled.- Consumer geo enforce is the default for production keys. Sandbox keys stay
off. A missing buyer IP returns
422 CONSUMER_IP_REQUIRED; missing geo data returns503 CONSUMER_GEO_UNAVAILABLE.
2026-08-12#
Contract — new opt-in date 2026-09-01 (bulk strict default)#
- Added contract date
2026-09-01:POST /orders/bulkwith omittedstrictdefaults totrue(whole-cart atomicity). The stamped default stays2026-04-29(partial success) until a future announced rollout — pin viaX-Avrix-Api-Version: 2026-09-01or dry-run withnext. Responses now echo the effective contract date. - Recommendation unchanged: send
strictexplicitly on every bulk cart so defaults never affect you. See Versioning and compatibility.
2026-08-10#
Documentation — Journey IA and gap closure#
- Reordered the public docs sidebar into a verb-shaped integration sequence (Welcome → Get started → Connect a partner → Sync your catalog → Sell an order → Stay in sync → Operate → Go live → Reference). Section labels carry no numeric prefixes; order is the reading spine.
- Added guides: OAuth tokens, Partner onboarding, Promotions, API key management.
- Filled endpoint coverage on existing guides (regions, transactions, media download, keys pull/export, bulk orders, reservation extend, webhook event feed / management).
- Added CI guard
verify:docs-endpoint-coverageso every OpenAPI operation maps to a published guide (or an explicit allowlist).
2026-08-07#
Documentation — Wave 3 (precision remediation)#
Contract notes reflected in guides (behaviour unchanged; docs aligned to runtime / OpenAPI):
- Catalog sync:
GET /catalog/changes?since=is exclusive ofsince; paginate withlimit(nonextCursor); wire shape isdata.changes[]. No published tombstone retention — cold-start on checksum drift. Checksum covers visible product IDs +updated_at(sha256-sorted-product-rows-v1); treat digest change as a cold-start signal. - Allocations cache: published ~60s TTL; orders rate class.
- Fulfilment:
deferredRevealwhenimmediateFulfilment: false; persistkeyIdsfromPOST /orders(authoritative);GET /ordersnever returns plaintext. - Returns: technical eligibility = owned + status
revealed(quarantine; not redeemed) — no invented calendar window. - Finance: named endpoints
/finance/summary,/finance/api-sales,/finance/invoices; settlement cadence agreement-dependent; chargebacks via refunds. - Versioning: clients MUST ignore unknown fields and enum values; deprecations list Sunset when scheduled (no invented fixed-day SLA).
- Rate limits: exact headers
X-RateLimit-Limit/Remaining/Reset+Retry-After; default tier standard; budgets per company (keys share); upgrade via Avrix contact.
2026-08-06#
Documentation — Wave 2#
- Published dedicated guides: event reference, idempotency, creating orders, checkout holds, hot drop, fulfilment/keys, integration tutorial, overview, commercial model, glossary, finance, reconciliation, security, rate limits, data handling, monitoring, and polling recovery.
- Light refresh of key return policy (chargeback statuses and quarantine eligibility).
Partner note — environment-aware API key prefixes#
Newly minted Seller API secrets encode key type in the token prefix:
| Key type | Example prefix |
|---|---|
Live (production) | avrix_sk_live_… |
| Sandbox | avrix_sk_sbx_… |
Authentication remains hash-based. The key’s environment field and Console badge remain authoritative. Legacy plain avrix_sk_… secrets continue to work until rotated. Placeholders in these guides use avrix_sk_sbx_your_key_here.
2026-08-05#
Documentation — Wave 1#
- Portal guides for authentication, catalog and allocations (four-gate model), order lifecycle, webhooks (signature verification), error reference anchors, and sandbox-to-production cutover.
- Mermaid diagrams and
Note/Warningadmonitions enabled in the public docs renderer. - PSP-neutral checkout language (“your PSP”) standardised across published order guides.
Earlier#
Partner-facing contract history remains on Versioning and compatibility.