Warehouses

A warehouse is a physical (or logical) location stock lives at — every VariantStock row pins to one warehouse + one variant, every Shipment has an origin warehouse, and the routing engine picks a warehouse per order based on availability + proximity. The Python SDK wraps the four warehouse endpoints behind fulkruma.warehouses. Each method returns the raw envelope dict the API sent ({"warehouse": {...}} or {"warehouses": [...]}); no schema enforcement. For HTTP shapes and the full field reference, see API → Warehouses.

Namespace

fulkruma.warehouses     # WarehousesResources — every warehouse method below

The namespace is instantiated on the FulkrumaClient constructor and shares its underlying httpx.Client. There is no per-namespace state — calls are independent.

Methods

create

fulkruma.warehouses.create(
    body: dict,
    *,
    on_behalf_of: str | None = None,
) -> dict

Creates a warehouse in the workspace this key belongs to. Only name is required in body; everything else (address, city, postal, lat, lng, phone, isDefault) is optional. The SDK does not auto-mint an idempotency key here — warehouse creation is naturally non-idempotent and rarely retried.

result = fulkruma.warehouses.create({
    "name": "Jakarta DC-1",
    "address": "Jl. Sudirman No. 1",
    "city": "Jakarta",
    "postal": "12190",
    "lat": -6.2088,
    "lng": 106.8456,
    "phone": "+62211234567",
    "isDefault": True,
})

print(result["warehouse"]["id"])   # → "wh_01HXAB..."

Setting isDefault: True automatically clears the flag on whichever warehouse currently holds it — the backend does the swap inside a single transaction.

list

fulkruma.warehouses.list(*, on_behalf_of: str | None = None) -> dict

Returns every warehouse in the workspace, including archived ones. Filter on archivedAt to find active. Not paginated — we expect single-digit warehouse counts per merchant.

result = fulkruma.warehouses.list()
active = [w for w in result["warehouses"] if not w.get("archivedAt")]
for w in active:
    print(w["id"], w["name"], w.get("city"))

update

fulkruma.warehouses.update(
    warehouse_id: str,
    patch: dict,
    *,
    on_behalf_of: str | None = None,
) -> dict

Partial update. Pass only fields you want to change. Promote a warehouse with {"isDefault": True} — the API clears the flag on the previous default in the same transaction.

result = fulkruma.warehouses.update("wh_01HX...", {
    "phone": "+62217654321",
    "lat": -6.2090,
    "lng": 106.8460,
})

archive

fulkruma.warehouses.archive(
    warehouse_id: str,
    *,
    on_behalf_of: str | None = None,
) -> dict

Soft-delete. The warehouse stays referenceable from historic shipments and stock movements; it no longer shows up in the routing pool. Not reversible via the API — mint a new warehouse if you need to "un-archive".

fulkruma.warehouses.archive("wh_01HX...")

If the warehouse still has active stock (onHand > 0 on any variant), the API returns 409 conflict — zero it out first via stock.adjust with a negative delta.

Types

The Python SDK does not define Warehouse dataclasses — every resource method returns a plain dict matching the API envelope:

{
    "warehouse": {
        "id": "wh_...",
        "accountId": "acc_...",
        "name": "...",
        "address": "..." | None,
        "city": "..." | None,
        "postal": "..." | None,
        "lat": float | None,
        "lng": float | None,
        "phone": "..." | None,
        "isDefault": bool,
        "archivedAt": "..." | None,
        "createdAt": "...",
        "updatedAt": "..."
    }
}

Wrap in your own typed dataclass if your codebase prefers strict types. The Python SDK errs on the side of "pass through what the server sent" so it doesn't lag behind backend evolution.

Common patterns

Bootstrap a default warehouse. If you don't know whether a workspace has a warehouse yet (first-run setup, fresh tenants), create-or-promote:

def ensure_default(fulkruma, name: str):
    result = fulkruma.warehouses.list()
    active = [w for w in result["warehouses"] if not w.get("archivedAt")]
    if not active:
        return fulkruma.warehouses.create({"name": name, "isDefault": True})
    default = next((w for w in active if w.get("isDefault")), None)
    if default:
        return {"warehouse": default}
    return fulkruma.warehouses.update(active[0]["id"], {"isDefault": True})

Resolve a warehouse by city. Routing logic typically asks "which warehouse should fulfill this Jakarta order?":

def warehouse_for_city(fulkruma, city: str):
    result = fulkruma.warehouses.list()
    matches = [
        w for w in result["warehouses"]
        if not w.get("archivedAt") and (w.get("city") or "").lower() == city.lower()
    ]
    matches.sort(key=lambda w: w.get("isDefault", False), reverse=True)
    return matches[0] if matches else None

For real proximity routing, use the lat/lng columns and do haversine math client-side — we don't expose a server-side "nearest" query.

Context manager for short scripts. The client owns an httpx.Client by default. Use with to release sockets:

from fulkruma import FulkrumaClient

with FulkrumaClient(key_id="AKIAFULK...", secret="...") as fulkruma:
    print(fulkruma.warehouses.list())
# httpx pool closed here

for_merchant for platform keys. If you hold a fulkruma:platform:admin scope key (e.g. Storlaunch's platform key), scope a clone to one merchant:

merchant_client = fulkruma.for_merchant("acc_01H...")
merchant_client.warehouses.list()  # operates inside that merchant's workspace

Or pass on_behalf_of per call.

Errors

Warehouse endpoints raise FulkrumaError — the SDK does not define WarehouseNotFound or similar subclasses. Branch on err.status and err.code:

err.status err.code Cause
400 validation_error Missing name, bad coordinate range, postal too long.
404 not_found Warehouse ID doesn't exist or lives in another workspace.
409 conflict Archive attempted on a warehouse with live stock.
403 insufficient_scope Key lacks fulkruma:warehouse:write.

Transport failures surface as FulkrumaError(0, "timeout", ...) or FulkrumaError(0, "network_error", ...). See Errors for the full handling guide.

Next