Documentation menu
These are pre-release docs from the main branch.

API reference

59 operations in commercial. Raw spec: openapi.json.

GET/api/v1/catalog-items

GET /api/catalog-items — newest/active first, filterable.

Parameters

NameInTypeRequiredDescription
activequery—No`None` = all items (active + archived).
categoryquery—No
group_namequery—No
searchquery—NoSubstring search over name/sku/category/group.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/catalog-items

POST /api/catalog-items — create through the instant-first write path.

Request body

CreateCatalogItemRequest

Responses

StatusDescription
200
409SKU already exists or idempotency key reused
422validation failure

Security: bearer

GET/api/v1/catalog-items/{id}

GET /api/catalog-items/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

PATCH/api/v1/catalog-items/{id}

PATCH /api/catalog-items/{id} — absent fields keep their value.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateCatalogItemRequest

Responses

StatusDescription
200
404not visible in scope
409SKU already exists, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/catalog-items/{id}/archive

POST /api/catalog-items/{id}/archive — flip `active` off (data, never a delete). Already-archived items archive as a no-op replay of state.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

ArchiveCatalogItemRequest

Responses

StatusDescription
200
404not visible in scope
409version conflict or idempotency key reused

Security: bearer

GET/api/v1/client-loans

GET /api/client-loans.

Parameters

NameInTypeRequiredDescription
statusquery—Noopen | returned (absent = both).
client_idquery—No
overdue_onlyquery—NoOnly open loans past their expected return.
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/client-loans

POST /api/client-loans — instant-first (IF1/IF2).

Request body

IssueLoanRequest

Responses

StatusDescription
200
404client / contract / device not visible in scope
409device not in stock / already on an open loan / incomplete lease terms

Security: bearer

GET/api/v1/client-loans/{id}

GET /api/client-loans/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

POST/api/v1/client-loans/{id}/return

POST /api/client-loans/{id}/return — instant-first (IF1/IF2).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

ReturnLoanRequest

Responses

StatusDescription
200
404not visible in scope
409already returned / missing return location for stock devices

Security: bearer

GET/api/v1/pipeline/deals

GET /api/pipeline/deals — newest first, filterable.

Parameters

NameInTypeRequiredDescription
client_idquery—No
stage_idquery—No
quote_idquery—No
statusquery—NoExact status: `open|won|lost`.
owner_user_idquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/pipeline/deals

POST /api/pipeline/deals — create an opportunity and record its initial placement in the transition ledger (same transaction).

Request body

CreateDealRequest

Responses

StatusDescription
200
404client, quote, or stage not visible in scope
409idempotency key reused
422validation failure

Security: bearer

GET/api/v1/pipeline/deals/{id}

GET /api/pipeline/deals/{id} — deal + transition ledger. (Registered at the `get_one` name in the router.)

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

PATCH/api/v1/pipeline/deals/{id}

PATCH /api/pipeline/deals/{id} — editable fields; the stage is not touched here (that is the audited /stage leg).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateDealRequest

Responses

StatusDescription
200
404deal, client, or quote not visible in scope
409version conflict or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/pipeline/deals/{id}/stage

POST /api/pipeline/deals/{id}/stage — one audited stage transition. Guarded on the CURRENT stage (concurrent moves: first wins, loser gets a typed 409); the ledger row commits in the same transaction.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

MoveStageRequest

Responses

StatusDescription
200
404deal or target stage not visible in scope
409concurrent move or version conflict
422validation failure

Security: bearer

GET/api/v1/pipeline/stages

GET /api/pipeline/stages — ordered pipeline definition.

Parameters

NameInTypeRequiredDescription
include_inactivequery—No`None` = active stages only (default); `true` includes inactive.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/pipeline/stages

POST /api/pipeline/stages — create a stage (name unique per org).

Request body

CreateStageRequest

Responses

StatusDescription
200
409stage name already exists or idempotency key reused
422validation failure

Security: bearer

PATCH/api/v1/pipeline/stages/{id}

PATCH /api/pipeline/stages/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateStageRequest

Responses

StatusDescription
200
404stage not visible in scope
409stage name already exists, version conflict, or idempotency key reused
422validation failure

Security: bearer

GET/api/v1/quotes

GET /api/quotes — newest first, client/status filters.

Parameters

NameInTypeRequiredDescription
client_idquery—No
statusquery—NoExact status: `draft|sent|accepted|declined|expired`.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/quotes

POST /api/quotes — create a numbered draft. Lines snapshot catalog context; roll-ups are computed server-side (never trusted from the client).

Request body

CreateQuoteRequest

Responses

StatusDescription
200
404client or catalog item not visible in scope
409idempotency key reused
422validation failure

Security: bearer

GET/api/v1/quotes/{id}

GET /api/quotes/{id} — the quote plus its lines.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

PATCH/api/v1/quotes/{id}

PATCH /api/quotes/{id} — edit a DRAFT (header + optionally the whole line list). Sent+ quotes are immutable: typed 409.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateQuoteRequest

Responses

StatusDescription
200
404quote or catalog item not visible in scope
409quote is not a draft, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/quotes/{id}/accept

POST /api/quotes/{id}/accept — sent → accepted (terminal).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

QuoteLifecycleRequest

Responses

StatusDescription
200
404quote not visible in scope
409illegal transition, version conflict, or idempotency key reused

Security: bearer

POST/api/v1/quotes/{id}/convert-to-invoice

POST /api/quotes/{id}/convert-to-invoice — materialize an ACCEPTED quote into a fixed-fee contract + DRAFT invoice through the existing invoicing persistence (the seam's rationale is in the module docs).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

ConvertQuoteRequest

Responses

StatusDescription
200
404quote not visible in scope
409quote not accepted / outside validity / already converted, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/quotes/{id}/decline

POST /api/quotes/{id}/decline — sent → declined. Terminal.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

QuoteLifecycleRequest

Responses

StatusDescription
200
404quote not visible in scope
409illegal transition, version conflict, or idempotency key reused

Security: bearer

POST/api/v1/quotes/{id}/expire

POST /api/quotes/{id}/expire — explicitly expire a quote PAST its validity window (decline is the in-window exit).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

QuoteLifecycleRequest

Responses

StatusDescription
200
404quote not visible in scope
409still inside validity window, illegal transition, version conflict, or idempotency key reused

Security: bearer

POST/api/v1/quotes/{id}/send

One lifecycle leg: shared shape for send/accept/decline (expire has a POST /api/quotes/{id}/send — draft → sent. Locks the document.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

QuoteLifecycleRequest

Responses

StatusDescription
200
404quote not visible in scope
409illegal transition, version conflict, or idempotency key reused

Security: bearer

GET/api/v1/stock-items

GET /api/stock-items.

Parameters

NameInTypeRequiredDescription
activequery—No`None` = all items (active + archived).
searchquery—No
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/stock-items

POST /api/stock-items — create through the instant-first write path.

Request body

CreateStockItemRequest

Responses

StatusDescription
200
409SKU already exists or idempotency key reused
422validation failure

Security: bearer

GET/api/v1/stock-items/{id}

GET /api/stock-items/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

PATCH/api/v1/stock-items/{id}

PATCH /api/stock-items/{id} — absent fields keep their value.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateStockItemRequest

Responses

StatusDescription
200
404not visible in scope
409SKU already exists, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/stock-items/{id}/archive

POST /api/stock-items/{id}/archive — archival is a flag.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

ArchiveStockItemRequest

Responses

StatusDescription
200
404not visible in scope

Security: bearer

GET/api/v1/stock-items/{id}/movements

GET /api/stock-items/{id}/movements — THE traceability ledger read.

Parameters

NameInTypeRequiredDescription
idpathstringYes
limitquery—No

Responses

StatusDescription
200

Security: bearer

GET/api/v1/stock-items/{id}/serials

GET /api/stock-items/{id}/serials — serialized WHERE-IT-IS-NOW.

Parameters

NameInTypeRequiredDescription
idpathstringYes
location_idquery—No
statusquery—No
limitquery—No

Responses

StatusDescription
200

Security: bearer

GET/api/v1/stock-locations

GET /api/stock-locations.

Parameters

NameInTypeRequiredDescription
kindquery—No
active_onlyquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/stock-locations

POST /api/stock-locations.

Request body

CreateStockLocationRequest

Responses

StatusDescription
200
409idempotency key reused or name conflicts
422validation failure

Security: bearer

GET/api/v1/stock-locations/{id}

GET /api/stock-locations/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

PATCH/api/v1/stock-locations/{id}

PATCH /api/stock-locations/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateStockLocationRequest

Responses

StatusDescription
200
404not visible in scope
422validation failure

Security: bearer

GET/api/v1/stock-serials

GET /api/stock-serials — WHERE IS IT NOW, per serialized unit (tools and loaned devices; each row carries its live holder or client).

Parameters

NameInTypeRequiredDescription
item_idquery—No
location_idquery—No
statusquery—Noin_stock | out | checked_out | loaned.
holder_user_idquery—No
tool_onlyquery—No`true` = tools only; `false` = devices only; absent = both.
overdue_onlyquery—NoOnly units past their checkout due date or loan expected return.
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/stock/adjust

POST /api/stock/adjust — the signed counter-fix (NO serialized items; move the unit instead).

Request body

AdjustRequest

Responses

StatusDescription
200
409insufficient stock or serialized item
422validation failure (delta must be non-zero)

Security: bearer

POST/api/v1/stock/consume

POST /api/stock/consume — the OUT leg charging a ticket's job.

Request body

ConsumeRequest

Responses

StatusDescription
200
409insufficient stock or invalid movement
422validation failure (qty_milli must be positive)

Security: bearer

GET/api/v1/stock/consumption-velocity

GET /api/stock/consumption-velocity — the outbound-rate read model.

Parameters

NameInTypeRequiredDescription
window_daysquery—NoLookback window in days (default 30; outbound legs summed).
item_idquery—No
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/stock/levels/thresholds

POST /api/stock/levels/thresholds — min/max/reorder per (item, location); NULL clears. Creates the level row on first touch, without moving stock.

Request body

ThresholdsRequest

Responses

StatusDescription
200

Security: bearer

GET/api/v1/stock/on-hand

GET /api/stock/on-hand — the stock-on-hand read model.

Parameters

NameInTypeRequiredDescription
item_idquery—No
location_idquery—No
low_onlyquery—Notrue → only rows at/below the reorder point.
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/stock/receive

POST /api/stock/receive — a positive leg into ONE location. Serialized items move one physical unit per ledger row (exactly one serial).

Request body

ReceiveRequest

Responses

StatusDescription
200
404item or location not visible in scope
409idempotency key reused, item archived, or invalid movement
422validation failure

Security: bearer

POST/api/v1/stock/ship-out

POST /api/stock/ship-out — OUT to a client or an on-site job, with optional carrier + tracking ref.

Request body

ShipOutRequest

Responses

StatusDescription
200
409insufficient stock or invalid movement
422validation failure

Security: bearer

POST/api/v1/stock/transfer

POST /api/stock/transfer — shrink one location, grow another.

Request body

TransferRequest

Responses

StatusDescription
200
409insufficient stock or invalid movement
422validation failure

Security: bearer

GET/api/v1/tickets/{id}/stock-movements

GET /api/tickets/{id}/stock-movements — the job's parts-used ledger read: every movement anchored to this ticket, newest first. Ticket visibility gates the read; cost fields stay `financial:read`-gated (RB2), same as the per-item ledger read.

Parameters

NameInTypeRequiredDescription
idpathstringYes
limitquery—No

Responses

StatusDescription
200

Security: bearer

GET/api/v1/tool-checkouts

GET /api/tool-checkouts.

Parameters

NameInTypeRequiredDescription
statusquery—Noopen | returned (absent = both).
holder_user_idquery—No
kit_idquery—No
overdue_onlyquery—NoOnly open checkouts past their due date.
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/tool-checkouts

POST /api/tool-checkouts — instant-first (IF1/IF2).

Request body

CreateCheckoutRequest

Responses

StatusDescription
200
404item, kit, or location not visible in scope
409kit not ready / unit already out / not a tool

Security: bearer

GET/api/v1/tool-checkouts/{id}

GET /api/tool-checkouts/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

POST/api/v1/tool-checkouts/{id}/checkin

POST /api/tool-checkouts/{id}/checkin — instant-first (IF1/IF2).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

CheckinRequest

Responses

StatusDescription
200
404not visible in scope
409already returned / line not on this checkout

Security: bearer

GET/api/v1/tool-kits

GET /api/tool-kits.

Parameters

NameInTypeRequiredDescription
activequery—No`true` = active kits only; absent = all.
limitquery—No

Responses

StatusDescription
200

Security: bearer

POST/api/v1/tool-kits

POST /api/tool-kits — instant-first (IF1/IF2).

Request body

CreateKitRequest

Responses

StatusDescription
200
409kit name already exists

Security: bearer

GET/api/v1/tool-kits/{id}

GET /api/tool-kits/{id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

PATCH/api/v1/tool-kits/{id}

PATCH /api/tool-kits/{id} — instant-first (IF1/IF2).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

UpdateKitRequest

Responses

StatusDescription
200
404not visible in scope
409version conflict

Security: bearer

GET/api/v1/tool-kits/{id}/availability

GET /api/tool-kits/{id}/availability — can the kit go out here?

Parameters

NameInTypeRequiredDescription
idpathstringYes
location_idquery—No

Responses

StatusDescription
200
404not visible in scope

Security: bearer

POST/api/v1/tool-kits/{id}/members

POST /api/tool-kits/{id}/members — add a member tool to the kit.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

AddKitMemberRequest

Responses

StatusDescription
200
404kit or item not visible in scope
409item is not a tool / already a member

Security: bearer

DELETE/api/v1/tool-kits/{id}/members/{member_id}

DELETE /api/tool-kits/{id}/members/{member_id}.

Parameters

NameInTypeRequiredDescription
idpathstringYes
member_idpathstringYes

Responses

StatusDescription
200
404kit or member not visible in scope

Security: bearer

GET/api/v1/tools/availability

GET /api/tools/availability — in-stock counts per tool item + location.

Parameters

NameInTypeRequiredDescription
location_idquery—No
item_idquery—No
limitquery—No

Responses

StatusDescription
200

Security: bearer