{"openapi":"3.1.0","info":{"title":"Primate Vision API","version":"1.0.0-alpha","description":"Public API v1 for Primate Vision — video understanding for the agentic web. P1 (PRI-430) publishes resource schemas; endpoints ship from P3 onward.","contact":{"email":"support@primateintelligence.ai"}},"servers":[{"url":"https://api.primateintelligence.ai","description":"Production"},{"url":"https://api.dev.primateintelligence.ai","description":"Development"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key (pv_live_… / pv_test_…). Clerk session JWTs are accepted on the same endpoints for first-party clients."}},"schemas":{"VideoStatus":{"type":"string","enum":["awaiting_upload","processing","ready","failed"],"description":"Video lifecycle state (§14.1)."},"VideoUpload":{"type":["object","null"],"properties":{"method":{"type":"string","enum":["PUT"]},"url":{"type":"string","format":"uri"},"headers":{"type":"object","additionalProperties":{"type":"string"}},"expires_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["method","url","headers","expires_at"],"description":"Presigned upload instructions. Present only while awaiting_upload. URL TTL 1h."},"ErrorObject":{"type":["object","null"],"properties":{"code":{"type":"string","example":"video_unreadable"},"message":{"type":"string"}},"required":["code","message"]},"Video":{"type":"object","properties":{"id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"object":{"type":"string","enum":["video"]},"status":{"$ref":"#/components/schemas/VideoStatus"},"filename":{"type":["string","null"],"maxLength":255},"size_bytes":{"type":["integer","null"],"minimum":1,"maximum":2147483648},"duration_s":{"type":["number","null"],"exclusiveMinimum":0},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"fps":{"type":["number","null"]},"content_type":{"type":"string","enum":["video/mp4","video/quicktime"]},"source":{"type":"string","enum":["upload","url"]},"upload":{"$ref":"#/components/schemas/VideoUpload"},"error":{"$ref":"#/components/schemas/ErrorObject"},"metadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"default":{},"description":"Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars."},"expires_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"},"created_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["id","object","status","filename","size_bytes","duration_s","width","height","fps","content_type","source","upload","error","expires_at","created_at"],"description":"A source video (§13.1)."},"AnalysisStatus":{"type":"string","enum":["queued","preparing","analyzing","rendering","completed","failed","canceled"],"description":"Analysis lifecycle state (§14.2). Clients MUST tolerate unknown future non-terminal values."},"Progress":{"type":["object","null"],"properties":{"stage":{"type":"string"},"percent":{"type":"number","minimum":0,"maximum":100}},"required":["stage","percent"],"description":"Non-terminal only. percent is monotonic non-decreasing per analysis."},"Clip":{"type":"object","properties":{"start_s":{"type":"number","minimum":0},"end_s":{"type":"number"},"confidence":{"type":"number","minimum":0,"maximum":1},"terms":{"type":"object","additionalProperties":{"type":"number","minimum":0,"maximum":1}}},"required":["start_s","end_s","confidence","terms"]},"Result":{"type":["object","null"],"properties":{"answer":{"type":"string","enum":["yes","no","indeterminate"]},"confidence":{"type":"number","minimum":0,"maximum":1},"clips":{"type":"array","items":{"$ref":"#/components/schemas/Clip"},"maxItems":500},"term_confidences":{"type":"object","additionalProperties":{"type":"number","minimum":0,"maximum":1}},"query_type":{"type":"string","enum":["object","action","compound","attribute","open_ended"]},"video_duration_s":{"type":"number","exclusiveMinimum":0}},"required":["answer","confidence","clips","term_confidences","query_type","video_duration_s"],"description":"Analysis result — the product (§13.3). Reserved (always absent in v1): masks, bounding_boxes, events."},"Narrative":{"type":["object","null"],"properties":{"status":{"type":"string","enum":["generating","ready","failed"]},"entries":{"type":"array","items":{"type":"object","properties":{"t_s":{"type":"number"},"text":{"type":"string"}},"required":["t_s","text"]}}},"required":["status","entries"]},"Artifacts":{"type":["object","null"],"properties":{"annotated_video_url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["annotated_video_url","expires_at"],"description":"Signed URL TTL 1h; re-fetch GET analysis for a fresh URL."},"AnalysisUsage":{"type":["object","null"],"properties":{"billed_seconds":{"type":"integer","minimum":0},"credit_balance_after":{"type":"integer"}},"required":["billed_seconds","credit_balance_after"],"description":"Terminal only. fps-independent clock seconds (pricing doc)."},"Analysis":{"type":"object","properties":{"id":{"type":"string","pattern":"^an_[0-9A-HJKMNP-TV-Z]{26}$","description":"Analysis resource id (prefixed ULID).","example":"an_01KX12XX3BNM688AWHX7406HCF"},"object":{"type":"string","enum":["analysis"]},"status":{"$ref":"#/components/schemas/AnalysisStatus"},"video_id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"prompt":{"type":["string","null"],"minLength":1,"maxLength":2000},"query":{"type":"object","additionalProperties":{},"description":"CompiledQuery (schema at /v1/schemas/compiled-query.json). Always populated in responses."},"parse_mode":{"type":["string","null"],"enum":["llm","heuristic","client"]},"model":{"type":"string","example":"darwin-1.3"},"options":{"type":"object","properties":{"narrative":{"type":"boolean","default":false}}},"progress":{"$ref":"#/components/schemas/Progress"},"queue_position":{"type":["integer","null"],"minimum":1},"result":{"$ref":"#/components/schemas/Result"},"narrative":{"$ref":"#/components/schemas/Narrative"},"artifacts":{"$ref":"#/components/schemas/Artifacts"},"error":{"$ref":"#/components/schemas/ErrorObject"},"usage":{"$ref":"#/components/schemas/AnalysisUsage"},"metadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"default":{},"description":"Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars."},"created_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"},"started_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"},"completed_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["id","object","status","video_id","prompt","query","parse_mode","model","options","progress","queue_position","result","narrative","artifacts","error","usage","created_at","started_at","completed_at"],"description":"A video analysis (§13.2)."},"StreamStatus":{"type":"string","enum":["queued","ready","live","ended"],"description":"Stream lifecycle state (§4.8): queued → ready → live → ended. Terminal immutable."},"StreamEndReason":{"type":["string","null"],"enum":["completed","canceled","insufficient_credits","error","timeout"]},"StreamSignaling":{"type":["object","null"],"properties":{"url":{"type":"string","description":"Versioned signaling WS. Auth: ?token= accepts a client token (pvct_) or first-party Clerk JWT — NEVER a secret key.","example":"wss://api.primateintelligence.ai/v1/streams/st_01KX…/signal"},"expires_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["url","expires_at"],"description":"Present while the stream is joinable; null once ended."},"StreamLimits":{"type":["object","null"],"properties":{"max_session_s":{"type":"integer","exclusiveMinimum":0,"description":"Plan entitlement (max_session_s) — hard session cutoff."},"warn_at_remaining_s":{"type":"integer","exclusiveMinimum":0,"description":"The `warning {remaining_s}` threshold surfaced mid-stream."}},"required":["max_session_s","warn_at_remaining_s"]},"StreamUsage":{"type":["object","null"],"properties":{"billed_seconds":{"type":"integer","minimum":0,"description":"LIVE-clock seconds (join/negotiation free). fps-independent by construction."},"credit_balance_after":{"type":["integer","null"]}},"required":["billed_seconds","credit_balance_after"],"description":"Terminal only. Same ledger as uploads — one balance."},"Stream":{"type":"object","properties":{"id":{"type":"string","pattern":"^st_[0-9A-HJKMNP-TV-Z]{26}$","description":"Stream resource id (prefixed ULID). P6 §4.8.","example":"st_01KX12XX3BNM688AWHX7406HCF"},"object":{"type":"string","enum":["stream"]},"status":{"$ref":"#/components/schemas/StreamStatus"},"end_reason":{"$ref":"#/components/schemas/StreamEndReason"},"prompt":{"type":["string","null"],"minLength":1,"maxLength":2000},"query":{"type":["object","null"],"additionalProperties":{}},"model":{"type":"string","example":"darwin-1.3"},"queue_position":{"type":["integer","null"],"minimum":1},"estimated_start_s":{"type":["integer","null"],"minimum":0},"signaling":{"$ref":"#/components/schemas/StreamSignaling"},"ice_servers":{"type":["array","null"],"items":{"type":"object","additionalProperties":{}}},"limits":{"$ref":"#/components/schemas/StreamLimits"},"duration_s":{"type":["integer","null"],"minimum":0,"description":"Live-clock duration. Terminal only."},"results_summary":{"type":["object","null"],"additionalProperties":{}},"usage":{"$ref":"#/components/schemas/StreamUsage"},"metadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"default":{},"description":"Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars."},"created_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"},"live_started_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"},"ended_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["id","object","status","end_reason","prompt","query","model","queue_position","estimated_start_s","signaling","ice_servers","limits","duration_s","results_summary","usage","created_at","live_started_at","ended_at"],"description":"A real-time analysis stream (§4.8)."},"WebhookEventType":{"type":"string","enum":["video.ready","video.failed","analysis.completed","analysis.failed","analysis.canceled","stream.ended"],"description":"Closed v1 event vocabulary (§4.5). Payload = the full resource DTO."},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","pattern":"^we_[0-9A-HJKMNP-TV-Z]{26}$","description":"Webhook endpoint id (prefixed ULID).","example":"we_01KX12XX3BNM688AWHX7406HCF"},"object":{"type":"string","enum":["webhook_endpoint"]},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"},"minItems":1},"description":{"type":["string","null"],"maxLength":500},"status":{"type":"string","enum":["enabled","disabled"]},"disabled_reason":{"type":["string","null"],"enum":["auto_failure","user"],"description":"auto_failure = 72h of 100% delivery failure (§16.3). Re-enable via POST …/enable."},"secret":{"type":"string","pattern":"^whsec_","description":"Signing secret — returned EXACTLY ONCE (create / rotate_secret), never readable again."},"last_success_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"},"created_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["id","object","url","events","description","status","disabled_reason","last_success_at","created_at"],"description":"A customer webhook endpoint (§4.5). Standard Webhooks signing."},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","pattern":"^whd_[0-9A-HJKMNP-TV-Z]{26}$","description":"Webhook delivery id (prefixed ULID).","example":"whd_01KX12XX3BNM688AWHX7406HCF"},"object":{"type":"string","enum":["webhook_delivery"]},"event_id":{"type":"string","description":"Stable across retries — consumers dedupe on this.","example":"evt_01KX12XX3BNM688AWHX7406HCF"},"event_type":{"$ref":"#/components/schemas/WebhookEventType"},"status":{"type":"string","enum":["pending","delivering","delivered","failed","dead"]},"attempt_count":{"type":"integer","minimum":0},"last_response_status":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"Null once delivered or dead-lettered.","example":"2026-07-08T14:00:00Z"},"last_attempt_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"},"delivered_at":{"type":["string","null"],"format":"date-time","example":"2026-07-08T14:00:00Z"},"created_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["id","object","event_id","event_type","status","attempt_count","last_response_status","last_error","next_attempt_at","last_attempt_at","delivered_at","created_at"],"description":"One event→endpoint delivery record (§16.3). Dead-lettered rows visible 30 days."},"Meter":{"type":"object","properties":{"meter":{"type":"string","example":"credit_seconds"},"unit":{"type":"string","example":"seconds"},"balance":{"type":"number","description":"Balance-shaped meters (e.g. credit_seconds)."},"limit":{"type":["number","null"],"description":"null = unlimited/PAYG."},"used":{"type":"number","description":"Period-shaped meters."},"resets_at":{"type":"string","format":"date-time","description":"Omitted for non-period meters.","example":"2026-07-08T14:00:00Z"}},"required":["meter","unit"],"description":"One named usage meter (§4.6). The list is data-driven — pricing changes add/change rows, never the schema."},"ErrorEnvelope":{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorObject"},{"type":"object","properties":{"status":{"type":"integer"}},"required":["status"]}]}},"required":["error"]},"CreateVideoRequest":{"type":"object","properties":{"filename":{"type":"string","maxLength":255},"content_type":{"type":"string","enum":["video/mp4","video/quicktime"],"description":"Required in presigned mode."},"size_bytes":{"type":"integer","minimum":1,"maximum":2147483648},"url":{"type":"string","maxLength":2048,"format":"uri","description":"URL-ingest mode: https-only, SSRF-guarded (§18.2). Mutually exclusive with filename/size_bytes."},"metadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"description":"Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars."}},"description":"Exactly one mode: presigned ({filename?, content_type, size_bytes?}) or ingest ({url}). Unknown fields are rejected."},"VideoList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Video"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]},"DeletedVideo":{"type":"object","properties":{"id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"object":{"type":"string","enum":["video"]},"deleted":{"type":"boolean","enum":[true]}},"required":["id","object","deleted"]},"CreateAnalysisRequest":{"type":"object","properties":{"video_id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Free text — compiled server-side. Exactly one of prompt/query."},"query":{"type":"object","additionalProperties":{},"description":"Structured CompiledQuery JSON (schema at /v1/schemas/compiled-query.json)."},"model":{"type":"string","description":"Defaults to latest; response always echoes the concrete version.","example":"darwin-1.3"},"options":{"type":"object","properties":{"narrative":{"type":"boolean","default":false}}},"metadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"description":"Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars."},"webhook":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}}},"required":["url","events"],"description":"Per-request webhook override (delivery ships P7). Write-only — never echoed."}},"required":["video_id"]},"AnalysisList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Analysis"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]},"Model":{"type":"object","properties":{"id":{"type":"string","example":"darwin-1.3"},"object":{"type":"string","enum":["model"]},"status":{"type":"string","enum":["stable","preview","deprecated"]},"default":{"type":"boolean"},"capabilities":{"type":"object","properties":{"prompt":{"type":"boolean"},"structured_query":{"type":"boolean"},"narrative":{"type":"boolean"},"streaming":{"type":"boolean"}},"required":["prompt","structured_query","narrative","streaming"]},"sunset_at":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","object","status","default","capabilities","sunset_at","created_at"]},"ErrorRegistryEntry":{"type":"object","properties":{"code":{"type":"string"},"kind":{"type":"string","enum":["http","resource"]},"status":{"type":["integer","null"]},"retryable":{"type":"boolean"},"idem":{"type":"boolean"},"message":{"type":"string"},"docs_url":{"type":"string","format":"uri"}},"required":["code","kind","status","retryable","idem","message","docs_url"]},"ClientToken":{"type":"object","properties":{"object":{"type":"string","enum":["client_token"]},"id":{"type":"string"},"token":{"type":"string","description":"256-bit CSPRNG bearer, SHA-256 at rest — shown exactly once. Browser-safe: accepted on product-plane endpoints (CORS *).","example":"pvct_…"},"scopes":{"type":"array","items":{"type":"string"}},"expires_at":{"type":"string"},"video_id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"stream_id":{"type":"string"}},"required":["object","id","token","scopes","expires_at"]},"CreateClientTokenRequest":{"type":"object","properties":{"scopes":{"type":"array","items":{"type":"string","enum":["videos:write","analyses:read","analyses:write","streams:create","streams:signal"]},"minItems":1,"description":"Non-empty subset of the §4.9 scope set. Must also be a subset of the minting key’s scopes."},"ttl_s":{"type":"integer","minimum":60,"maximum":900,"default":900,"description":"Token lifetime in seconds (60–900, default 900). Tokens are never refreshable — mint a new one."},"video_id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Optional resource binding: narrows the token to this one video. Mutually exclusive with stream_id.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"stream_id":{"type":"string","description":"Optional resource binding to one stream (WS acceptance ships P6)."}},"required":["scopes"]},"CreateStreamRequest":{"type":"object","properties":{"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Free text — compiled server-side. Exactly one of prompt/query."},"query":{"type":"object","additionalProperties":{},"description":"Structured CompiledQuery JSON."},"model":{"type":"string","example":"darwin-1.3"},"metadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"description":"Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars."},"webhook":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}}},"required":["url","events"],"description":"Per-request webhook override (delivery ships P7). Write-only."}},"description":"Requires positive credit balance. One queued|ready|live stream per account by default (409 stream_already_active otherwise)."},"StreamList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Stream"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]},"SandboxKey":{"type":"object","properties":{"object":{"type":"string","enum":["sandbox_key"]},"api_key":{"type":"string","description":"Shown exactly once — store it now. Fixture-only test mode: no GPU, no credits.","example":"pv_test_…"},"mode":{"type":"string","enum":["test"]},"expires_at":{"type":"string","description":"Keys expire 7 days after issuance."},"fixture_video_id":{"type":["string","null"],"description":"Pre-seeded ready video for immediate POST /v1/analyses."},"fixture_prompt":{"type":"string"},"expected_answer":{"type":"string","description":"Deterministic canned answer for the fixture prompt — assert this in CI."},"docs_url":{"type":"string"},"note":{"type":"string"}},"required":["object","api_key","mode","expires_at","fixture_video_id","fixture_prompt","expected_answer","docs_url","note"]},"CreateWebhookEndpointRequest":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri","description":"HTTPS delivery URL."},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"},"minItems":1,"description":"Subscribed event types — a non-empty subset of the closed v1 vocabulary."},"description":{"type":"string","maxLength":500}},"required":["url","events"]},"WebhookEndpointList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]},"WebhookDeliveryList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]},"UsageResponse":{"type":"object","properties":{"meters":{"type":"array","items":{"$ref":"#/components/schemas/Meter"},"description":"Data-driven meter list (§4.6 invariant #1). At launch: credit_seconds (balance) + seconds_processed.period (used/resets_at). Pricing changes add/change rows — never the schema. quota_exceeded errors carry details.meter naming which meter tripped."}},"required":["meters"],"description":"Usage meters + legacy dashboard fields (the flat fields are deprecated in favor of meters[]; they remain during the web-client migration)."}},"parameters":{}},"paths":{"/v1/videos":{"post":{"summary":"Create a video (presigned upload or URL ingest)","tags":["videos"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"§16.1: first request wins; 24h replay window; same key + different body → 409 idempotency_key_reused."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVideoRequest"}}}},"responses":{"201":{"description":"Video created. Presigned mode → status awaiting_upload with upload instructions; URL mode → status processing (async fetch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Video"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"summary":"List videos (cursor pagination, created_at desc)","tags":["videos"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor — mutually exclusive with ending_before."},{"name":"ending_before","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"List envelope (§13.4).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoList"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/videos/{id}/complete":{"post":{"summary":"Signal that the presigned S3 PUT finished","tags":["videos"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Size verified (±5% of declaration); video is ready.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Video"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/videos/{id}":{"get":{"summary":"Fetch a video","tags":["videos"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The video.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Video"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"summary":"Delete a video (409 while analyses are non-terminal on it)","tags":["videos"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedVideo"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/analyses":{"post":{"summary":"Create an analysis (video × prompt × model)","description":"Async by default: 202 + poll. `Prefer: wait=<s>` (≤120) blocks and returns the terminal resource when it finishes in time. Test-mode keys (pv_test_) return deterministic canned results with no GPU time and no credit burn.","tags":["analyses"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"§16.1: first request wins; 24h replay window; same key + different body → 409 idempotency_key_reused."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"wait=<seconds> (cap 120)"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnalysisRequest"}}}},"responses":{"200":{"description":"Terminal resource (Prefer: wait satisfied, or test mode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Analysis"}}}},"202":{"description":"Queued. Response echoes the compiled query and honest queue_position.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Analysis"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"insufficient_credits (details.meter = credit_seconds)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"inference_unavailable / capacity_exhausted (with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"summary":"List analyses (filters: status, video_id, model, created_*)","tags":["analyses"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor — mutually exclusive with ending_before."},{"name":"ending_before","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"video_id","in":"query","schema":{"type":"string"}},{"name":"model","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"List envelope (§13.4).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalysisList"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/analyses/{id}":{"get":{"summary":"Fetch an analysis (live progress while running)","tags":["analyses"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The analysis. progress uses the canonical stage set queued→preparing→analyzing→rendering.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Analysis"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/analyses/{id}/cancel":{"post":{"summary":"Cancel an analysis (best-effort once analyzing)","tags":["analyses"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Canceled (or best-effort race documented in §14.2).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Analysis"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/models":{"get":{"summary":"List available models","tags":["meta"],"responses":{"200":{"description":"Model catalog. Requests may say model:\"latest\"; responses always name the concrete version.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Model"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]}}}}}}},"/v1/errors":{"get":{"summary":"Machine-readable error-code registry (§15)","tags":["meta"],"responses":{"200":{"description":"Closed registry — codes are append-only; SDK retry policy generates from retryable/idem flags.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/ErrorRegistryEntry"}},"has_more":{"type":"boolean"}},"required":["object","data","has_more"]}}}}}}},"/v1/client_tokens":{"post":{"summary":"Mint an ephemeral browser-safe client token (pvct_)","tags":["client_tokens"],"description":"Server-side token exchange (§4.9): authenticate with a secret key (or first-party session), receive a short-lived scoped pvct_ bearer for the browser. The browser then calls Primate directly — your secret key never leaves your server. Tokens are revoked implicitly when the minting key is revoked (≤5s). Rate class: 120/min per key. Client tokens can never mint tokens.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientTokenRequest"}}}},"responses":{"201":{"description":"Token minted. The raw token appears only in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientToken"}}}},"400":{"description":"validation_failed — scopes/ttl_s/binding violations, all listed at once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"insufficient_scope — requested scopes exceed the minting key’s, or the caller is itself a client token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found — video_id binding does not resolve to an owned video.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded — client_tokens.create is 120/min per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams":{"post":{"summary":"Create a real-time analysis stream","description":"Returns the signaling WS URL, ephemeral ICE servers, and session limits. Signaling protocol v1: join → ready|queued(position) → offer/answer/ice → live; mid-stream update_prompt, metering (5s ticks), warning, end {reason}. WS auth: ?token= accepts a client token (pvct_) or first-party session JWT — never a secret key. Billed per second of live clock time (join/negotiation free) from the same credit ledger as uploads.","tags":["streams"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"§16.1: first request wins; 24h replay window; same key + different body → 409 idempotency_key_reused."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStreamRequest"}}}},"responses":{"201":{"description":"Stream created (queued or ready). Response carries signaling.url + ice_servers + limits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stream"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"insufficient_credits — positive balance required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"stream_already_active — one queued|ready|live stream per account (also the un-keyed-retry guard, §31-F2) / idempotency_key_reused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"capacity_exhausted (with Retry-After) — slot queue beyond the hard cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"summary":"List streams (cursor pagination, created_at desc)","tags":["streams"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor — mutually exclusive with ending_before."},{"name":"ending_before","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"status","in":"query","schema":{"type":"string","enum":["queued","ready","live","ended"]}}],"responses":{"200":{"description":"List envelope (§13.4).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamList"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{id}":{"get":{"summary":"Fetch a stream (usage + results summary once ended)","tags":["streams"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stream. Terminal streams carry usage {billed_seconds, credit_balance_after} + duration_s + results_summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stream"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{id}/end":{"post":{"summary":"End a stream (idempotent)","description":"Graceful end: settles the credit reservation for the billed live-clock seconds (releases it entirely when the stream never went live). Ending an already-ended stream returns the terminal resource unchanged.","tags":["streams"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The ended stream with final usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stream"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/sandbox":{"post":{"summary":"Provision an instant sandbox (pv_test_) key — no auth, no card","tags":["provisioning"],"description":"Anonymous zero-touch provisioning (A1). Issues a fixture-only test-mode key: analyses return deterministic canned results, never touch the GPU, and burn no credits. IP-limited; 7-day expiry. Live keys require signup (billing gate, not an integration gate).","responses":{"201":{"description":"Sandbox key issued. The raw key appears only in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxKey"}}}},"429":{"description":"sandbox_limit_exceeded — IP daily cap reached. Retry-After set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"db_unavailable — transient; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhook_endpoints":{"post":{"summary":"Create a webhook endpoint (returns the one-time whsec_ secret)","description":"Standard Webhooks signing (webhook-id / webhook-timestamp / webhook-signature). The secret appears ONLY in this response. Delivery contract: 10s timeout, retries 1m/5m/30m/2h/8h/24h with jitter, at-least-once, no ordering — dedupe on the evt_ id. Endpoints failing 100% for 72h are auto-disabled with an email notice. At most 10 endpoints per account.","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"§16.1: first request wins; 24h replay window; same key + different body → 409 idempotency_key_reused."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointRequest"}}}},"responses":{"201":{"description":"Endpoint created. `secret` is returned exactly once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"validation_failed / webhook_endpoint_limit (10 per account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"summary":"List webhook endpoints (secrets never included)","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhook_endpoints/{id}":{"get":{"summary":"Fetch a webhook endpoint (secret never included)","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook endpoint id (we_…)."}],"responses":{"200":{"description":"The endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"summary":"Delete a webhook endpoint (effective immediately, §17)","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook endpoint id (we_…)."}],"responses":{"200":{"description":"{id, object, deleted: true}."},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhook_endpoints/{id}/rotate_secret":{"post":{"summary":"Rotate the signing secret (24h dual-secret overlap)","description":"Returns the NEW secret exactly once. The previous secret keeps signing deliveries for 24h so receivers can migrate without dropped verifications — the signature header carries one signature per active secret.","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook endpoint id (we_…)."}],"responses":{"200":{"description":"Endpoint with the new one-time `secret`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhook_endpoints/{id}/enable":{"post":{"summary":"Re-enable a disabled endpoint (clears auto-disable state)","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook endpoint id (we_…)."}],"responses":{"200":{"description":"The re-enabled endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhook_endpoints/{id}/deliveries":{"get":{"summary":"List recent deliveries (last 100, with response codes)","description":"Dead-lettered deliveries stay visible for 30 days. Deliveries carry the evt_ id (stable across retries), attempt count, last response status/error, and next retry time.","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook endpoint id (we_…)."}],"responses":{"200":{"description":"List envelope (newest first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhook_endpoints/{id}/deliveries/{delivery_id}/redeliver":{"post":{"summary":"Redeliver a delivery now (works on failed/dead rows)","tags":["webhook_endpoints"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook endpoint id (we_…)."},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string"},"description":"Delivery id (whd_…)."}],"responses":{"202":{"description":"Requeued for immediate delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/usage":{"get":{"summary":"Usage meters (credit balance, period consumption)","tags":["usage"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Meters + legacy fields. Same numbers as the dashboard (shared service, reconciles with the credit ledger to the second).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"400":{"description":"validation_failed / url_forbidden / prompt errors (§15)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"invalid_api_key / key_revoked / key_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"resource_not_found (account-scoped; no existence oracle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"resource_conflict / idempotency_key_reused / idempotency_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"webhooks":{}}