{"openapi":"3.1.0","info":{"title":"Synthetic Users API","version":"1.0.0","description":"Public customer API for the Synthetic Users platform. All routes except GET /health require `Authorization: Bearer sk_<env>_...` (an API key minted via POST /api-keys). Every error response has the shape `{\"error\":{\"code\",\"message\"}}`. Every response carries an `X-Request-Id` header."},"servers":[{"url":"https://api.syntheticusers.io"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"health"},{"name":"trial"},{"name":"api-keys"},{"name":"allowed-targets"},{"name":"deploy-checks"},{"name":"runs"},{"name":"personas"},{"name":"credentials"},{"name":"webhooks"},{"name":"billing"},{"name":"ux-maps"}],"paths":{"/health":{"get":{"tags":["health"],"summary":"Service health check (unauthenticated)","operationId":"getHealth","security":[],"responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"type":"object","required":["ok","service","version"],"properties":{"ok":{"type":"boolean","const":true},"service":{"type":"string","examples":["synthetic-users-api"]},"version":{"type":"string"}}}}}}}}},"/trial/preview":{"post":{"tags":["trial"],"summary":"Public trial-flow lite-fetch preview (unauthenticated)","description":"Real navigate + screenshot + title via the agent-browser container, plus one non-agentic Haiku call for a one-line description. NOT the full synthetic-user agent loop — no persona, no multi-turn exploration, no report. Rate-limited to 5 requests/hour per IP and cached by hostname for ~24h. CORS is an explicit allowlist of marketing origins; never `*`.","operationId":"postTrialPreview","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"https-only; RFC1918, loopback, link-local, and other internal hosts are rejected."}}}}}},"responses":{"200":{"description":"Preview fetched (live or served from the hostname cache)","content":{"application/json":{"schema":{"type":"object","required":["title","description","screenshot"],"properties":{"title":{"type":"string"},"description":{"type":"string","description":"One-line, LLM-generated description of the site."},"screenshot":{"type":"object","required":["base64","contentType"],"properties":{"base64":{"type":"string"},"contentType":{"type":"string","const":"image/png"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"The container session or the description LLM call failed (`error.code` is `container_error` or `llm_error`). Never a fabricated 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys":{"post":{"tags":["api-keys"],"summary":"Mint a new API key (plaintext returned exactly once)","operationId":"createApiKey","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"env":{"type":"string","enum":["live","test"],"default":"live","description":"Any value other than \"test\" is treated as \"live\"."},"scopes":{"type":"array","items":{"type":"string"},"description":"Accepted but ignored in V1; the key is always minted with scopes [\"*\"]."}}}}}},"responses":{"201":{"description":"Key minted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiKeyMetadata"},{"type":"object","required":["plaintext"],"properties":{"plaintext":{"type":"string","description":"Full API key plaintext (`sk_live_...` or `sk_test_...`). Returned exactly once; never retrievable again."}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"tags":["api-keys"],"summary":"List API key metadata (no plaintext, no hash)","operationId":"listApiKeys","responses":{"200":{"description":"Keys for the calling customer, oldest first","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyMetadata"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api-keys/{id}":{"delete":{"tags":["api-keys"],"summary":"Revoke an API key (soft delete; idempotent)","operationId":"revokeApiKey","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Key revoked (or already revoked). `kv_evicted` is only present on the request that performs the revocation.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiKeyMetadata"},{"type":"object","properties":{"kv_evicted":{"type":"boolean","description":"Whether the hot-path KV tombstone write succeeded. Absent when the key was already revoked."}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/allowed-targets":{"post":{"tags":["allowed-targets"],"summary":"Add a host pattern to the run-target allowlist","operationId":"createAllowedTarget","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["host_pattern"],"properties":{"host_pattern":{"type":"string","description":"Hostname pattern. RFC1918, `.internal`, and `.local` patterns are rejected."}}}}}},"responses":{"201":{"description":"Pattern added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllowedTarget"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"tags":["allowed-targets"],"summary":"List allowlist patterns","operationId":"listAllowedTargets","responses":{"200":{"description":"Patterns for the calling customer, sorted by host_pattern","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AllowedTarget"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/allowed-targets/{id}":{"delete":{"tags":["allowed-targets"],"summary":"Remove an allowlist pattern","operationId":"deleteAllowedTarget","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/deploy-checks":{"post":{"tags":["deploy-checks"],"summary":"Create a Deploy Check (trigger token returned once)","operationId":"createDeployCheck","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployCheckWriteBody"}}}},"responses":{"201":{"description":"Deploy Check created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/DeployCheck"},{"type":"object","required":["trigger_token"],"properties":{"trigger_token":{"type":"string","pattern":"^su_dc_[a-z0-9]{32}$","description":"Check-specific trigger token for POST /deploy-checks/{id}/events. Returned once; only its hash is stored."}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"tags":["deploy-checks"],"summary":"List non-archived Deploy Checks","operationId":"listDeployChecks","responses":{"200":{"description":"Deploy Checks for the calling customer, oldest first","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DeployCheck"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/deploy-checks/{id}":{"get":{"tags":["deploy-checks"],"summary":"Get a Deploy Check","operationId":"getDeployCheck","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Deploy Check","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployCheck"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["deploy-checks"],"summary":"Update a Deploy Check (fields merged with existing config, then re-validated)","operationId":"updateDeployCheck","parameters":[{"$ref":"#/components/parameters/PathId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployCheckWriteBody"}}}},"responses":{"200":{"description":"Updated Deploy Check","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployCheck"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"tags":["deploy-checks"],"summary":"Archive a Deploy Check (soft delete; idempotent)","operationId":"deleteDeployCheck","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"204":{"description":"Archived (or already archived)"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/deploy-checks/{id}/rotate-token":{"post":{"tags":["deploy-checks"],"summary":"Rotate the trigger token (new plaintext returned once)","operationId":"rotateDeployCheckToken","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"New trigger token","content":{"application/json":{"schema":{"type":"object","required":["deploy_check_id","trigger_token"],"properties":{"deploy_check_id":{"type":"string"},"trigger_token":{"type":"string","pattern":"^su_dc_[a-z0-9]{32}$"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/deploy-checks/{id}/events":{"get":{"tags":["deploy-checks"],"summary":"List the last 100 deploy events for a check","operationId":"listDeployCheckEvents","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Events, most recent first","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DeployEvent"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["deploy-checks"],"summary":"Ingest a deploy event (trigger-token auth, not a customer API key)","description":"Authenticated with `Authorization: Bearer su_dc_...` — the check-specific trigger token minted at check creation or rotation. This route is matched before customer Bearer auth. Idempotent on `idempotency_key`: a repeat of an already-decided event returns 200 with `duplicate: true`. Only `status: \"ready\"` events queue runs; filter mismatches and non-ready statuses are recorded and ignored.","operationId":"ingestDeployEvent","security":[{"deployCheckTriggerToken":[]}],"parameters":[{"$ref":"#/components/parameters/PathId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status","idempotency_key"],"properties":{"status":{"type":"string","enum":["ready","created","building","failed","canceled"]},"idempotency_key":{"type":"string","minLength":1},"environment":{"type":"string"},"target_url":{"type":"string","description":"Required in practice for `event_url` checks with `status: ready` — its absence finalizes the event as `blocked_target` (400)."},"external_deployment_id":{"type":"string"},"git_sha":{"type":"string"},"git_ref":{"type":"string"},"branch":{"type":"string"},"provider":{"type":"string","default":"generic-ci"},"dashboard_url":{"type":"string"},"deployed_at":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Duplicate event (idempotency_key already decided)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployEventDecisionResponse"}}}},"202":{"description":"Event accepted and decided (queued_run, ignored_filter, or ignored_non_ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployEventDecisionResponse"}}}},"400":{"description":"Invalid body, or the event was finalized as blocked (e.g. `blocked_target`). Blocked events return the decision-response shape; validation failures return the standard error shape.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DeployEventDecisionResponse"}]}}}},"401":{"description":"Unknown Deploy Check id or invalid trigger token (`invalid_trigger_token`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/runs":{"post":{"tags":["runs"],"summary":"Create a run (fan-out of 1..10 synthetic-user sessions)","operationId":"createRun","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Requires `url` or `target_url`, plus one of `persona_ids` (preferred), `persona_id`, or inline `persona`.","properties":{"url":{"type":"string","description":"Alias for `target_url`."},"target_url":{"type":"string","description":"https-only; RFC1918 and internal hosts rejected; must match the customer's allowlist (auto-seeded on the very first run)."},"persona_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":10},"persona_id":{"type":"string"},"persona":{"$ref":"#/components/schemas/InlinePersonaInput"},"task":{"type":"string"},"flows":{"type":"array","items":{"type":"string"}},"credential_ids":{"type":"array","items":{"type":"string"},"description":"Credential ids the agent may use. Each credential's target_host must match the run's target host (exact or subdomain)."},"model":{"type":"string","enum":["claude-opus-4-8"],"description":"Optional LLM override. Admitted values come from RUN_MODELS in the deployed build."},"duration_budget":{"type":"string","enum":["short","medium"],"default":"short"},"guided_exploration":{"type":"string","enum":["off","standard","deep"],"default":"off","description":"`deep` forces duration_budget to `medium` when `short` was selected."},"reuse_inbox_session_id":{"type":"string","pattern":"^sess_[a-z0-9]+$","description":"Eval/operator-only; rejected for regular customers."}}}}}},"responses":{"202":{"description":"Run queued","content":{"application/json":{"schema":{"type":"object","required":["run_id","status","sessions","guided_exploration"],"properties":{"run_id":{"type":"string"},"status":{"type":"string","const":"queued"},"sessions":{"type":"array","items":{"type":"object","required":["id","persona_id"],"properties":{"id":{"type":"string"},"persona_id":{"type":"string"}}}},"guided_exploration":{"type":"string","enum":["off","standard","deep"]},"is_preview":{"type":"boolean","const":true,"description":"Present only when a preview-tier model was selected."},"preview_notice":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Billing admission failed (e.g. past_due) or free-run gate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Billing cancelled or target host not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Requested model is not configured or no healthy provider route (`model_unavailable`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["runs"],"summary":"List runs (keyset pagination, most recent first)","description":"The `status` filter is applied per scan window after the SQL limit: `matched < scanned` with a non-null `next_cursor` means deeper pages may hold more matches. Only treat an empty `items` as exhaustion when `scanned === 0`.","operationId":"listRuns","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"status","in":"query","schema":{"type":"string","enum":["queued","running","completed","failed","partial","cancelled"]}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque keyset cursor: `<created_at_ms>|<id>` (a plain unix-ms value is also accepted). Use the returned `next_cursor`."}],"responses":{"200":{"description":"Page of runs","content":{"application/json":{"schema":{"type":"object","required":["items","next_cursor","scanned","matched"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RunListItem"}},"next_cursor":{"type":["string","null"]},"scanned":{"type":"integer"},"matched":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/runs/{run_id}":{"get":{"tags":["runs"],"summary":"Run summary with per-session status and aggregate status","operationId":"getRun","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"description":"Run detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/runs/{run_id}/sessions/{session_id}/transcript":{"get":{"tags":["runs"],"summary":"Session replay transcript","operationId":"getSessionTranscript","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"},{"name":"compressed","in":"query","schema":{"type":"boolean"},"description":"When `true`, returns the stored gzip artifact verbatim (`application/gzip`)."}],"responses":{"200":{"description":"Transcript (decompressed JSON by default; schema_version 1 transcripts are migrated to v2 on read)","content":{"application/json":{"schema":{"type":"object","description":"Replay transcript. Shape defined by the transcript v2 storage schema; not fully specified here.","additionalProperties":true}},"application/gzip":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/runs/{run_id}/sessions/{session_id}/report":{"get":{"tags":["runs"],"summary":"Per-session report (JSON; same as report.json)","operationId":"getSessionReport","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Report JSON. `session.model` is stripped before returning to customers.","content":{"application/json":{"schema":{"type":"object","description":"SyntheticUserRun report artifact (contains runId, session, persona, report). Full shape not specified here.","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/runs/{run_id}/sessions/{session_id}/report.json":{"get":{"tags":["runs"],"summary":"Per-session report (JSON)","operationId":"getSessionReportJson","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Report JSON. `session.model` is stripped before returning to customers.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/runs/{run_id}/sessions/{session_id}/report.md":{"get":{"tags":["runs"],"summary":"Per-session report (Markdown)","operationId":"getSessionReportMd","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Report Markdown","content":{"text/markdown":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/sessions/{session_id}/report.html":{"get":{"tags":["runs"],"summary":"Per-session report (HTML)","operationId":"getSessionReportHtml","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Report HTML","content":{"text/html":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/sessions/{session_id}/screenshots/{filename}":{"get":{"tags":["runs"],"summary":"Stream a session screenshot from storage","operationId":"getSessionScreenshot","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"},{"name":"filename","in":"path","required":true,"schema":{"type":"string","pattern":"^[\\w.\\-]+\\.(png|jpg|jpeg|webp)$"},"description":"Screenshot filename. No path separators or traversal allowed."},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["screenshots","ambient"]},"description":"Artifact class to fetch; defaults to screenshots."}],"responses":{"200":{"description":"Screenshot bytes","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/sessions/{session_id}/events":{"get":{"tags":["runs"],"summary":"Poll structured session events (seq-cursored)","operationId":"listSessionEvents","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"},{"name":"since","in":"query","schema":{"type":"number","minimum":0,"default":0},"description":"Return events with seq strictly greater than this value."},{"name":"limit","in":"query","schema":{"type":"number","minimum":1,"default":100},"description":"Clamped server-side to 1..500."}],"responses":{"200":{"description":"Events page","content":{"application/json":{"schema":{"type":"object","required":["items","next_since","terminal"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SessionEvent"}},"next_since":{"type":"number"},"terminal":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/runs/{run_id}/sessions/{session_id}/stream":{"get":{"tags":["runs"],"summary":"Stream session events as Server-Sent Events","operationId":"streamSessionEvents","parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/SessionId"},{"name":"since","in":"query","schema":{"type":"number","minimum":0,"default":0}}],"responses":{"200":{"description":"SSE stream. Each event: `id` = seq, `event` = event kind, `data` = the SessionEvent JSON.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/runs/{run_id}/map":{"get":{"tags":["runs"],"summary":"Run-derived UX journey map (built on-read from a session transcript)","operationId":"getRunMap","parameters":[{"$ref":"#/components/parameters/RunId"},{"name":"session","in":"query","schema":{"type":"string"},"description":"Session id to build the map from. Defaults to the first completed session, else the first session."}],"responses":{"200":{"description":"Journey map","content":{"application/json":{"schema":{"type":"object","required":["artifact","screenshot_base_path","session_id","sessions"],"properties":{"artifact":{"type":"object","description":"UxMapArtifact (nodes/edges/journeys graph). Full shape not specified here.","additionalProperties":true},"screenshot_base_path":{"type":"string"},"session_id":{"type":"string"},"sessions":{"type":"array","items":{"type":"object","required":["id","persona_id","status"],"properties":{"id":{"type":"string"},"persona_id":{"type":"string"},"status":{"$ref":"#/components/schemas/SessionStatus"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/aggregate-report":{"get":{"tags":["runs"],"summary":"Aggregate report across a multi-persona run (JSON; same as aggregate-report.json)","operationId":"getAggregateReport","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"description":"Aggregate report JSON artifact","content":{"application/json":{"schema":{"type":"object","description":"Aggregate report artifact streamed from storage. Full shape not specified here.","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/aggregate-report.json":{"get":{"tags":["runs"],"summary":"Aggregate report (JSON)","operationId":"getAggregateReportJson","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"description":"Aggregate report JSON artifact","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/aggregate-report.md":{"get":{"tags":["runs"],"summary":"Aggregate report (Markdown)","operationId":"getAggregateReportMd","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"description":"Aggregate report Markdown","content":{"text/markdown":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/runs/{run_id}/aggregate-report.html":{"get":{"tags":["runs"],"summary":"Aggregate report (HTML)","operationId":"getAggregateReportHtml","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"description":"Aggregate report HTML","content":{"text/html":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFoundOrPending"}}}},"/personas":{"post":{"tags":["personas"],"summary":"Create a custom persona","operationId":"createPersona","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonaWriteBody"}}}},"responses":{"201":{"description":"Persona created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Persona"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"tags":["personas"],"summary":"List visible personas (library + caller's own)","operationId":"listPersonas","parameters":[{"name":"source","in":"query","schema":{"type":"string","enum":["library","custom","job_import"]}}],"responses":{"200":{"description":"Personas (library Default first, then by name)","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Persona"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/personas/import-from-text":{"post":{"tags":["personas"],"summary":"Extract a persona draft from pasted job-posting text (nothing persisted yet)","operationId":"importPersonaFromText","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":50,"maxLength":20000,"description":"Job-posting text; trimmed length must be 50–20000 characters."}}}}}},"responses":{"200":{"description":"Editable draft plus a single-use import token (1-hour TTL)","content":{"application/json":{"schema":{"type":"object","required":["import_token","draft"],"properties":{"import_token":{"type":"string"},"draft":{"$ref":"#/components/schemas/ExtractedPersonaFields"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"description":"Extraction produced no usable fields after retries (`extraction_failed`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Extraction provider unavailable (`extraction_unavailable`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/personas/import-from-text/save":{"post":{"tags":["personas"],"summary":"Persist a reviewed import draft as a job_import persona (single-use token)","operationId":"saveImportedPersona","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["import_token","draft"],"properties":{"import_token":{"type":"string"},"draft":{"allOf":[{"$ref":"#/components/schemas/ExtractedPersonaFields"},{"type":"object","properties":{"name":{"type":"string","description":"Persona name; may be supplied here or as the top-level `name`."}}}]},"name":{"type":"string","description":"Required unless `draft.name` is set."}}}}}},"responses":{"201":{"description":"Persona persisted with source=job_import. Note: this response does NOT include `source_text`.","content":{"application/json":{"schema":{"type":"object","required":["id","customer_id","name","role_description","technical_sophistication","patience_level","goals","pain_points","current_tools","synthesized_prompt","source","created_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":["string","null"]},"name":{"type":"string"},"role_description":{"type":"string"},"technical_sophistication":{"$ref":"#/components/schemas/LowMediumHigh"},"patience_level":{"$ref":"#/components/schemas/LowMediumHigh"},"goals":{"type":"array","items":{"type":"string"}},"pain_points":{"type":"array","items":{"type":"string"}},"current_tools":{"type":"array","items":{"type":"string"}},"synthesized_prompt":{"type":"string"},"source":{"type":"string","const":"job_import"},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Token not found, expired, malformed, or owned by another customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Token already used (`token_already_used`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/personas/{id}":{"get":{"tags":["personas"],"summary":"Get a persona (library or own custom)","operationId":"getPersona","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Persona","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Persona"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["personas"],"summary":"Update a custom persona (fields merged with existing, then re-validated; prompt re-synthesized)","operationId":"updatePersona","parameters":[{"$ref":"#/components/parameters/PathId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonaPatchBody"}}}},"responses":{"200":{"description":"Updated persona","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Persona"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Library personas are read-only (`library_read_only`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"tags":["personas"],"summary":"Delete a custom persona (blocked when it has run history)","operationId":"deletePersona","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Library personas are read-only (`library_read_only`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Persona has run history (`persona_in_use`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/credentials":{"post":{"tags":["credentials"],"summary":"Store a credential (envelope-encrypted; password never returned)","operationId":"createCredential","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","target_host","username","password"],"properties":{"label":{"type":"string","minLength":1},"target_host":{"type":"string","description":"Hostname or URL; normalized to a host. Internal/private hosts are rejected."},"username":{"type":"string","minLength":1},"password":{"type":"string","minLength":1}}}}}},"responses":{"201":{"description":"Credential metadata (no secret)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialMetadata"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Credential storage not available (`encryption_not_configured`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["credentials"],"summary":"List credential metadata (never includes secrets)","operationId":"listCredentials","responses":{"200":{"description":"Credentials sorted by label","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CredentialMetadata"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/credentials/{id}":{"get":{"tags":["credentials"],"summary":"Get credential metadata","operationId":"getCredential","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Credential metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialMetadata"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["credentials"],"summary":"Update label/host/username and optionally rotate the password","operationId":"updateCredential","parameters":[{"$ref":"#/components/parameters/PathId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","minLength":1},"target_host":{"type":"string"},"username":{"type":"string","minLength":1},"password":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Updated credential metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialMetadata"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Encryption failure (`encryption_failure`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["credentials"],"summary":"Delete a credential","operationId":"deleteCredential","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks":{"post":{"tags":["webhooks"],"summary":"Create a webhook (HMAC secret returned once)","operationId":"createWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","description":"http(s) URL we can POST to."},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEvent"}},"secret":{"type":"string","minLength":16,"description":"Optional caller-supplied HMAC secret (min 16 chars). Generated server-side (32 random bytes, base64url) when omitted."}}}}}},"responses":{"201":{"description":"Webhook created; `secret` is included only in this response.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookMetadata"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"tags":["webhooks"],"summary":"List webhooks (no secrets)","operationId":"listWebhooks","responses":{"200":{"description":"Webhooks for the calling customer, oldest first","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WebhookMetadata"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/webhooks/{id}":{"get":{"tags":["webhooks"],"summary":"Get webhook metadata","operationId":"getWebhook","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Webhook metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookMetadata"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["webhooks"],"summary":"Update url/events; optionally regenerate the secret","operationId":"updateWebhook","parameters":[{"$ref":"#/components/parameters/PathId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEvent"}},"regenerate_secret":{"type":"boolean","description":"When true, a new secret is generated and included in the response (once)."}}}}}},"responses":{"200":{"description":"Updated webhook. Includes `secret` only when `regenerate_secret` was true.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookMetadata"},{"type":"object","properties":{"secret":{"type":"string"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"tags":["webhooks"],"summary":"Delete a webhook","operationId":"deleteWebhook","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/test":{"post":{"tags":["webhooks"],"summary":"Send a synchronous `webhook.test` event to the webhook URL","operationId":"testWebhook","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Delivery attempt result","content":{"application/json":{"schema":{"type":"object","required":["delivered","status_code","attempt_count","error"],"properties":{"delivered":{"type":"boolean"},"status_code":{"type":["integer","null"]},"attempt_count":{"type":"integer"},"error":{"type":["string","null"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Dispatcher failed (`test_failed`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}/deliveries":{"get":{"tags":["webhooks"],"summary":"List the last 100 delivery attempts","operationId":"listWebhookDeliveries","parameters":[{"$ref":"#/components/parameters/PathId"}],"responses":{"200":{"description":"Delivery attempts","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryAttempt"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Dispatcher failed (`dispatcher_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/billing/portal-url":{"get":{"tags":["billing"],"summary":"Create a Stripe billing-portal session","operationId":"getBillingPortalUrl","responses":{"200":{"description":"Portal session URL","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Stripe customer provisioning or portal session failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Stripe not configured (`stripe_not_configured`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/billing/checkout":{"post":{"tags":["billing"],"summary":"Start a Stripe Checkout session for a subscription tier (7-day trial)","operationId":"createBillingCheckout","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier"],"properties":{"tier":{"type":"string","enum":["starter","hobby","pro"],"description":"Enterprise is custom (no self-serve price) and is rejected with 400."}}}}}},"responses":{"200":{"description":"Checkout session URL","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Stripe customer provisioning or checkout session failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Stripe not configured, or no price configured for the tier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/billing/status":{"get":{"tags":["billing"],"summary":"Current billing status","operationId":"getBillingStatus","responses":{"200":{"description":"Billing status","content":{"application/json":{"schema":{"type":"object","required":["billing_status","stripe_customer_linked"],"properties":{"billing_status":{"type":"string","enum":["active","past_due","cancelled","no_payment_method"]},"stripe_customer_linked":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/usage":{"get":{"tags":["billing"],"summary":"Monthly usage summary with per-run breakdown and subscription meter","operationId":"getUsage","parameters":[{"name":"month","in":"query","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"description":"Calendar month `YYYY-MM` (UTC). Defaults to the current month."}],"responses":{"200":{"description":"Usage summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageMonthlyResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/ux-maps/jobs":{"post":{"tags":["ux-maps"],"summary":"Queue a UX-map discovery job for a connected GitHub repository","operationId":"createUxMapJob","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["repository_id","commit_sha"],"properties":{"repository_id":{"type":"string","minLength":1},"commit_sha":{"type":"string","minLength":1},"trigger":{"type":"string","default":"manual"},"ref_kind":{"type":"string","default":"commit"},"ref_name":{"type":"string","description":"Defaults to `commit_sha`."},"base_commit_sha":{"type":["string","null"]},"changed_files":{"type":"array","items":{"type":"string"},"description":"When provided, drives incremental revalidation classification (may result in a `skip` job)."},"analysis_root":{"type":["string","null"],"description":"Repo-relative path like `apps/web`. No `..`, `.`, empty segments, or backslashes."}}}}}},"responses":{"202":{"description":"Job queued (or skipped by revalidation classification)","content":{"application/json":{"schema":{"type":"object","required":["job_id","status","revalidation_mode","reason"],"properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["queued","skipped"]},"revalidation_mode":{"type":"string"},"reason":{"type":["string","null"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"tags":["ux-maps"],"summary":"List the latest 20 UX-map jobs","operationId":"listUxMapJobs","responses":{"200":{"description":"Jobs, most recent first","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/UxMapJob"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Customer API key: `Authorization: Bearer sk_<env>_...` (env is `live` or `test`). Mint keys via POST /api-keys."},"deployCheckTriggerToken":{"type":"http","scheme":"bearer","description":"Deploy-Check-specific trigger token: `Authorization: Bearer su_dc_<32 chars>`. Only valid for POST /deploy-checks/{id}/events."}},"parameters":{"PathId":{"name":"id","in":"path","required":true,"schema":{"type":"string"}},"RunId":{"name":"run_id","in":"path","required":true,"schema":{"type":"string"}},"SessionId":{"name":"session_id","in":"path","required":true,"schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"Validation failure. `error.code` names the offending field or rule (e.g. `invalid_json`, `invalid_name`, `missing_target_url`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key (`unauthorized`, `invalid_api_key`). Includes a `WWW-Authenticate: Bearer` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found or not visible to the caller (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFoundOrPending":{"description":"Resource not found, not visible to the caller, or the artifact has not been produced yet (`not_found`, `artifact_missing`, `map_not_ready`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Resource is in a state that forbids the operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (10 cost-bearing requests/min for POST /runs and POST /personas/import-from-text; 60 req/min elsewhere).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalError":{"description":"Unexpected server error (`internal_error`). Stack traces are never leaked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"LowMediumHigh":{"type":"string","enum":["low","medium","high"]},"RunStatus":{"type":"string","enum":["queued","running","completed","failed","partial","cancelled"]},"SessionStatus":{"type":"string","enum":["queued","running","completed","failed"]},"WebhookEvent":{"type":"string","enum":["run.completed","run.failed","run.partial","session.completed","session.failed"]},"ApiKeyMetadata":{"type":"object","required":["id","customer_id","prefix","name","scopes","created_at","last_used_at","revoked_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"prefix":{"type":"string","description":"16-char lookup prefix of the key (e.g. `sk_live_XXXXXXXX`)."},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"}}},"AllowedTarget":{"type":"object","required":["id","customer_id","host_pattern","added_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"host_pattern":{"type":"string"},"added_at":{"type":"string","format":"date-time"}}},"DeployCheckWriteBody":{"type":"object","description":"Create/update body for a Deploy Check. On PATCH, omitted fields keep their existing values; the merged config is re-validated, so `name`, `url_strategy`, and `persona_ids` are effectively required on POST.","properties":{"name":{"type":"string","minLength":1},"enabled":{"type":"boolean","default":true},"url_strategy":{"type":"string","enum":["fixed_url","event_url"]},"fixed_target_url":{"type":["string","null"],"description":"Required when `url_strategy` is `fixed_url`; forced to null for `event_url`."},"persona_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":10},"task":{"type":["string","null"]},"flows":{"type":["array","null"],"items":{"type":"string"}},"credential_ids":{"type":["array","null"],"items":{"type":"string"}},"duration_budget":{"type":"string","enum":["short","medium"],"default":"short"},"guided_exploration":{"type":"string","enum":["off","standard","deep"],"default":"off"},"model":{"type":["string","null"],"enum":["claude-opus-4-8",null]},"environment_filter":{"type":["array","null"],"items":{"type":"string"}},"branch_filter":{"type":["array","null"],"items":{"type":"string"}},"ref_filter":{"type":["array","null"],"items":{"type":"string"}}}},"DeployCheck":{"type":"object","required":["id","customer_id","name","enabled","url_strategy","fixed_target_url","persona_ids","task","flows","credential_ids","duration_budget","guided_exploration","model","environment_filter","branch_filter","ref_filter","created_at","updated_at","last_triggered_at","archived_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"url_strategy":{"type":"string","enum":["fixed_url","event_url"]},"fixed_target_url":{"type":["string","null"]},"persona_ids":{"type":"array","items":{"type":"string"}},"task":{"type":["string","null"]},"flows":{"type":["array","null"],"items":{"type":"string"}},"credential_ids":{"type":["array","null"],"items":{"type":"string"}},"duration_budget":{"type":"string","enum":["short","medium"]},"guided_exploration":{"type":"string","enum":["off","standard","deep"]},"model":{"type":["string","null"]},"environment_filter":{"type":["array","null"],"items":{"type":"string"}},"branch_filter":{"type":["array","null"],"items":{"type":"string"}},"ref_filter":{"type":["array","null"],"items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_triggered_at":{"type":["string","null"],"format":"date-time"},"archived_at":{"type":["string","null"],"format":"date-time"}}},"DeployEvent":{"type":"object","required":["id","customer_id","deploy_check_id","idempotency_key","provider","external_deployment_id","status","decision","environment","target_url","selected_target_url","git_sha","git_ref","branch","dashboard_url","run_id","error_code","error_message","payload","received_at","deployed_at","decided_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"deploy_check_id":{"type":"string"},"idempotency_key":{"type":"string"},"provider":{"type":["string","null"]},"external_deployment_id":{"type":["string","null"]},"status":{"type":"string","enum":["ready","created","building","failed","canceled"]},"decision":{"type":["string","null"],"description":"e.g. received, queued_run, ignored_filter, ignored_non_ready, blocked_target, failed_admission."},"environment":{"type":["string","null"]},"target_url":{"type":["string","null"]},"selected_target_url":{"type":["string","null"]},"git_sha":{"type":["string","null"]},"git_ref":{"type":["string","null"]},"branch":{"type":["string","null"]},"dashboard_url":{"type":["string","null"]},"run_id":{"type":["string","null"]},"error_code":{"type":["string","null"]},"error_message":{"type":["string","null"]},"payload":{"type":["object","null"],"additionalProperties":true},"received_at":{"type":"string","format":"date-time"},"deployed_at":{"type":["string","null"],"format":"date-time"},"decided_at":{"type":["string","null"],"format":"date-time"}}},"DeployEventDecisionResponse":{"type":"object","required":["event_id","deploy_check_id","decision","run_id","duplicate"],"properties":{"event_id":{"type":"string"},"deploy_check_id":{"type":"string"},"decision":{"type":["string","null"]},"run_id":{"type":["string","null"]},"duplicate":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}},"description":"Present when the event was finalized with an error code."}}},"InlinePersonaInput":{"type":"object","description":"Inline persona for single-persona runs. Requires either `synthesized_prompt` or all of `role_description` + `technical_sophistication` + `patience_level`.","properties":{"name":{"type":"string"},"synthesized_prompt":{"type":"string"},"role_description":{"type":"string"},"technical_sophistication":{"$ref":"#/components/schemas/LowMediumHigh"},"patience_level":{"$ref":"#/components/schemas/LowMediumHigh"},"goals":{"type":"array","items":{"type":"string"}},"pain_points":{"type":"array","items":{"type":"string"}},"current_tools":{"type":"array","items":{"type":"string"}}}},"RunTriggerMetadata":{"type":"object","required":["kind","deploy_check_id","deploy_check_name","deploy_event_id","provider","environment","branch","git_sha","dashboard_url","deployed_at","received_at"],"properties":{"kind":{"type":"string","const":"deploy_check"},"deploy_check_id":{"type":"string"},"deploy_check_name":{"type":"string"},"deploy_event_id":{"type":"string"},"provider":{"type":["string","null"]},"environment":{"type":["string","null"]},"branch":{"type":["string","null"]},"git_sha":{"type":["string","null"]},"dashboard_url":{"type":["string","null"]},"deployed_at":{"type":["string","null"],"format":"date-time"},"received_at":{"type":"string","format":"date-time"}}},"RunListItem":{"type":"object","required":["id","target_url","status","duration_budget","guided_exploration","created_at","started_at","completed_at","persona_ids","session_count","first_completed_session_id","trigger"],"properties":{"id":{"type":"string"},"target_url":{"type":"string"},"status":{"$ref":"#/components/schemas/RunStatus"},"duration_budget":{"type":"string","enum":["short","medium"]},"guided_exploration":{"type":"string","enum":["off","standard","deep"]},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"persona_ids":{"type":"array","items":{"type":"string"}},"session_count":{"type":"integer"},"first_completed_session_id":{"type":["string","null"]},"trigger":{"oneOf":[{"$ref":"#/components/schemas/RunTriggerMetadata"},{"type":"null"}]}}},"RunDetail":{"type":"object","required":["run_id","target_url","status","created_at","started_at","completed_at","guided_exploration","trigger","aggregate_status","sessions"],"properties":{"run_id":{"type":"string"},"target_url":{"type":"string"},"status":{"$ref":"#/components/schemas/RunStatus"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"guided_exploration":{"type":"string","enum":["off","standard","deep"]},"trigger":{"oneOf":[{"$ref":"#/components/schemas/RunTriggerMetadata"},{"type":"null"}]},"aggregate_status":{"type":"string","enum":["pending","ready","not_applicable","failed"],"description":"`not_applicable` for single-persona runs."},"product_context":{"type":"object","description":"Optional; present when the first session's product context has been captured. Includes a `source` (`fetched` | `cache_hit`) plus the product-context fields.","additionalProperties":true},"sessions":{"type":"array","items":{"$ref":"#/components/schemas/RunDetailSession"}}}},"RunDetailSession":{"type":"object","required":["id","persona_id","status","termination_kind","error_message","termination_reason","primary_failure_class","primary_failure_subsystem","primary_failure_message","primary_failure_seq","primary_failure_at","terminal_failure_class","terminal_failure_subsystem","completed_at"],"properties":{"id":{"type":"string"},"persona_id":{"type":"string"},"status":{"$ref":"#/components/schemas/SessionStatus"},"termination_kind":{"type":["string","null"]},"error_message":{"type":["string","null"]},"termination_reason":{"type":["string","null"]},"primary_failure_class":{"type":["string","null"]},"primary_failure_subsystem":{"type":["string","null"]},"primary_failure_message":{"type":["string","null"]},"primary_failure_seq":{"type":["number","null"]},"primary_failure_at":{"type":["string","null"],"format":"date-time"},"terminal_failure_class":{"type":["string","null"]},"terminal_failure_subsystem":{"type":["string","null"]},"completed_at":{"type":["string","null"],"format":"date-time"}}},"SessionEvent":{"type":"object","required":["id","customer_id","run_id","session_id","seq","kind","turn","payload","created_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"run_id":{"type":"string"},"session_id":{"type":"string"},"seq":{"type":"number"},"kind":{"type":"string","description":"Event kind, e.g. session_queued, session_start, container_start, container_ready, turn_start, turn_end, agent_text, tool_start, tool_end, react, screenshot, annotation, guided_question, guided_resolution, guided_done, auth_* subgoal events, task_checkpoint, checkpoint_reconciled, synthesis_start, synthesis_end, session_end, model_turn_timeout, session.keepalive_tick, error, log."},"turn":{"type":["number","null"]},"payload":{"type":"object","additionalProperties":true},"created_at":{"type":"string","format":"date-time"}}},"PersonaWriteBody":{"type":"object","required":["name","role_description","technical_sophistication","patience_level","goals","pain_points","current_tools"],"properties":{"name":{"type":"string","minLength":1},"role_description":{"type":"string","minLength":1},"technical_sophistication":{"$ref":"#/components/schemas/LowMediumHigh"},"patience_level":{"$ref":"#/components/schemas/LowMediumHigh"},"goals":{"type":"array","items":{"type":"string"}},"pain_points":{"type":"array","items":{"type":"string"}},"current_tools":{"type":"array","items":{"type":"string"}}}},"PersonaPatchBody":{"type":"object","description":"All fields optional; omitted fields keep their existing values. The merged result must still validate as a complete persona.","properties":{"name":{"type":"string","minLength":1},"role_description":{"type":"string","minLength":1},"technical_sophistication":{"$ref":"#/components/schemas/LowMediumHigh"},"patience_level":{"$ref":"#/components/schemas/LowMediumHigh"},"goals":{"type":"array","items":{"type":"string"}},"pain_points":{"type":"array","items":{"type":"string"}},"current_tools":{"type":"array","items":{"type":"string"}}}},"Persona":{"type":"object","required":["id","customer_id","name","role_description","technical_sophistication","patience_level","goals","pain_points","current_tools","synthesized_prompt","source","source_text","created_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":["string","null"],"description":"null for library personas."},"name":{"type":"string"},"role_description":{"type":"string"},"technical_sophistication":{"$ref":"#/components/schemas/LowMediumHigh"},"patience_level":{"$ref":"#/components/schemas/LowMediumHigh"},"goals":{"type":"array","items":{"type":"string"}},"pain_points":{"type":"array","items":{"type":"string"}},"current_tools":{"type":"array","items":{"type":"string"}},"synthesized_prompt":{"type":"string"},"source":{"type":"string","enum":["library","custom","job_import"]},"source_text":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}}},"ExtractedPersonaFields":{"type":"object","required":["role_description","technical_sophistication","patience_level","goals","pain_points","current_tools"],"properties":{"role_description":{"type":"string"},"technical_sophistication":{"$ref":"#/components/schemas/LowMediumHigh"},"patience_level":{"$ref":"#/components/schemas/LowMediumHigh"},"goals":{"type":"array","items":{"type":"string"}},"pain_points":{"type":"array","items":{"type":"string"}},"current_tools":{"type":"array","items":{"type":"string"}}}},"CredentialMetadata":{"type":"object","required":["id","label","target_host","username","created_at"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"target_host":{"type":"string"},"username":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"WebhookMetadata":{"type":"object","required":["id","customer_id","url","events","created_at","last_delivery_at","last_delivery_status"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"created_at":{"type":"string","format":"date-time"},"last_delivery_at":{"type":["string","null"],"format":"date-time"},"last_delivery_status":{"type":["string","null"]}}},"WebhookDeliveryAttempt":{"type":"object","required":["id","webhook_id","event","attempt_count","status","last_status","last_response_code","next_retry_at","dead_at","delivered_at","created_at","updated_at"],"properties":{"id":{"type":"string"},"webhook_id":{"type":"string"},"event":{"type":"string","enum":["run.completed","run.failed","run.partial","session.completed","session.failed","webhook.test"]},"attempt_count":{"type":"integer"},"status":{"type":"string","enum":["pending","delivered","dead"]},"last_status":{"type":["string","null"]},"last_response_code":{"type":["integer","null"]},"next_retry_at":{"type":["string","null"],"format":"date-time"},"dead_at":{"type":["string","null"],"format":"date-time"},"delivered_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"UsageTokenTotals":{"type":"object","required":["input_tokens","output_tokens","cached_input_tokens","total_tokens"],"properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"cached_input_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}}},"UsageRunBreakdown":{"type":"object","required":["id","target_url","sessions_count","cost_cents","tokens"],"properties":{"id":{"type":"string"},"target_url":{"type":["string","null"]},"sessions_count":{"type":"integer"},"cost_cents":{"type":"integer"},"tokens":{"$ref":"#/components/schemas/UsageTokenTotals"}}},"UsageMonthlyResponse":{"type":"object","required":["month","sessions_completed","total_cost_cents","currency","billing_mode","tokens","runs","tier","percent_used","resets_at"],"properties":{"month":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"sessions_completed":{"type":"integer"},"total_cost_cents":{"type":"integer"},"currency":{"type":"string","const":"usd"},"billing_mode":{"type":"string","enum":["free","per_session"]},"tokens":{"$ref":"#/components/schemas/UsageTokenTotals"},"runs":{"type":"array","items":{"$ref":"#/components/schemas/UsageRunBreakdown"}},"tier":{"type":["string","null"],"description":"Subscription tier; null when no tier is assigned."},"percent_used":{"type":"number","description":"Percent of monthly allowance consumed (0-100+; 0 when tier is null)."},"resets_at":{"type":"string","format":"date","description":"First day of the next calendar month (UTC)."}}},"UxMapJob":{"type":"object","required":["id","customer_id","repository_id","trigger","ref_kind","ref_name","commit_sha","base_commit_sha","status","revalidation_mode","reason","sandbox_provider","sandbox_id","artifact_key","error_message","created_at","started_at","completed_at"],"properties":{"id":{"type":"string"},"customer_id":{"type":"string"},"repository_id":{"type":"string"},"trigger":{"type":"string"},"ref_kind":{"type":"string"},"ref_name":{"type":"string"},"commit_sha":{"type":"string"},"base_commit_sha":{"type":["string","null"]},"status":{"type":"string"},"revalidation_mode":{"type":"string"},"reason":{"type":["string","null"]},"sandbox_provider":{"type":["string","null"]},"sandbox_id":{"type":["string","null"]},"artifact_key":{"type":["string","null"]},"error_message":{"type":["string","null"]},"analysis_root":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"}}}}}}