Hero is now a data-native 0pt artifact (no photos, keeps grain/reveal/float); header is the 0pt wordmark with Swedish nav; layout is sv + loads JetBrains Mono + 0pt metadata; globals wires the loaded fonts. Adds CLAUDE.md + docs/01-09 as the build contract. next build green (5 routes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
4.5 KiB
Markdown
98 lines
4.5 KiB
Markdown
# 04 · API contract (FastAPI)
|
|
|
|
Canonical REST contract. Reconcile the existing 25+ endpoints in `src/orchestrator/main.py` toward this. JSON in/out, `snake_case`. All amounts in **öre**. Auth: Next.js → FastAPI over a shared internal token (`X-Internal-Token`); customer identity passed as `org_id`/`user_id` resolved from the Auth.js session server-side.
|
|
|
|
## Conventions
|
|
- Errors: `{ "error": { "code", "message" } }`, proper HTTP status. Interface-voice messages (Swedish) are produced in the frontend, not here.
|
|
- Idempotency: accept `Idempotency-Key` on POSTs that create orders/payments.
|
|
- Pagination: `?limit=&cursor=`.
|
|
|
|
## Catalog
|
|
```
|
|
GET /catalog -> [{id,title,markets,entity_type,count,updated,price_ore,confidence}]
|
|
GET /catalog/{id} -> full dataset (overview, coverage, schema, quality summary)
|
|
GET /catalog/{id}/sample?n=10 -> sample rows WITH per-field provenance
|
|
```
|
|
|
|
## Estimate (configurator → price/time/confidence)
|
|
```
|
|
POST /estimates
|
|
body: { flow, markets[], entity_type, fields[], enrichment[], volume, frequency, confidence_target, formats[] }
|
|
-> { total_ore, deposit_ore, balance_ore, est_days, est_confidence, breakdown[] }
|
|
```
|
|
|
|
## AI-assist (NL → config)
|
|
```
|
|
POST /ai/parse
|
|
body: { text } -- "5000 svenska SaaS-bolag med e-post och omsättning"
|
|
-> { config: <estimate body>, rationale } -- prefilled configurator; uses pgvector match to catalog/fields
|
|
```
|
|
|
|
## Orders (lifecycle)
|
|
```
|
|
POST /orders body: { flow, config } -> { order_id, status:'draft'|'quoted' }
|
|
GET /orders -> [orders for org]
|
|
GET /orders/{id} -> order + current job + delivery (if any)
|
|
POST /orders/{id}/scope (custom flow) body:{ notes } -> status:'quoted' (manual quote step)
|
|
POST /orders/{id}/accept customer approves delivery -> triggers balance release; status:'accepted'
|
|
POST /orders/{id}/revise body:{ notes } -> status:'revision'; escrow untouched
|
|
```
|
|
|
|
## Payments (50/50 escrow)
|
|
```
|
|
POST /payments/{order_id}/deposit -> { checkout_url } (Stripe Checkout for 50%; on success → IN_PRODUCTION)
|
|
POST /payments/{order_id}/release -> captures/charges the held balance (called on accept)
|
|
POST /webhooks/stripe (Stripe → here) verify signature; update payments + order status
|
|
```
|
|
> Implement balance as either a manual capture on a held PaymentIntent, or a second Checkout/invoice keyed to `order_id`. Never charge balance before `accept`.
|
|
|
|
## Jobs / live status
|
|
```
|
|
GET /orders/{id}/status -> { stage, pct, per_market[], confidence, eta, rows }
|
|
GET /orders/{id}/events -> Server-Sent Events stream (preferred for the production tracker)
|
|
```
|
|
|
|
## Uploads (R2 presigned)
|
|
```
|
|
POST /uploads body:{ order_id, name, mime, bytes } -> { file_id, put_url } (client PUTs to R2)
|
|
GET /orders/{id}/files -> [files]
|
|
```
|
|
|
|
## Customer DB access (read-only grant)
|
|
```
|
|
POST /connections/db-grant
|
|
body: { order_id, conn_string, scope, expires_at } -- stored ENCRYPTED; validated read-only
|
|
-> { grant_id, status:'pending'|'verified' }
|
|
DELETE /connections/db-grant/{id} -> revoke
|
|
```
|
|
|
|
## Deliveries
|
|
```
|
|
GET /orders/{id}/delivery -> { formats, destinations, record_count, avg_confidence, quality_report, files[], provenance_export_url }
|
|
POST /orders/{id}/delivery/destinations body:{ destinations[] } -> updates targets (CSV/Parquet/JSON/API/NocoDB/Metabase/FiveCRM/Sheets/webhook)
|
|
```
|
|
|
|
## Sources (the transparency moat)
|
|
```
|
|
GET /sources -> [{source_id,name,market,access,licence,cadence,confidence}]
|
|
GET /sources/{market} -> sources + freshness for a market
|
|
```
|
|
|
|
## Subscriptions / feeds
|
|
```
|
|
POST /subscriptions body:{ order_id, cadence, webhook_url } -> { sub_id }
|
|
GET /subscriptions -> [subs]
|
|
POST /subscriptions/{id}/pause | /resume
|
|
```
|
|
|
|
## Webhooks out (to customers)
|
|
- On delivery / feed update: POST to `subscriptions.webhook_url` with `{ order_id, delta_url, summary }`.
|
|
|
|
## Health
|
|
```
|
|
GET /health -> { ok, db, workers, queue }
|
|
```
|
|
|
|
## Existing-code reconciliation
|
|
Map current endpoints (orders, estimates, ai-assistent, catalog, webhooks, health) onto the names above; rename for consistency, keep behavior. Add: `/orders/{id}/events` (SSE), `/payments/*` escrow split, `/connections/db-grant`, `/sources`, `/subscriptions`. Remove any endpoint that drives a bypass/stealth fetch path.
|