{"openapi":"3.1.0","info":{"title":"headless.design API","version":"1.1.0","summary":"Cold-read API — free /scan + paid /cold-read","description":"Public REST API for headless.design. Built agent-native: every endpoint (including unrouted 404s) returns RFC 9457 problem+json on errors, HATEOAS `_links` for valid state transitions, and IETF `RateLimit-*` headers on rate-limited endpoints.\n\n## Pilot phase — V2 transition in progress\n\nV1 endpoints (`POST /v1/cold-read/request`, `GET /v1/cold-read/{id}`) keep working but the canonical lifecycle moves to V2 (`POST /v1/cold-read-requests`, `GET /v1/orders/{id}`). V1 paths will keep working as 308-bridges with RFC 8594 `Sunset` headers for 60 days after the V2 cutover.\n\n## Agent-native constraints\n\n- `/v1/scan` is fully agent-completable end-to-end.\n- `/v1/cold-read-requests` requires human payment. Agents submit + monitor + relay; humans pay via Frilansfinans (B2B canonical) or Swish (privatperson). The custom A2A capability extension at `https://api.headless.design/extensions/human-handoff/v1` declares the handoff signaling.\n\n## Errors\n\nAll error responses follow RFC 9457 `application/problem+json`. Branch on the `type` URI (e.g. `https://headless.design/errors/rate-limited`), not on prose. Even unrouted paths return problem+json — no plain-text 404 leakage.","contact":{"name":"Gustaf Garnow","email":"gustaf@headless.design","url":"https://headless.design"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.headless.design","description":"Production"},{"url":"http://localhost:3000","description":"Local dev"}],"tags":[{"name":"scan","description":"Free 15-second landing-page scan"},{"name":"cold-read","description":"V1 — paid 72-hour cold-read (3 900 SEK ex moms). Deprecated in favor of `orders`; kept for 60-day transition window."},{"name":"orders","description":"V2 canonical — Order lifecycle (submitted → reviewing → accepted → billing_pending → paid → in_progress → delivered)."},{"name":"admin","description":"Admin-only — ADMIN_SECRET bearer token"},{"name":"internal","description":"Operator-only state mutations on Orders. Bearer ADMIN_SECRET."},{"name":"meta","description":"Discoverability + health"}],"components":{"securitySchemes":{"AdminBearer":{"type":"http","scheme":"bearer","description":"Admin-only endpoints. Value must equal ADMIN_SECRET env var."},"PipelineKey":{"type":"apiKey","in":"header","name":"X-Pipeline-Key","description":"Internal: pipeline → API callbacks (value from PIPELINE_API_KEY)."}},"headers":{"IdempotencyKey":{"description":"RFC-style idempotency header. UUID v4 recommended. Same key replayed within 24h returns the cached response unchanged.","schema":{"type":"string","minLength":8,"maxLength":256}}},"schemas":{"Problem":{"type":"object","description":"RFC 9457 problem+json error envelope.","required":["type","title","status"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"additionalProperties":true},"Link":{"type":"object","required":["href"],"properties":{"href":{"type":"string","format":"uri"},"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"]},"title":{"type":"string"}}},"Links":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Link"}},"PrimaryGoal":{"type":"string","enum":["book_call","paid_signup","newsletter","demo","other"]},"ClientHint":{"type":"object","description":"Optional caller-identity hint. Logged only, does not change behavior. Helps us see agent traffic.","properties":{"type":{"type":"string","enum":["agent","human","hybrid"]},"name":{"type":"string","maxLength":100},"on_behalf_of":{"type":"string","maxLength":200}}},"ScanRequest":{"type":"object","required":["url","primary_goal"],"properties":{"url":{"type":"string","format":"uri"},"primary_goal":{"$ref":"#/components/schemas/PrimaryGoal"},"audience":{"type":"string","maxLength":200}}},"ScanResponse":{"type":"object","required":["session_id","tier","_source","_links"],"description":"Scan result. Shape differs by `_source`: `lite` carries a `reviews` array; `pipeline` carries `scores`, `findings`, `sonnet`, `report_md`. Both carry `chat_summary` and `_links`.","properties":{"session_id":{"type":"string"},"tier":{"type":"string","enum":["scan"]},"_source":{"type":"string","enum":["lite","pipeline"]},"served_from":{"type":"string","enum":["live","cache"]},"url":{"type":"string","format":"uri"},"reviews":{"type":"array","items":{"type":"object"}},"scores":{"type":"object"},"findings":{"type":"object"},"sonnet":{"type":"object"},"chat_summary":{"type":"string","description":"Markdown summary optimized for AI agents to paste into chat conversations. ~500 chars."},"report_md":{"type":"string"},"lead_qualified":{"type":"boolean"},"upgrade_cta":{"type":"object"},"remaining_calls_today":{"type":"integer"},"_links":{"$ref":"#/components/schemas/Links"}}},"ColdReadRequest":{"type":"object","required":["url","primary_goal","audience","pain_point","email"],"properties":{"url":{"type":"string","format":"uri"},"primary_goal":{"$ref":"#/components/schemas/PrimaryGoal"},"audience":{"type":"string","minLength":10,"maxLength":200},"pain_point":{"type":"string","maxLength":280},"competitors":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3},"notes":{"type":"string","maxLength":500},"email":{"type":"string","format":"email"},"html":{"type":"string","maxLength":200000,"description":"Optional source HTML for PR-ready diff. If omitted, Gustaf will email within 1h to collect it."},"jsx":{"type":"string","maxLength":200000,"description":"Optional source JSX/TSX for PR-ready diff. Same collection flow as html."},"repo_url":{"type":"string","format":"uri","maxLength":300,"description":"Optional public git URL. Gustaf will pull the relevant file(s) for the diff."},"client":{"$ref":"#/components/schemas/ClientHint"}}},"ColdReadRequestResponse":{"type":"object","required":["invoice_id","pay_url","status_url","_links"],"properties":{"invoice_id":{"type":"string"},"landing_url":{"type":"string","format":"uri","deprecated":true,"description":"Alias for pay_url. Will be removed in v2."},"pay_url":{"type":"string","format":"uri"},"status_url":{"type":"string","format":"uri"},"poll_interval_sec":{"type":"integer"},"human_handoff":{"type":"boolean"},"next_action":{"type":"string"},"amount":{"type":"integer"},"currency":{"type":"string","enum":["SEK"]},"expires_at":{"type":"string","format":"date-time"},"_links":{"$ref":"#/components/schemas/Links"}}},"ColdReadStatus":{"type":"object","required":["invoice_id","status","_links"],"properties":{"invoice_id":{"type":"string"},"status":{"type":"string","enum":["pending_payment","paid","processing","draft_ready","done","expired"],"description":"Stable v1 enum. Renaming is a breaking change requiring /v2. `draft_ready` is the Model A tvåfas interim state — AI draft is out, Gustaf is QA'ing."},"eta":{"type":"string","format":"date-time","nullable":true},"eta_seconds":{"type":"integer","nullable":true},"draft_url":{"type":"string","format":"uri","nullable":true,"description":"AI-draft spec — live from `draft_ready` onward."},"final_url":{"type":"string","format":"uri","nullable":true,"description":"Human-QA'd final spec — live from `done` onward."},"diff_url":{"type":"string","format":"uri","nullable":true,"description":"PR-ready unified diff — optional, live from `done`."},"delivery_url":{"type":"string","format":"uri","nullable":true,"description":"Convenience alias: points at final_url on done, draft_url on draft_ready."},"rescan_used":{"type":"boolean"},"stages":{"type":"array","items":{"type":"object"}},"_links":{"$ref":"#/components/schemas/Links"}}},"RescanRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"The new URL to scan (post-ship)."}}},"OrderStatus":{"type":"string","description":"Order lifecycle state. Lifecycle: submitted → reviewing → (needs_info ↔ reviewing) → accepted → billing_pending → paid → in_progress → delivered. Terminal-failure: rejected, expired.","enum":["submitted","reviewing","needs_info","accepted","rejected","billing_pending","paid","in_progress","delivered","expired"]},"NextAction":{"type":"string","description":"Structured next-step hint, derived from `status`. Stable enum — agents can branch on this without parsing prose.","enum":["wait_for_review","provide_more_info","await_billing_instructions","complete_billing","wait_for_delivery","review_complete","request_closed"]},"BillingMethod":{"type":"string","description":"Billing rail. Frilansfinans = B2B canonical (Swedish umbrella service, real invoice with VAT + F-skatt, 14-day net). Swish = privatperson-only (no VAT, no receipt for bookkeeping).","enum":["frilans_finans","swish","undecided"]},"BillingStatus":{"type":"string","enum":["not_started","pending","confirmed","waived"]},"DeliveryStatus":{"type":"string","enum":["not_started","queued","active","delivered"]},"Source":{"type":"string","description":"Provenance of the request. `agent` = autonomous agent on behalf of a human; `web` = browser form; `manual` = operator-entered.","enum":["web","agent","manual"]},"BillingDetails":{"type":"object","description":"Customer-side billing fields. Frilansfinans (the canonical B2B rail in the pilot) has no public API — the operator manually creates the invoice in their portal. Without these fields the operator has to email-pingpong the buyer for missing details, adding 1-3h friction. Agent-native callers should collect from the human buyer up front. All optional individually; `org_number` (Swedish 10-digit) or `vat_id` (EU prefix + digits) is required IN PRACTICE for B2B Frilansfinans invoicing.","properties":{"company_name":{"type":"string","maxLength":200,"description":"Customer's company / legal entity name."},"org_number":{"type":"string","minLength":8,"maxLength":32,"description":"Swedish org-nummer (10 digits, e.g. `556677-8899`). Frilansfinans auto-fetches the registered name + billing address from Bolagsverket when set."},"vat_id":{"type":"string","minLength":4,"maxLength":32,"description":"EU VAT-id for non-Swedish EU customers (reverse charge — invoice goes out at 0 % moms). Country prefix + digits: `SE556677889901`, `DE123456789`, etc."},"address":{"type":"string","maxLength":500,"description":"Free-form billing address."},"contact_person":{"type":"string","maxLength":120,"description":"The human at the customer who'll receive the invoice email."}}},"OrderRequest":{"type":"object","required":["url","goal","audience","contact_email"],"description":"POST /v1/cold-read-requests body. Subset of fields — pilot keeps it lean. All optional fields can be set at creation OR added later via /v1/internal/orders/{id}/transition with a `patch`.","properties":{"url":{"type":"string","format":"uri"},"goal":{"type":"string","minLength":5,"maxLength":240,"description":"Freeform goal in the buyer's words."},"audience":{"type":"string","minLength":5,"maxLength":240},"constraints":{"type":"string","minLength":5,"maxLength":2000,"description":"Tone, do-not-touch, deadline hints."},"contact_email":{"type":"string","format":"email"},"primary_goal":{"$ref":"#/components/schemas/PrimaryGoal","description":"Optional structured tag alongside the freeform `goal`."},"pain_point":{"type":"string","maxLength":280},"competitors":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3},"notes":{"type":"string","maxLength":500},"html":{"type":"string","maxLength":200000,"description":"Optional source HTML for PR-ready diff."},"jsx":{"type":"string","maxLength":200000},"repo_url":{"type":"string","format":"uri","maxLength":300},"repo_format":{"type":"string","maxLength":50},"billing_method_preference":{"$ref":"#/components/schemas/BillingMethod"},"billing":{"$ref":"#/components/schemas/BillingDetails"},"source":{"$ref":"#/components/schemas/Source"},"agent_name":{"type":"string","maxLength":100},"agent_on_behalf_of":{"type":"string","maxLength":200},"client":{"$ref":"#/components/schemas/ClientHint"}}},"OrderRequestResponse":{"type":"object","required":["order_id","status","next_action","status_url","_links"],"description":"201 response from POST /v1/cold-read-requests. NOTE: there is intentionally no `pay_url` here — pay_url only materializes when the operator transitions the order to `billing_pending`.","properties":{"order_id":{"type":"string","description":"Stable id (INV-XXXXXXXX format)."},"service":{"type":"string","enum":["cold_read"]},"status":{"$ref":"#/components/schemas/OrderStatus"},"next_action":{"$ref":"#/components/schemas/NextAction"},"billing_status":{"$ref":"#/components/schemas/BillingStatus"},"delivery_status":{"$ref":"#/components/schemas/DeliveryStatus"},"human_handoff":{"type":"boolean"},"message":{"type":"string"},"status_url":{"type":"string","format":"uri"},"amount":{"type":"integer","description":"Integer SEK, major units (kronor). 3900 = 3 900 kr ex moms."},"currency":{"type":"string","enum":["SEK"]},"poll_interval_sec":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time","description":"7 days after creation. Past this, polling 404s."},"_links":{"$ref":"#/components/schemas/Links"}}},"Order":{"type":"object","required":["order_id","status","next_action","_links"],"description":"Full Order projection from GET /v1/orders/{id}. State-aware fields: `pay_url` present only when status = billing_pending; `_links.pay`/`draft_result`/`result`/`diff`/`rescan` only when their precondition holds.","properties":{"order_id":{"type":"string"},"service":{"type":"string","enum":["cold_read"]},"status":{"$ref":"#/components/schemas/OrderStatus"},"substatus":{"type":"string"},"next_action":{"$ref":"#/components/schemas/NextAction"},"human_handoff":{"type":"boolean"},"human_action_required":{"type":"boolean","description":"True when status = billing_pending or needs_info."},"a2a_task_state":{"type":"string","nullable":true,"enum":["input-required"],"description":"A2A v0.3.0 task-state mapping. Set to `input-required` when the buyer must act; null otherwise."},"url":{"type":"string","format":"uri"},"goal":{"type":"string"},"audience":{"type":"string"},"constraints":{"type":"string"},"contact_email":{"type":"string","format":"email"},"primary_goal":{"$ref":"#/components/schemas/PrimaryGoal"},"pain_point":{"type":"string"},"competitors":{"type":"array","items":{"type":"string","format":"uri"}},"amount":{"type":"integer"},"currency":{"type":"string","enum":["SEK"]},"billing_method_preference":{"$ref":"#/components/schemas/BillingMethod"},"billing_method_final":{"$ref":"#/components/schemas/BillingMethod"},"billing_status":{"$ref":"#/components/schemas/BillingStatus"},"billing":{"$ref":"#/components/schemas/BillingDetails"},"external_reference":{"type":"string","description":"Frilansfinans invoice number, Swish ref, etc."},"delivery_status":{"$ref":"#/components/schemas/DeliveryStatus"},"pay_url":{"type":"string","format":"uri","nullable":true,"description":"Only populated when status = billing_pending."},"draft_url":{"type":"string","format":"uri","nullable":true},"final_url":{"type":"string","format":"uri","nullable":true},"diff_url":{"type":"string","format":"uri","nullable":true},"delivery_url":{"type":"string","format":"uri","nullable":true,"description":"Alias: latest of draft_url/final_url."},"rescan_used":{"type":"boolean"},"rescan_url":{"type":"string","format":"uri"},"stages":{"type":"object"},"source":{"$ref":"#/components/schemas/Source"},"agent_name":{"type":"string"},"agent_on_behalf_of":{"type":"string"},"poll_interval_sec":{"type":"integer","description":"Recommended cadence: 30s on billing_pending, 600s on in_progress, 60s otherwise."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"reviewed_at":{"type":"string","format":"date-time","nullable":true},"accepted_at":{"type":"string","format":"date-time","nullable":true},"rejected_at":{"type":"string","format":"date-time","nullable":true},"billing_pending_at":{"type":"string","format":"date-time","nullable":true},"paid_at":{"type":"string","format":"date-time","nullable":true},"in_progress_at":{"type":"string","format":"date-time","nullable":true},"delivered_at":{"type":"string","format":"date-time","nullable":true},"expires_at":{"type":"string","format":"date-time"},"_links":{"$ref":"#/components/schemas/Links"}}},"OrderTransitionRequest":{"type":"object","required":["to_status"],"description":"POST /v1/internal/orders/{id}/transition body. The single endpoint for state mutations. Subsumes V1 mark-paid + mark-done.","properties":{"to_status":{"$ref":"#/components/schemas/OrderStatus"},"substatus":{"type":"string","maxLength":120},"note":{"type":"string","maxLength":2000,"description":"Operator note, appended to operator_notes audit trail."},"patch":{"type":"object","description":"Atomic patch applied alongside the transition (e.g. set billing fields when going to paid).","additionalProperties":false,"properties":{"billing_method_final":{"$ref":"#/components/schemas/BillingMethod"},"billing_status":{"$ref":"#/components/schemas/BillingStatus"},"external_reference":{"type":"string","maxLength":200},"delivery_status":{"$ref":"#/components/schemas/DeliveryStatus"},"final_url":{"type":"string","format":"uri"},"draft_url":{"type":"string","format":"uri"},"diff_url":{"type":"string","format":"uri"},"delivery_url":{"type":"string","format":"uri"},"spec_md":{"type":"string","maxLength":500000},"rescan_url":{"type":"string","format":"uri"},"api_key":{"type":"string","maxLength":100}}}}},"OrderTransitionResponse":{"type":"object","required":["order_id","status","next_action","updated_at"],"properties":{"order_id":{"type":"string"},"status":{"$ref":"#/components/schemas/OrderStatus"},"next_action":{"$ref":"#/components/schemas/NextAction"},"substatus":{"type":"string"},"billing_status":{"$ref":"#/components/schemas/BillingStatus"},"delivery_status":{"$ref":"#/components/schemas/DeliveryStatus"},"updated_at":{"type":"string","format":"date-time"}}}}},"paths":{"/v1/scan":{"post":{"tags":["scan"],"summary":"Run a free 15-second landing-page scan","description":"Synchronous. Returns scores + findings + chat_summary. Rate-limited per IP/URL/day.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanRequest"}}}},"responses":{"200":{"description":"Scan complete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResponse"}}}},"400":{"description":"Invalid input","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"URL unreachable or page empty","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Pipeline unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/cold-read-requests":{"post":{"tags":["orders"],"summary":"V2 canonical — submit a cold-read request (creates an Order)","description":"Creates an Order in `submitted` state. Operator reviews and transitions through the lifecycle. UNLIKE V1's /v1/cold-read/request, the 201 response does NOT include `pay_url` — pay_url only materializes when the operator transitions the order to `billing_pending`. Agents should poll `status_url` and watch for the transition.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":8,"maxLength":256}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}}},"responses":{"201":{"description":"Order created in submitted state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequestResponse"}}}},"400":{"description":"Invalid input","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Concurrent idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Idempotency-Key reused with different payload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/orders/{id}":{"get":{"tags":["orders"],"summary":"V2 canonical — get full Order state","description":"Returns the full Order projection. State-aware fields: `pay_url` only when status = billing_pending; `human_action_required` + `a2a_task_state: input-required` when buyer must act. Cache: 15s.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current Order state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"404":{"description":"Order not found or expired","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/internal/orders/{id}/transition":{"post":{"tags":["internal"],"summary":"Operator-only — mutate an Order's state","description":"Single endpoint for every state change. Validates the from→to pair against the lifecycle table; returns 409 with `allowed_transitions` on illegal moves. Optional `patch` is applied atomically with the transition (set billing fields when going to paid; set delivery fields when going to delivered). Subsumes V1 mark-paid and mark-done.","security":[{"AdminBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderTransitionRequest"}}}},"responses":{"200":{"description":"Transition succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderTransitionResponse"}}}},"400":{"description":"Invalid input","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid ADMIN_SECRET","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Order not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Illegal transition for current status","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/cold-read/request":{"post":{"tags":["cold-read"],"summary":"Create a paid /cold-read invoice","description":"Creates an invoice in `pending_payment` state and returns a `pay_url` the human must complete. Supports `Idempotency-Key` header (UUID) to make safe retries.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":8,"maxLength":256}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColdReadRequest"}}}},"responses":{"201":{"description":"Invoice created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColdReadRequestResponse"}}}},"400":{"description":"Invalid input","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Concurrent idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Idempotency-Key reused with different payload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/cold-read/{id}":{"get":{"tags":["cold-read"],"summary":"Poll invoice/cold-read status","description":"Returns state machine state. Cached 30s for non-terminal states.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColdReadStatus"}}}},"404":{"description":"Invoice not found or expired","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/cold-read/{id}/rescan":{"post":{"tags":["cold-read"],"summary":"One-shot follow-up scan after the cold-read ships","description":"Runs a fresh /scan-style review against the new URL. Available only when cold-read status=done and rescan_used=false. Burns the slot on first call.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescanRequest"}}}},"responses":{"200":{"description":"Rescan complete","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Invoice not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Rescan already used or wrong status","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"URL unreachable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Pipeline unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/deliverables/{invoice_id}/{file}":{"get":{"tags":["cold-read"],"summary":"Download a cold-read artifact","description":"Returns raw markdown or unified diff for the requested file. Unauthenticated — access gated by the unguessable invoice_id in the URL path.","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"file","in":"path","required":true,"schema":{"type":"string","enum":["spec_draft.md","spec_final.md","diff.patch"]}}],"responses":{"200":{"description":"Artifact content","content":{"text/markdown":{"schema":{"type":"string"}},"text/x-diff":{"schema":{"type":"string"}}}},"404":{"description":"Unknown file or artifact not stored","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/ping":{"get":{"tags":["meta"],"summary":"Health check","responses":{"200":{"description":"Alive","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/manifest":{"get":{"tags":["meta"],"summary":"Who built this, why, how","responses":{"200":{"description":"Manifest","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/openapi.json":{"get":{"tags":["meta"],"summary":"This spec","responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}