Files
SlabUploader/docs/contracts/API.md
T
Ty 01aa4c52c3 docs(phase2): reconcile requirements and migrate capabilities/contracts
Surface-matrix sweep of every source doc; added nine missed behaviors (METADATA-115, REVIEW-50, APP-55/60/65, SETTINGS-45 mode, Generate-text availability, defaults-off). 153 requirements, 0 coverage gaps.

Migrated capabilities to docs/capabilities/ and contracts to docs/contracts/; added WOO-API, API, DATA-MODEL, PROMPTS, UX, TEST-SPEC. Populated MILESTONES; branch-neutral trunk docs; renamed umbrella term to Woo Slab Taxonomy. Archived reconciliation working records under .audit/archive/.
2026-09-18 21:02:06 -07:00

192 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# App API contract — client ↔ FastAPI
Status: SPEC · 2026-09-18.
Base path `/api/v1`. Health at `/api/health`. All JSON unless noted. Timestamps UTC ISO-8601.
The outbound WooCommerce payload is owned by `docs/contracts/WOO-API.md`.
## Requirements impact
| Requirement | Impact on this design |
|---|---|
| APP-5 | slab status values and transitions |
| APP-20 | client ids, UTC timestamps, `client_rev`/`server_rev` |
| APP-25 | FastAPI is the only Woo and inference caller |
| APP-30 | `/api/v1` base path, `/api/health` |
| APP-35 | error envelope, full lists, JSON |
| APP-40 | server stores client numbers, never recomputes |
| APP-45 | health body and non-200 condition |
| APP-50 | upstream Woo 4xx mapped into the envelope |
| CAPTURE-15, CAPTURE-165 | offline and U2Net are out of scope |
| CAPTURE-25, CAPTURE-30, CAPTURE-115, CAPTURE-130 | calibration choice and batch photos |
| CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90 | length/thickness units and derived numbers |
| CAPTURE-95, CAPTURE-105, CAPTURE-135, CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155 | PNG output, upload, preview/controls |
| METADATA-5, METADATA-10, METADATA-15, METADATA-30 | taxonomy cardinality and scoping |
| METADATA-35, METADATA-40, METADATA-45, METADATA-50, METADATA-55 | Call 1 / Call 2 |
| METADATA-60, METADATA-65, METADATA-70, METADATA-75, METADATA-100, METADATA-105, METADATA-110, METADATA-115 | context, constraints, fallback, prompts, failure |
| OPS-55 | no client auth |
| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20 | create sequence, draft rule, duplicate, stale |
| PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-35, PUBLISH-40, PUBLISH-45, PUBLISH-50, PUBLISH-55, PUBLISH-60 | create outcomes, images, payload |
| REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-40, REVIEW-45, REVIEW-50 | ready set, fields, limits |
| SETTINGS-5, SETTINGS-10, SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-30 | env URL, credentials, crypto |
| SETTINGS-35, SETTINGS-40, SETTINGS-45, SETTINGS-50, SETTINGS-51, SETTINGS-55, SETTINGS-60, SETTINGS-65, SETTINGS-75, SETTINGS-80, SETTINGS-85, SETTINGS-90 | rates, tests, taxonomy, flags |
## 1. Conventions
- **Auth:** no client auth; the app relies on the trusted LAN/Tailscale network plus HTTPS.
- **Envelope:** every error is `{ "error": { "code": string, "message": string, "detail": object|null } }`.
- **Lists:** returned in full, no pagination.
- **Revisions:** create/update carry `client_rev`; the server echoes `server_rev`; a mismatch is
`409 revision_conflict`.
- **IDs and time:** slab ids are client-generated UUIDv4; timestamps are UTC ISO-8601.
- **Photos:** `multipart/form-data` with `data` (JSON), `files[]` (original images), and `meta[]`
(`{ "seq": n }`). One photo is the calibration photo, chosen by the owner.
- **Secrets:** never returned; only a configured boolean.
- **Error display:** raw status codes, stack traces, and internal codes never reach the owner.
## 2. Error codes
| Status | Code | Meaning |
|---|---|---|
| 400 | `validation` | payload failed validation |
| 404 | `not_found` | unknown slab, photo, or setting |
| 409 | `revision_conflict` | `client_rev`/`server_rev` mismatch; `detail.current_server_rev` |
| 409 | `duplicate_sku` | SKU exists in Woo under any status |
| 422 | `unpublishable` | slab not publishable or missing a required field |
| 422 | `stale_field` | a picked taxonomy id vanished; returns current options |
| 502 | `woo_error` | WooCommerce rejected the request; `detail` carries the Woo body |
| 500 | `internal` | unexpected |
There is no `412 validation_module_stale` path.
## 3. Health and meta
- `GET /api/health` → `{ "status": "ok", "version": string, "woo_reachable": bool }`. Non-200 when
the database or image volume is not writable.
- `GET /api/v1/meta` → `{ "version": string }`.
## 4. Slabs
### `POST /api/v1/slabs`
Multipart create of a calibrated draft. Returns `201` with the slab at `status: "calibrated"`.
Stores the client-sent derived numbers and never recomputes them. Auto-runs Call 1 only when
`inference_enabled` is true; inferred values persist only at confidence ≥ 0.7. `409 duplicate_sku`
if the SKU already exists.
`SlabCreate` fields:
| Field | Type | Notes |
|---|---|---|
| `id` | uuid | client-generated |
| `sku` | string | 3–24, alphanumeric and hyphen |
| `length_in`, `thickness_in` | number | 1/8 inch steps |
| `sqft`, `bdft`, `width_min_in`, `width_max_in`, `width_avg_in` | number | client-computed |
| `species_id` | int | one species leaf |
| `wood_category_ids` | int[] | one or more |
| `edge_type_term_id` | int | one |
| `figure_term_ids` | int[] | one or more |
| `grade_term_id` | int | exactly one |
| `thickness_term_id` | int | round-up band |
| `moisture_term_id` | int | default kiln-dried |
| `fig_tag_ids` | int[] | optional |
| `feat_tag_ids` | int[] | optional |
| `price` | number | 2 decimals |
| `price_source` | string | `recommendation` \| `override` |
| `title`, `short_description`, `description` | string\|null | prose |
| `content_source` | string | `manual` \| `llm` \| `template` |
| `client_rev` | int | optimistic concurrency |
Grade, edge, thickness, and moisture are singular; figure and the tag lists are arrays.
### `GET /api/v1/slabs`
Returns `[Slab]`, most recent first.
### `GET /api/v1/slabs/{id}`
Returns the `Slab`. Also the poll endpoint for inference and publish. The client backs off
(1s, 2s, 4s, cap 10s).
### `PUT /api/v1/slabs/{id}`
Partial update of editable fields plus `client_rev`. Stores client-sent derived fields and does
not recompute them. `409 revision_conflict`, `409 duplicate_sku` (SKU field actions `edit_sku` /
`open_existing`), `422 stale_field` (current options included).
### `DELETE /api/v1/slabs/{id}`
Soft delete of unpublished drafts only; `204`. Published slabs cannot be deleted from the app.
## 5. Photos
- `GET /api/v1/slabs/{id}/photos/{pid}/original` → `image/*` while retained.
- `GET /api/v1/slabs/{id}/photos/{pid}/processed` → `image/*` (transparent PNG) while retained.
- After publish both return `410 Gone`.
- `POST /api/v1/slabs/{id}/u2net/{pid}` is reserved post-POC and is not exposed.
## 6. Inference
### `POST /api/v1/slabs/{id}/infer-taxon`
Runs or retries Call 1. Sends the clean processed photos at 1024 px long edge or less, the synced
taxonomy, and the user metadata. Returns `202`; the client polls. A failure or timeout follows the
runtime failure rule (recoverable offers Retry, unrecoverable explains).
### `POST /api/v1/slabs/{id}/infer-content`
Runs Call 2 on user action only. Fresh request carrying the full Call 1 context; no provider
thread id. Requires `test-inference` `ok`. Returns `202`; the client polls. The model writes prose
only and never touches dimensions.
Call 1 result fields: `species_id`, `species_confidence`, `wood_category_ids`,
`edge_type_term_id`, `figure_term_ids`, `figure_confidence`, `grade_term_id`, `grade_confidence`,
`character`, `inclusions`, `voids`, `checks`. Free-text observations map to synced `feat-*` tags
only.
Call 2 result fields: `title`, `short_description`, `description`.
## 7. Publish
### `POST /api/v1/slabs/{id}/publish`
Preconditions are the full ready set, else `422 unpublishable` with field keys. Sequence: validate
the ready set; look up the SKU and stop if present; refresh taxonomy when the last sync is over
one hour old; resolve categories, tags, and attributes; upload the processed PNGs to Woo media;
`POST /products` per `docs/contracts/WOO-API.md`; finalize. `SLAB-UAT-*` always creates a draft.
Returns `202`; the client polls.
- Success: `woo_product_id` set, photos purged, local slab row deleted, device draft cleared.
- Failure: attempt marked failed, photos retained, `sync_log` written, plain message on the
affected field or action, retry from the same screen.
- Partial failure (media uploaded, product POST failed): orphaned media left, logged, manual
cleanup note surfaced.
## 8. Taxonomy
- `GET /api/v1/admin/taxonomy` → grouped categories, attributes with terms, and tags, plus
`taxonomy_anchor` and `taxonomy_last_sync_at`. Only fig-* and feat-* tag families are kept.
These lists are the only legal picker options.
- `POST /api/v1/admin/taxonomy/sync` → pulls from Woo, rebuilds the cache in one transaction, sets
`taxonomy_last_sync_at`, bumps `taxonomy_anchor` only when the stored-subset hash changes, and
on failure returns `502` while keeping the previous cache.
## 9. Pricing
- `GET /api/v1/admin/pricing` → `[PriceRule]`
- `POST /api/v1/admin/pricing` → `201`
- `PUT /api/v1/admin/pricing/{id}` → `200`
- `DELETE /api/v1/admin/pricing/{id}` → `204`
One flat `price_per_bdft` per species leaf, no tiers.
## 10. Settings
- `GET /api/v1/settings` → `SettingsView` with secrets masked.
- `PUT /api/v1/settings` — partial update. `woo_base_url` is not writable; the server rejects the
write. Woo credentials are write-only and stored AES-GCM. Inference base URL and key are
encrypted; flags, brand voice, and geo context are settings. Secrets are never echoed.
- `POST /api/v1/settings/test-woo` → `{ "ok": bool, "detail": string, "taxonomy_anchor": string,
"taxonomy_last_sync_at": string }`. Fails closed on missing credentials or non-HTTPS; an auth
rejection is distinct from an unreachable store; on success always resyncs.
- `POST /api/v1/settings/test-inference` → `{ "ok": bool, "mode": "stateful"|"stateless"|"fail",
"vision_capable": bool, "detail": string }`. `fail` blocks Call 2 only.
## 11. Removed from v1
- The shared validation module and its `412` path.
- Provider thread ids and `previous_response_id` (Call 2 is a fresh full-context call).
- The U2Net control; the endpoint is reserved only.
- Offline/IndexedDB sync.