← Case study

POSTGRESQL REFERENCE · WORKING SOURCE

docs/api.md

This file ships in the runnable source archive. Synthetic fixtures and demonstration policies are identified in the README.

Download the complete project ↓
# HTTP API

Base URL for the local demo: `http://127.0.0.1:8000`. The server returns JSON directly, without a `data` envelope. Types for the core requests and responses are in `src/contracts.ts`; server validation remains authoritative.

## Authentication and mutations

`POST /api/login` accepts `{ "email": "shipper@cedar.demo", "password": "warehouse-demo" }` and returns `{user,csrfToken,now}` while setting an HttpOnly session cookie. `GET /api/session` reads the current identity. `POST /api/logout` removes that session.

Every POST except login requires `Content-Type: application/json` and `X-CSRF-Token` from the authenticated session. Browser Origin must match the application origin. Confirmation, receiving, physical release and movement adjustments also require an `Idempotency-Key` header of 1–160 characters. Keep the same key **and** payload for a transport retry; use a new key for a different intent. The key is scoped to actor, action and resource.

The demo session lasts eight actual wall-clock hours, independently of the controllable operational clock. Client-supplied customer/organization IDs never grant access or determine ownership. Unknown resource IDs and another tenant's resource IDs both return 404.

## Routes

| Method and route | Role / purpose | Request or response |
| --- | --- | --- |
| `GET /healthz` | Unauthenticated DB health | `{ok,service}` after a real DB query |
| `GET /api/bootstrap` | Authenticated, scoped | User, clock, accessible warehouses/zones/positions, own/managed reservations, pallets, quotes, holds, release requests, blocks and derived exceptions |
| `GET /api/capacity` | Authenticated, permitted warehouse | Query `warehouseId,zoneId,startAt,endAt`; physical counts, continuously bookable positions, hourly projected claims/deficits, position state and allowed exception details |
| `POST /api/quotes` | Customer | Storage request shown below; returns itemized estimate, accepted profile, tariff version, expiry and advisory capacity |
| `POST /api/holds` | Customer | `{quoteId}`; returns hold ID, expiry and allocated position IDs |
| `POST /api/holds/:id/confirm` | Customer, idempotent | `{}`; returns confirmed reservation and accepts its quoted rates |
| `GET /api/reservations/:id` | Owner customer / warehouse operator | Reservation summary with `quantity,unreceived,inCustody,released,status,receivingStatus` |
| `POST /api/reservations/:id/receive` | Operator, idempotent | Pallet entries and optional explicit variance approval; returns reservation, pallets, movement IDs and variance facts |
| `POST /api/reservations/:id/close-receiving` | Operator | `{disposition:"release"|"retain",reason}`; explicit treatment of unreceived balance |
| `POST /api/reservations/:id/amend` | Owner customer / operator | `{startAt,endAt,quantity,reason}`; protected replacement preserving old allocation on failure |
| `POST /api/reservations/:id/cancel` | Owner customer / operator | `{reason}`; clears only unreceived commitment |
| `POST /api/reservations/:id/expire` | Operator | `{reason}`; only after interval end, clears only unreceived commitment |
| `POST /api/reservations/:id/release-requests` | Owner customer / operator | `{palletIds,reason}`; requests departure, without changing custody |
| `POST /api/reservations/:id/release` | Operator, idempotent | `{palletIds,reason,releaseRequestId?}`; atomically records actual departure and charge evidence |
| `GET /api/pallets/:id` | Owner customer / operator | Stable internal ID, optional external reference, current/last custody state and declaration |
| `POST /api/pallets/:id/relocate` | Operator | `{positionId,reason}`; compatible same-zone destination checked through expected departure; overdue custody requires destination free of all known future claims |
| `POST /api/blocks` | Operator | `{warehouseId,positionIds,startAt,endAt,reason}`; records actual outage even when it affects promises, returns blocks/exceptions |
| `POST /api/blocks/:id/unblock` | Operator | `{reason}`; records clearing with an audit event |
| `GET /api/reservations/:id/invoice` | Owner customer / operator | Agreed tariff, original quote, actual/provisional lines, summary and supporting movements |
| `POST /api/movements/:id/adjustments` | Operator, idempotent | `{reason,note,amountCents?}`; linked append-only annotation and optional exact-cent adjustment |
| `GET /api/audit?reservationId=...` | Owner customer / operator | Scoped commercial/operational audit entries with actor, occurrence/recording times, reason and data |
| `POST /api/demo/clock` | Seeded demo operator only | `{now:ISO}`; forward-only, locks warehouses before changing clock; unavailable when DEMO_MODE is false |

There is no browser reset endpoint, automatic expiration job, hold cancellation endpoint, payment endpoint, reconciliation endpoint or public identity administration API in this release.

## Storage request

```json
{
  "warehouseId": "wh-denver",
  "zoneId": "zone-denver-a",
  "quantity": 6,
  "startAt": "2026-10-26T09:00:00-06:00",
  "endAt": "2026-10-29T00:00:00-06:00",
  "pallet": {
    "lengthMm": 1219,
    "widthMm": 1016,
    "heightMm": 1500,
    "weightKg": 800,
    "condition": "ambient",
    "goods": "packaged_dry"
  }
}
```

New booking inputs use exact-hour timestamps, positive half-open intervals and a maximum 2160 elapsed hours. Quantity is 1–100; actual zone supply is independently enforced. Quotes consume no capacity. Holds allocate compatible positions across the whole interval, expire after ten demo minutes capped by quote expiry, and atomically convert on confirmation. Quotes expire after thirty demo minutes. A start that has elapsed makes a new hold/confirmation ineligible even if its rate snapshot has not expired.

## Receive five of six

```json
{
  "pallets": [
    {"label":"DEMO-CEDAR-001"},
    {"label":"DEMO-CEDAR-002"},
    {"label":"DEMO-CEDAR-003"},
    {"label":"DEMO-CEDAR-004"},
    {"label":"DEMO-CEDAR-005"}
  ],
  "reason":"Five pallets arrived against six expected"
}
```

A pallet entry may include `externalId` and actual `lengthMm,widthMm,heightMm,weightKg,condition,goods` overrides. Every supplied physical override is validated against the zone. The default is the reservation's accepted declaration. External references are unvalidated strings, unique within the owner's records; they do not assert GS1 conformance. The application assigns a stable synthetic internal pallet ID independently.

Early arrival or excess delivery additionally requires literal boolean `approveVariance:true` and a reason. Receiving after the booked end requires `expectedEndAt` as an exact-hour future instant, at most 90 elapsed days from the receipt. Each path rechecks capacity under the warehouse lock, including existing occupied positions affected by the extension. A failed variance leaves the reservation and inventory unchanged.

Operations use the server clock; caller-supplied receipt/release times are not accepted as authority. Actual early receipts may establish a precise operational start. An amendment can retain that exact existing start; a changed start must be an exact hour and cannot create a new backdated promise.

Close the remaining balance with:

```json
{"disposition":"release","reason":"Delivery complete; release unused sixth position"}
```

Use `retain` to keep the remaining commitment without charging it. Reopening/altering the expected balance requires an explicit protected amendment. Released quantities are selected pallet IDs; the API never accepts an unbounded quantity detached from physical goods.

## Invoice response and evidence

The fixture summary is:

```json
{
  "palletDays": 13,
  "storageCents": 2600,
  "receivingCents": 2000,
  "releaseCents": 1500,
  "adjustmentCents": 0,
  "totalCents": 6100
}
```

Each line includes `id,kind,palletId,palletLabel,serviceDate,quantity,unitCents,totalCents,movementIds,rateVersion,rateCardId,provisional`. Handling lines use `serviceDate:null`; storage lines carry the warehouse-local date. Open storage accrual is provisional and computed without a write. Released storage and completed handling are persisted in the same transaction as operational evidence. Every total is integer cents.

Each movement includes `id,kind,palletId,occurredAt,recordedAt,actorName,ownerOrgId,fromPositionId,toPositionId,reason,adjustmentOf,data`. Adjustment records retain their link to the original event. They do not mutate old event timestamps or custody quantities. Arbitrary historical stock reconstruction is outside this correction interface.

## Errors and retries

```json
{
  "error": {
    "code": "CAPACITY_CONFLICT",
    "message": "Only 1 compatible positions are available for the entire interval; 2 were requested.",
    "details": {"available":1,"requested":2},
    "retryable": false
  }
}
```

| HTTP | Typical codes | Client response |
| ---: | --- | --- |
| 400 | `INVALID_JSON`, `IDEMPOTENCY_REQUIRED` | Correct request shape/header |
| 401 | `UNAUTHENTICATED`, `INVALID_CREDENTIALS` | Sign in; do not infer another tenant's resource existence |
| 403 | `FORBIDDEN`, `CSRF`, `DEMO_DISABLED` | Use an authorized session/action and fresh CSRF token |
| 404 | `NOT_FOUND` | Resource missing or not visible to this principal |
| 409 | `CAPACITY_CONFLICT`, `IDEMPOTENCY_CONFLICT`, `INVALID_TRANSITION`, `HOLD_EXPIRED`, `QUOTE_EXPIRED`, `QUOTE_INTERVAL_STARTED`, `EXCESS_DELIVERY`, `EARLY_RECEIPT`, `LATE_RECEIPT`, `INVALID_RELEASE`, `CLOCK_BACKWARDS`, `RESOURCE_CONFLICT` | Show persisted state and specific corrective action; changing intent uses a new key |
| 413 / 415 | `PAYLOAD_TOO_LARGE`, `CONTENT_TYPE` | JSON request size is limited to 64 KiB; send JSON |
| 422 | `VALIDATION`, `INCOMPATIBLE_PALLET`, `INVALID_INTERVAL` | Correct the declaration or choose a compatible zone/interval |
| 503 | `RETRY_TRANSACTION` | Transaction retry budget exhausted; retry original operation with original key/payload |
| 500 | `INTERNAL_ERROR` | No partial operational transaction was saved; inspect diagnostics and retry an idempotent action with its original key |

The transaction layer retries complete transactions on PostgreSQL `40001`, `40P01` and `55P03`, with jitter and a maximum of three attempts. A five-second lock timeout bounds each lock attempt. There is no automatic retry of a partially committed domain operation.