Create a stock location
Authorizations
A secret API key. Publishable keys cannot reach this API. A key may carry an expiry, and an expired key is refused exactly like an unknown one, with a 401 that names no reason; check the key's expires_at in the dashboard rather than inferring it from a response. When a merchant rolls a key's secret they choose a grace window of up to 3 days, and for its duration BOTH the new secret and the one it replaced authenticate, so an integration moves over on its own deploy schedule instead of at the instant the button is pressed. Move before the window closes: after it, the old secret is refused. Nothing else about this contract moves with a roll. The key keeps its id and its scopes, so the only thing an integration updates is the credential itself.
Headers
A unique key per logical write. Replaying a request with the same key returns the first response byte for byte instead of applying the write twice.
Body
Creates the warehouse a picker is told to go to. PUT /api/v1/inventory-levels has required a location_id since it shipped and nothing on this API could mint one, so a 3PL onboarding a new site had to stop, open the dashboard, type the address by hand and copy the id back into its own config. NO STOCK MOVES AND NO MONEY MOVES. A fresh location holds nothing at all: every quantity is a level, written afterwards with PUT /api/v1/inventory-levels, and until then this row is a name and an address. THERE IS NO DELETE ON THIS FAMILY, and it is refused rather than forgotten. inventory_level.location_id is ON DELETE CASCADE, so removing a location would silently destroy every stock record held there, with no soft delete and no restore; the existing guard is a COUNT, so a location whose levels were just zeroed would pass it. The reversible operation is published instead: PATCH with is_active false retires a warehouse, keeps every level resolvable, and is undone with one more patch. apiscope publishes no delete qualifier for this family to gate a DELETE with either. IT DOES NOT CONVERGE PAST THE IDEMPOTENCY WINDOW. stock_location has no unique index beyond its primary key, and that is correct rather than an oversight: two warehouses may legitimately share a name and even an address, since a mezzanine and a floor in one building are two locations to a picker. So a retry with a FRESH key creates a second location. That failure is recoverable in the safe direction, because a fresh location holds no levels and no stock: list, spot the duplicate, and have the merchant remove it from the dashboard with nothing having moved. Nothing is emitted: this family registers no webhook topics. THE ADDRESS IS FLAT, and a nested "address" object is refused BY NAME because the internal payload the dashboard consumes nests it and a client copying that shape would have every address key ignored and get a 201 for it. state, region and country are refused too: other commerce APIs spell them that way, and a warehouse address typed against the wrong vocabulary fails at the carrier rather than here.
Required, and it has no defensible default. Blank or whitespace is a 400 naming the key.
Optional, defaulting to true. false creates a location that exists and is not somewhere new stock is placed, which is the same retired state PATCH produces. It may not be null.
Optional. Every address field may be sent as null, which means this warehouse has no recorded value for it, and an empty string is accepted as the SAME request rather than refused, because a form with a cleared input sends "". Both store SQL NULL, so the next read answers null rather than "".
The sub-national division. This resource spells it province; state and region are refused by name.
The ISO 3166-1 alpha-2 code. NOT case-normalised on the way in, unlike currency codes elsewhere on this API: the read publishes it exactly as stored, so send it in the case you want to read back and be consistent across your own writes.
Response
Created
