Freeport Seller API

Listings and stock sync

A listing is one line of stock: a game, a lane, a realm (or a package on a top-up or gift-card lane), a price per some quantity, a minimum order, a stock count and a delivery time. Your software keeps a line per pool; the API keeps a listing per line. What ties them together is external_id: your own id for the line, unique per account, which every write can address instead of ours.

Quantities are whole units of the game's own currency or goods, and they run large: stock up to 1015, a batch up to 1012, sent as 64-bit integers. Gold by the million is "price": "0.14" with "price_per_qty": 1000000; a price to more places than a cent is a bigger batch, so $4.48999 a billion is "4489.99" per 1000000000000. A listing nobody could order (its largest allowed order worth less than the smallest order Freeport takes, or a maximum under its minimum) is refused with never_orderable or maximum_below_minimum.

Where a game lists delivery methods for its currency, a listing there says how the gold changes hands. GET /v1/catalog/games/{slug} shows the methods on that lane as the vocabulary entry keyed deliveryMethods, and the listing names at least one of them in attributes.deliveryMethods, several joined with a comma and a space: "attributes": { "deliveryMethods": "Mail trade, Auction house" }. Spell each as the vocabulary does: what matches is stored in the game's order, and anything else is dropped. Creating a listing that names none is refused with 400 delivery_method_required, and the message lists the game's methods. Attributes are replaced whole, so a change that sends attributes names the methods again or is refused the same way; a change that sends none keeps what the listing has. A lane with no such entry takes no methods, and the key is dropped if you send it.

One listing at a time

  • POST /v1/listings creates. Name the placement by slug (wow-classic-era, gold, whitemane-horde) or by id; a title is generated if you send none.
  • PATCH /v1/listings/{id} changes price, stock, status or anything else. A listing never moves to another game, lane or realm; make a new line instead.
  • PUT /v1/listings/by-external-id/{external_id} creates or updates by your id, which is the call most tools end up making. Like a sheet line, it describes the whole listing: a max_qty or low_stock_alert_qty you leave out is cleared, not kept. PATCH changes only what it names, and "max_qty": null on a PATCH lifts the cap.
  • PUT /v1/listings/{id}/tiers replaces the volume ladder: up to three rungs, each starting above the one before it and cheaper than it. Checkout picks the rung an order reaches and freezes that price on the order.

A whole sheet

When your system is the truth, send the sheet. A stock sync lands up to a thousand lines a call and any number of calls, then applies as one decision in the background:

  1. POST /v1/stock/syncs with { "mode": "replace" } or "merge". Replace means every listing with an external id the sheet does not mention goes to stock zero and pauses; merge leaves them alone.
  2. PUT /v1/stock/syncs/{id}/lines with up to a thousand lines. Each line is checked on its own and its placement resolved; a bad realm name is reported in the answer to this call and counted as failed at commit, while the rest go on. The same external id staged twice across calls keeps the later line; twice in one call is refused as duplicate_external_id, unless the first copy failed validation, in which case the later valid copy replaces it.
  3. POST /v1/stock/syncs/{id}/commit hands the sync to the worker and returns at once. GET /v1/stock/syncs/{id} shows committing becoming committed with counts, and /lines shows the result per line: created, updated, unchanged, paused or failed with a code. Where the game lists delivery methods for its currency, a line that would create a listing there without naming one, or that sends attributes naming none, is staged without complaint and fails here with delivery_method_required.

POST /v1/listings/batch is the same engine in merge mode, opened, staged and committed in one call, for a sheet that fits in one request.

An unchanged line costs one read and no write, so sending the whole sheet every few minutes is fine. A 5,000-line sheet applies in well under a minute. Open syncs expire after an hour, and a finished sync keeps its per-line results for seven days.

The sheet template

stock-sheet-template.csv has one row per line with the columns a sync line takes. Tiers are written as min_qty:price pairs separated by semicolons. Delivery methods are written in delivery_methods as the game's own names separated by semicolons, and go into the line as attributes.deliveryMethods, joined with a comma and a space. Turn each row into a JSON line and send it; the samples do exactly that.

Names to ids

Your system says "Whitemane Horde" and ours says whitemane-horde. POST /v1/catalog/resolve takes the names you keep and ranks our games, lanes and realms against them, with a named faction deciding between the two sides of a realm. Use it once to build a crosswalk and store the slugs. GET /v1/catalog/changes?since= tells you what the catalog gained or renamed since a moment, so the crosswalk stays current without re-reading everything.

Gift cards and top-ups: the code vault

These two lanes can be closed for trade. GET /v1/catalog/games/{slug} says so on each lane with open; a closed lane stays in the catalog with its packages, so your crosswalk does not shift when it opens. While a lane is closed, creating a listing in it, putting one back on sale, raising its stock or adding codes answers 409 lane_closed, and a sheet line for it fails with that code. Lowering stock, pausing and withdrawing codes are never refused.

On a gift-card or top-up listing, stock the codes themselves with POST /v1/listings/{id}/codes. They are encrypted at rest and counted, never shown again; stock follows the vault. From then on every order on that listing is accepted and delivered the moment it is paid, with the codes attached for the buyer. A vault short of an order's quantity leaves that order for you to deliver by hand, as before.

Low stock

Set low_stock_alert_qty on a listing and you receive listing.low_stock when the stock crosses it and listing.sold_out when it reaches zero, whether a sale, a stock edit, a sheet line or a withdrawal of codes moved it. Either is a reason to top the line up, and both are the cheap way to know without polling.