Stock

The member’s stock in the warehouse, as lots. Reading needs exchange.stock.read, changing needs exchange.stock.write.

A lot #

A stock-lot is the member’s stock of one material or item at one warehouse location, quality and stolen state: everything the member holds there, personal and shared, summed across the org-unit pools it is booked in — what the member’s own warehouse page in the web shows. It has no org unit and no row id. Stock another member holds is never in a lot.

Field Meaning
key Opaque identifier of the lot.
material {bt, name}: a material, or a game item. bt is its Basetool id.
materialKind Materials only, see below.
location {name, uex?} of the warehouse location.
quality 0 to 1000. Every material lot has the quality it is booked at; an item lot has 0.
stolen Whether the stock is marked stolen.
quantity {amount, unit} in the material’s own unit; SCU to three decimals, items in whole PIECEs.
{
  "key": "lot-1",
  "material": {"bt": "c3b1e0c2-8a4e-4f64-9a51-2b9f5c7e0a11", "name": "Laranite"},
  "location": {"name": "Area18", "uex": {"kind": "CITY", "id": 4}},
  "quality": 712,
  "stolen": false,
  "quantity": {"amount": 12.5, "unit": "SCU"}
}

Material kind #

materialKind classifies a material so a client can sort it into its own lists. It never decides a lot’s quality.

Field Meaning
type RAW (goes into a refinery), REFINED or NO_REFINE. Always present.
commodity Listed in UEX’s commodity catalogue. Always present. That catalogue holds ores, refined metals and gems as well as trade goods, so true does not mean “a trade good without quality”.
mineral UEX: a mined mineral.
harvestable UEX: harvested rather than mined.
raw UEX: an unrefined raw material.
refined UEX: the product of a refinery.
buyable UEX: a terminal sells it.
sellable UEX: a terminal buys it.

The six UEX flags are left out for a material UEX does not know.

Reading the lots — GET /exchange/v1/me/stock #

A snapshot without cursor, the changes since it with one: the same cursor, limit, paging and tombstones as blueprints. A lot whose stock is all booked out, or moved to another member, arrives as a tombstone; rebooking stock between personal and shared does not change its lot. A lot whose stock changes while a snapshot is read may move to a later page; its change follows in the feed either way.

Setting quantities — POST /exchange/v1/me/stock/changes #

Send an Idempotency-Key and a change set of 1 to 500 set-quantity ops, dryRun optional. An op names the lot and says what it should hold, and what the client last saw it hold:

{
  "ops": [
    {
      "op": "set-quantity",
      "opId": "o1",
      "material": {"bt": "c3b1e0c2-8a4e-4f64-9a51-2b9f5c7e0a11", "name": "Laranite"},
      "location": {"name": "Area18", "uex": {"kind": "CITY", "id": 4}},
      "quality": 712,
      "stolen": false,
      "quantity": {"amount": 20, "unit": "SCU"},
      "expectedQuantity": {"amount": 12.5, "unit": "SCU"}
    }
  ]
}

expectedQuantity is the lot’s quantity in the client’s last synced state, and 0 for a lot the client has not seen on the server. Only opId and override are optional. The server compares and books SCU amounts rounded to three decimals.

The server decides the ops in order, as if the earlier ones had already run, and each op in these steps; the first that fails ends it:

Step Refused as
material resolves as a material, else as an item unmatched UNMATCHED, ambiguous AMBIGUOUS
location resolves to a warehouse location, never created rejected LOCATION_UNKNOWN
quantity and expectedQuantity are in the material’s unit — PIECE for an item rejected UNIT_MISMATCH
stolen: true needs the Basetool’s stolen marking, which may be switched off rejected STOLEN_MARKING_DISABLED
The lot’s stock is locked; its quantity must equal expectedQuantity rejected VERSION_CONFLICT
A lot already at quantity unchanged
An empty lot whose last change came from the web, the app, the system, another client or another installation rejected REMOVED_ELSEWHERE
A fall larger than the lot’s stock not reserved for a job order or mission rejected STOCK_EARMARKED

A material op addresses the lot at the quality it sends, whatever the material’s kind; send 0 for stock whose quality you do not know. An item op addresses the lot at quality 0, whatever quality it sends. REMOVED_ELSEWHERE guards against refilling what the member emptied elsewhere; ask the member, then resend with override: true. On STOCK_EARMARKED, ask the member to release the reservation in the web. On VERSION_CONFLICT, pull the feed, merge, and send the op again under a new Idempotency-Key.

What a change books #

A change is booked like the warehouse books it, audited in the warehouse under your client’s name:

  • A rise books the difference in as new personal stock without an org unit; counted pieces join the existing stock of that item as a book-in in the web does.
  • A fall books the difference out, taking personal stock first, then stock without an org unit, then the oldest. It never takes stock reserved for a job order or mission.

Material Exchange offers. The Material Exchange is the Basetool’s board of stock offers and requests between members, unrelated to this API. A book-out that leaves less stock than an offer of the member promises lowers that offer, or removes it when nothing is left — as in the web, and audited. The result counts them in offersReduced and offersRemoved. An undo does not bring them back, so tell the member when either is above 0.

Stolen and not stolen. A fall of a lot and a rise of its twin — same material, location and quality, the other stolen value — in the same batch move the stock by re-marking it, not by booking it out and in. As much as both ops allow is marked, part of a row split off where needed; stock that backs a Material Exchange offer or is reserved stays as it is, and the rest is booked out and in.

Moves. To move stock between locations or qualities, send the fall and the rise in one batch. A fall counts as a move, not as a removal for the mass-change guard, when the batch’s rises of the same material or item cover all of it, falls taken in the batch’s order. Sent in two batches, the fall counts as a removal.

The stock result #

The change-result has the counts and results[] described for blueprints, plus the offers the book-outs touched:

{
  "dryRun": false,
  "applied": 3,
  "unchanged": 1,
  "notApplied": 1,
  "results": [
    {"index": 2, "result": "unchanged"},
    {"index": 4, "opId": "x", "result": "rejected", "reason": "VERSION_CONFLICT"}
  ],
  "offersReduced": 1,
  "offersRemoved": 0
}

dryRun: true decides every op and writes nothing; it neither marks stock nor asks the mass-change guard. For the guard, a lot counts as removed when it is set to 0, or cut to at most a tenth of what it held before your client’s first change to it within the guard’s 24 hours — see the sync guide.

Errors #

Code When
400 SCHEMA_INVALID The change set, limit or a parameter breaks the contract, for example a PIECE amount that is not whole.
400 IDEMPOTENCY_KEY_MISSING A change set without a well-formed Idempotency-Key.
409 MASS_CHANGE_CONFIRMATION_REQUIRED The batch removes too much; the member confirms it.
410 CURSOR_EXPIRED The cursor is older than the tombstones, or not one the server issued.
413 BATCH_TOO_LARGE More than 500 ops, or too large to hold for the member’s confirmation.

The full list is the error registry.