{"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://dev-api.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."},"VideoMedia":{"type":["object","null"],"properties":{"url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time","example":"2026-07-08T14:00:00Z"}},"required":["url","expires_at"],"description":"Signed 1h playback URL for the source video (sign-on-read — re-fetch for a fresh URL). Null while not ready or when no stored source object exists."},"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"]},"livemode":{"type":"boolean","description":"true when the request used a live key; false for test keys and sandbox. Stripe-style."},"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"},"media":{"$ref":"#/components/schemas/VideoMedia"},"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","livemode","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","null"],"exclusiveMinimum":0},"detected_count":{"type":["integer","null"],"minimum":0},"indeterminate_reason":{"type":["string","null"],"enum":["low_confidence","nothing_detected","unsupported_query_form","duration_mismatch","unsupported_action_term"]},"unsupported_action":{"type":"object","properties":{"reason":{"type":"string","description":"Why the request could not be scored.","example":"unsupported_query_form"},"docs_url":{"type":"string","description":"The supported-actions capability list — what the model can and cannot detect.","example":"https://primateintelligence.ai/docs/supported-actions"}},"required":["reason","docs_url"],"description":"PRI-516: present only when the prompt requested something the model cannot detect/score. Absent otherwise — never null."}},"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."},"MeteringRule":{"type":"string","enum":["per_second","per_frame"],"description":"PRI-552: settlement rule pinned at admission. Resources created before the cutover report per_second."},"AnalysisUsage":{"type":["object","null"],"properties":{"billed_seconds":{"type":"integer","minimum":0},"credit_balance_after":{"type":["integer","null"]},"metering_rule":{"allOf":[{"$ref":"#/components/schemas/MeteringRule"},{"description":"PRI-552: the rule this analysis settled under. Mirrors the top-level metering_rule for a self-contained terminal summary."}]},"frames":{"type":["integer","null"],"minimum":0,"description":"PRI-552: processed-frame count reported by the inference callback (cost telemetry). Present for both metering rules when available; null when the callback did not carry it."}},"required":["billed_seconds","credit_balance_after"],"description":"Terminal only. credit_balance_after is the balance immediately after THIS analysis settled — an immutable point-in-time snapshot; it does not change as later analyses run. Null when the account row is unavailable (e.g. free-grant exhausted before settlement). billed_seconds is the settled ledger amount, derived from the processed-frame count for per_frame rows (PRI-552); historical per_second rows carry their original clock-derived amount."},"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"]},"livemode":{"type":"boolean","description":"true when the request used a live key; false for test keys and sandbox. Stripe-style."},"origin":{"type":"string","enum":["api","console","system"],"description":"`api` — submitted via the public API; `console` — uploaded via the dashboard; `system` — generated internally (e.g. auto-probe, fixture seeding, run_prompt). System analyses are never billed. Agents should normally only process api/console-origin analyses. (Renamed 2026-07-27: former value `user` is now `api`.)"},"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-preview-1.3B"},"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"},"metering_rule":{"$ref":"#/components/schemas/MeteringRule"},"usage":{"$ref":"#/components/schemas/AnalysisUsage"},"rerun_eligible":{"type":"boolean","description":"PRI-493: present on failed analyses — true when this analysis qualifies for ONE free re-run (it failed during a platform incident, or was flagged by ops). POST /v1/analyses/{id}/rerun creates a fresh analysis at no charge. Absent on non-failed analyses."},"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","livemode","origin","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","ice_failed","media_timeout"],"description":"Why the stream ended. completed — normal end after live. canceled — ended before going live (client action). insufficient_credits — balance exhausted. error — server-side failure. timeout — max_session_s cutoff. ice_failed — WebRTC ICE never connected (see failure_diagnostic). media_timeout — connected but no media/results within the first-frame window. A stream that never went live is never `completed`."},"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"]},"StreamRecordingState":{"type":["object","null"],"properties":{"status":{"type":"string","enum":["recording","available","failed","none"]}},"required":["status"],"description":"PRI-496: recording state — null unless the stream opted in with recording: true at create."},"StreamUsage":{"type":["object","null"],"properties":{"billed_seconds":{"type":"integer","minimum":0,"description":"Settled ledger amount. per_frame streams (PRI-552, the default): derived from the processed-frame count. Historical per_second streams: LIVE-clock derived (join/negotiation free)."},"credit_balance_after":{"type":["integer","null"]},"metering_rule":{"allOf":[{"$ref":"#/components/schemas/MeteringRule"},{"description":"PRI-552: the rule this stream settled under. Mirrors the top-level metering_rule for a self-contained terminal summary."}]},"frames":{"type":["integer","null"],"minimum":0,"description":"PRI-552: processed-frame count (results_summary.result_frames). Present for both metering rules — frame counting predates the billing cutover; null when unknown."}},"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"},"metering_rule":{"allOf":[{"$ref":"#/components/schemas/MeteringRule"},{"description":"PRI-552: settlement rule pinned at create time. Streams created before the cutover report per_second."}]},"end_reason":{"$ref":"#/components/schemas/StreamEndReason"},"prompt":{"type":["string","null"],"minLength":1,"maxLength":2000},"query":{"type":["object","null"],"additionalProperties":{}},"model":{"type":"string","example":"Darwin-preview-1.3B"},"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":{},"description":"Terminal summary: {result_frames, frames (deprecated alias of result_frames — removed ~2026-08-28), last_detections}. result_frames counts result EVENTS emitted (results are sampled, not per-source-frame). Detection rows use the same enums/casing as the file-API result contract (answer: yes|no|indeterminate, lowercase; prompt echoed exactly as submitted)."},"failure_diagnostic":{"type":["object","null"],"additionalProperties":{},"description":"Present when end_reason is ice_failed (and some media_timeout cases): ICE candidate summary from the server side — {local_candidates: string[], hint}. Use it to distinguish \"server advertised only private/host candidates\" from \"client could not reach relay\"."},"recording":{"$ref":"#/components/schemas/StreamRecordingState"},"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","failure_diagnostic","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."},"CreditPricing":{"type":"object","properties":{"price_per_second_cents":{"type":"number","description":"Legacy ledger-unit rate, in cents — retained for backward compatibility. New integrations should price with frames: cost = usage.frames × price_per_frame_cents.","example":1},"price_per_frame_cents":{"type":"number","description":"Price per processed frame, in cents (PRI-552) — the billed metering unit. Cost = frames processed × this value. Read the rate from this endpoint, never hardcode it.","example":0.00015},"signup_grant_frames":{"type":"integer","description":"Free signup grant as a frame budget (PRI-552). 108,000 frames = 1 hour of video at 30fps; the same budget covers 6 hours at 5fps or 30 hours at 1fps.","example":108000},"signup_grant_seconds":{"type":"integer","description":"Legacy ledger-unit value of the signup grant — retained for backward compatibility; prefer signup_grant_frames.","example":6000},"card_grant_seconds":{"type":"integer","description":"Legacy ledger-unit value of the card-added bonus grant — retained for backward compatibility.","example":0},"auto_refill_threshold_seconds":{"type":"integer","description":"Balance below which auto-refill (if enabled) triggers."},"allowed_purchase_cents":{"type":"array","items":{"type":"integer"},"description":"Preset purchase amounts, in cents."},"custom_purchase_enabled":{"type":"boolean"},"min_purchase_cents":{"type":"integer"},"batch_discount_pct":{"type":"integer","description":"Batch analyses (POST /v1/analyses/batch): percent discount applied to each prompt after the first. The first prompt is always full price.","example":50},"batch_min_prompts":{"type":"integer","description":"Minimum prompts accepted per batch request.","example":2},"batch_max_prompts":{"type":"integer","description":"Maximum prompts accepted per batch request.","example":10},"frames_per_purchased_second":{"type":"integer","description":"PRI-552 (display-only): how many processed frames one purchased ledger unit covers at current rates — round(price_per_second_cents / price_per_frame_cents). Additive; the ledger and charge math are unchanged.","example":6667},"purchase_preset_frame_equivalents":{"type":"array","items":{"type":"object","properties":{"amount_cents":{"type":"integer","example":2500},"seconds":{"type":"integer","description":"Ledger-seconds this preset credits (unchanged purchase math).","example":2500},"frames":{"type":"integer","description":"Frame-equivalent processing this preset buys at current rates. Display only.","example":16666667}},"required":["amount_cents","seconds","frames"]},"description":"PRI-552 (display-only): frame-equivalent value of each purchase preset, parallel to allowed_purchase_cents."},"pricing_tier":{"type":"string","description":"PRI-554: machine-readable pricing-policy status — \"preview\" while preview pricing is in effect; \"standard\" once rates stabilize. Integrations should treat a transition away from \"preview\" as the signal that rates have stabilized.","example":"preview"},"pricing_tier_note":{"type":"string","description":"PRI-554: plain-language pricing policy. Current rates are preview pricing: early-access pricing for a preview model, charged because the metered per-frame cost, streaming ingestion, and low latency deliver value today. The rate may change — up or down — as the model matures, customer demand develops, and processing costs change. Rate changes apply to new processing only and are announced in the changelog before taking effect. Read the live rate from GET /v1/credit-pricing — never hardcode it.","example":"Current rates are preview pricing: early-access pricing for a preview model, charged because the metered per-frame cost, streaming ingestion, and low latency deliver value today. The rate may change — up or down — as the model matures, customer demand develops, and processing costs change. Rate changes apply to new processing only and are announced in the changelog before taking effect. Read the live rate from GET /v1/credit-pricing — never hardcode it."}},"required":["price_per_second_cents","price_per_frame_cents","signup_grant_frames","signup_grant_seconds","card_grant_seconds","auto_refill_threshold_seconds","allowed_purchase_cents","custom_purchase_enabled","min_purchase_cents","batch_discount_pct","batch_min_prompts","batch_max_prompts","frames_per_purchased_second","purchase_preset_frame_equivalents","pricing_tier","pricing_tier_note"],"description":"Current pricing configuration. Data-driven — a price change is a config update, not a schema change. Batch discount included (PRI-508) — read it from here instead of hardcoding. Current rates are preview pricing: early-access pricing for a preview model, charged because the metered per-frame cost, streaming ingestion, and low latency deliver value today. The rate may change — up or down — as the model matures, customer demand develops, and processing costs change. Rate changes apply to new processing only and are announced in the changelog before taking effect. Read the live rate from GET /v1/credit-pricing — never hardcode it."},"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"]},"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"]},"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"]},"AnalysisPreview":{"type":"object","properties":{"object":{"type":"string","enum":["analysis_preview"]},"query":{"type":"object","additionalProperties":{},"description":"The compiled query the analysis would run."},"parse_mode":{"type":"string","enum":["llm","heuristic","client"],"description":"How the prompt was compiled. \"client\" means you supplied query directly."},"assessable":{"type":"boolean","description":"False for open_ended queries — the model cannot score them, so detections will be empty."},"video_duration_s":{"type":["number","null"],"description":"Null when duration is not yet known (probe pending)."},"estimated_seconds":{"type":["integer","null"],"description":"Billable seconds this analysis would consume. Null when duration is unknown."},"estimated_cost_usd":{"type":["number","null"],"description":"Estimated cost at current pricing. Null when duration is unknown."}},"required":["object","query","parse_mode","assessable","video_duration_s","estimated_seconds","estimated_cost_usd"]},"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 (currently Darwin-preview-1.3B, status preview); deprecated aliases (e.g. darwin-1.3) are accepted case-insensitively; response always echoes the canonical id.","example":"Darwin-preview-1.3B"},"options":{"type":"object","properties":{"narrative":{"type":"boolean","default":false}},"description":"narrative: true — generate a narrative (timestamped event sentences) for the analysis. Populates the narrative object on the completed analysis; generation runs asynchronously after completion (poll the GET — status generating → ready|failed). Included in the analysis price."},"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."},"validate_only":{"type":"boolean","description":"Dry run (PRI-480). Compiles the prompt, reports whether the query is assessable, and estimates duration/cost — WITHOUT creating an analysis, touching the GPU, or burning credits. Returns an AnalysisPreview (object: \"analysis_preview\") with 200, not an Analysis. Use it to check a prompt before paying for it.","example":true}},"required":["video_id"]},"DemoVideoResult":{"type":"object","properties":{"answer":{"type":["string","null"]},"confidence":{"type":["number","null"]},"query_type":{"type":["string","null"]},"video_url":{"type":["string","null"],"description":"Playable annotated result video (CDN)."},"computed_at":{"type":"string"}},"required":["answer","confidence","query_type","video_url","computed_at"],"description":"Precomputed result for one prompt. Documented subset — additional fields (result_json, clips) exist but are undocumented and may change."},"DemoVideo":{"type":"object","properties":{"id":{"type":"string"},"display_name":{"type":"string"},"duration_seconds":{"type":["number","null"]},"thumbnail_url":{"type":["string","null"]},"results_by_prompt":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/DemoVideoResult"},"description":"Map of prompt text → precomputed result."}},"required":["id","display_name","duration_seconds","thumbnail_url","results_by_prompt"]},"DemoVideoList":{"type":"object","properties":{"videos":{"type":"array","items":{"$ref":"#/components/schemas/DemoVideo"}}},"required":["videos"]},"AnalysisBatchPreviewPrompt":{"type":"object","properties":{"index":{"type":"integer","description":"Zero-based prompt index."},"query":{"type":"object","additionalProperties":{},"description":"Compiled query structure."},"parse_mode":{"type":"string","enum":["heuristic","llm","client"]},"assessable":{"type":"boolean","description":"True when the query type can produce a yes/no/count answer."},"estimated_seconds":{"type":["integer","null"],"description":"Estimated ledger credits for this prompt. Null when video has no known duration."},"estimated_cost_usd":{"type":["number","null"],"description":"Estimated cost in USD. Null when video has no known duration."},"discount_pct":{"anyOf":[{"type":"number","enum":[0]},{"type":"number","enum":[50]}],"description":"0 for the first (full-price) prompt; the configured batch discount (GET /v1/credit-pricing batch_discount_pct) for additional prompts."}},"required":["index","query","parse_mode","assessable","estimated_seconds","estimated_cost_usd","discount_pct"]},"AnalysisBatchPricing":{"type":"object","properties":{"full_price_prompts":{"type":"integer","example":1},"discounted_prompts":{"type":"integer","example":1},"discount_pct":{"type":"integer","description":"Config-driven batch discount percent — the same value GET /v1/credit-pricing serves as batch_discount_pct.","example":50},"estimated_total_seconds":{"type":["integer","null"],"description":"Estimated total ledger credits for all prompts combined (full + discounted). Null when the video has no known duration.","example":15},"estimated_total_cost_usd":{"type":["number","null"],"description":"Estimated total cost in USD. Null when duration unknown.","example":0.15}},"required":["full_price_prompts","discounted_prompts","discount_pct","estimated_total_seconds","estimated_total_cost_usd"]},"AnalysisBatchPreview":{"type":"object","properties":{"object":{"type":"string","enum":["analysis_batch_preview"]},"video_id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"prompts":{"type":"array","items":{"$ref":"#/components/schemas/AnalysisBatchPreviewPrompt"}},"pricing":{"$ref":"#/components/schemas/AnalysisBatchPricing"}},"required":["object","video_id","prompts","pricing"]},"AnalysisBatch":{"type":"object","properties":{"object":{"type":"string","enum":["analysis_batch"]},"id":{"type":"string","description":"Stable batch identifier."},"video_id":{"type":"string","pattern":"^video_[0-9A-HJKMNP-TV-Z]{26}$","description":"Video resource id (prefixed ULID). Immutable.","example":"video_01KX12XX3BNM688AWHX7406HCF"},"analyses":{"type":"array","items":{"$ref":"#/components/schemas/Analysis"}},"pricing":{"$ref":"#/components/schemas/AnalysisBatchPricing"}},"required":["object","id","video_id","analyses","pricing"]},"CreateAnalysisBatchRequest":{"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"},"prompts":{"type":"array","items":{"type":"string","minLength":1,"maxLength":2000},"minItems":2,"maxItems":10,"description":"2–10 prompts to run against the video. First prompt is full price; each additional is billed at 50% (default — the live discount is served by GET /v1/credit-pricing as batch_discount_pct)."},"model":{"type":"string","example":"Darwin-preview-1.3B"},"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."},"validate_only":{"type":"boolean","default":false,"description":"Dry-run mode. When true, parses all prompts and returns an analysis_batch_preview (HTTP 200) with per-prompt cost estimates. No credits are reserved and no jobs are created."}},"required":["video_id","prompts"]},"Model":{"type":"object","properties":{"id":{"type":"string","example":"Darwin-preview-1.3B"},"object":{"type":"string","enum":["model"]},"status":{"type":"string","enum":["stable","preview","deprecated"],"description":"Lifecycle status. Preview means early access: detection quality and the set of reliably detected actions are still evolving, and results may improve or change between checkpoint updates. The API contract (request/response shapes, billing) is stable."},"default":{"type":"boolean"},"aliases":{"type":"array","items":{"type":"string"},"description":"Deprecated former ids still accepted on requests (case-insensitive). Responses always emit the canonical `id`.","example":["darwin-1.3"]},"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","aliases","capabilities","sunset_at","created_at"]},"ParseResponse":{"type":"object","properties":{"compiled_query":{"type":"object","additionalProperties":{},"description":"CompiledQuery — canonical JSON Schema served at /v1/schemas/compiled-query.json. Same structure accepted by POST /v1/analyses `query`."},"parse_mode":{"type":"string","enum":["haiku","heuristic"],"description":"How the prompt was compiled: \"haiku\" = LLM parser; \"heuristic\" = deterministic fallback (used when the LLM is unavailable)."}},"required":["compiled_query","parse_mode"]},"ParseRequest":{"type":"object","properties":{"prompt":{"type":"string","minLength":1,"description":"Free-text video question. Must be a non-empty string.","example":"Is there a dog in this video?"}},"required":["prompt"]},"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-preview-1.3B"},"options":{"type":"object","properties":{"narrative":{"type":"boolean","default":false}},"description":"narrative: true — opt into live narrative. Result-frame detections then carry a narrative_update object ({t_s, text}) whenever the engine produces a new sentence (event-driven, not per frame; absent on frames without one). Included in the metered price — no surcharge."},"recording":{"type":"boolean","description":"PRI-496: true — opt into retrieval of the server-side session recording. The ended stream then carries recording: {status} and GET /v1/streams/{id}/recording returns a signed 1h playback URL (raw H.264 elementary stream; lossless remux: ffmpeg -i in.h264 -c copy out.mp4). 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."}},"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"]},"StreamRecording":{"type":"object","properties":{"object":{"type":"string","enum":["stream_recording"]},"stream_id":{"type":"string","pattern":"^st_[0-9A-HJKMNP-TV-Z]{26}$","description":"Stream resource id (prefixed ULID). P6 §4.8.","example":"st_01KX12XX3BNM688AWHX7406HCF"},"url":{"type":"string","format":"uri","description":"Signed playback URL — fresh on every GET, 1h TTL. Re-fetch for a new one."},"expires_at":{"type":"string","description":"RFC 3339 expiry of the signed URL."},"content_type":{"type":"string","enum":["video/h264"]},"container":{"type":"string","enum":["h264-annex-b"],"description":"Raw H.264 Annex-B elementary stream. Lossless remux to MP4: ffmpeg -i in.h264 -c copy out.mp4."}},"required":["object","stream_id","url","expires_at","content_type","container"]},"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"]},"UpgradeKeyResponse":{"type":"object","properties":{"object":{"type":"string","enum":["api_key"]},"livemode":{"type":"boolean","enum":[true]},"api_key":{"type":"string","description":"The raw `pv_live_` key — store securely, shown only once."},"tier":{"type":"string","enum":["free_grant"]},"expires_at":{"type":"string","format":"date-time"},"grant_seconds":{"type":"integer","example":6000},"note":{"type":"string"},"claim_flow":{"type":"object","properties":{"endpoint":{"type":"string"},"how":{"type":"string"}},"required":["endpoint","how"]}},"required":["object","livemode","api_key","tier","expires_at","grant_seconds"]},"UpgradeKeyRequest":{"type":"object","properties":{"github_token":{"type":"string","description":"PRI-479: GitHub OAuth access token from the device flow. Required when the GitHub gate is enabled (calling without one returns 403 github_verification_required with device-flow bootstrap details). Verified against GitHub and discarded — never stored. One free grant per GitHub account, ever."}}},"DeviceCodeRequest":{"type":"object","properties":{"object":{"type":"string","enum":["device_code_request"]},"device_code":{"type":"string","description":"Opaque code; use as path param when polling."},"claim_url":{"type":"string","format":"uri","description":"Open in browser for the user to approve."},"expires_at":{"type":"string","format":"date-time"},"poll_interval":{"type":"integer","description":"Seconds between polls.","example":5},"instructions":{"type":"string"}},"required":["object","device_code","claim_url","expires_at","poll_interval"]},"DeviceCodeStatus":{"type":"object","properties":{"object":{"type":"string","enum":["device_code_request"]},"status":{"type":"string","enum":["pending","consumed"]}},"required":["object","status"]},"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)."},"CreditTransaction":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","description":"e.g. signup_grant, analysis_debit, stream_debit, purchase.","example":"signup_grant"},"seconds_delta":{"type":"number","description":"Signed: negative debits, positive credits."},"balance_after_seconds":{"type":"number"},"source_type":{"type":["string","null"]},"source_id":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","kind","seconds_delta","balance_after_seconds","source_type","source_id","created_at"]},"CreditsResponse":{"type":"object","properties":{"object":{"type":"string","enum":["credits"]},"balance_seconds":{"type":"number","description":"The authoritative credit balance (legacy field name retained for backward compatibility)."},"grant_seconds":{"type":["number","null"],"description":"Original signup grant, when one exists."},"used_seconds":{"type":["number","null"],"description":"Consumed against the signup grant (grant minus balance)."},"transactions":{"type":"array","items":{"$ref":"#/components/schemas/CreditTransaction"}},"has_more":{"type":"boolean"}},"required":["object","balance_seconds","grant_seconds","used_seconds","transactions","has_more"]}},"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/videos/{id}/analyses":{"get":{"summary":"List analyses for a video (result history)","description":"PRI-496: resource-nested spelling of GET /v1/analyses?video_id={id}. Same list envelope, same filters (status, limit, starting_after), ordered created_at desc. Use it to rebuild a video's full analysis/session history.","tags":["videos","analyses"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"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 of analyses referencing this video (§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":{"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. Set `validate_only: true` to dry-run a prompt — returns an AnalysisPreview with cost/assessability and creates nothing.","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) — OR an AnalysisPreview when validate_only:true was sent. Discriminate on the `object` field.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/Analysis"},{"$ref":"#/components/schemas/AnalysisPreview"}]}}}},"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":"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"},"description":"Filter by public analysis status (§13.2 vocabulary): queued | preparing | analyzing | rendering | completed | failed | canceled. E.g. status=analyzing returns currently-running analyses. Unknown values → 400 validation_failed."},{"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/demo-videos":{"get":{"summary":"Public sample videos with precomputed results (no auth)","description":"PRI-496 GAP-8: onboarding samples — curated videos with precomputed answers for a set of example prompts. Unauthenticated. Use these to see real result shapes before uploading your own video. The documented subset above is the stable contract; extra fields may appear and change without notice.","tags":["videos"],"responses":{"200":{"description":"Sample videos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoVideoList"}}}}}}},"/v1/analyses/{id}/rerun":{"post":{"summary":"Free re-run of a failed analysis (never billed)","description":"PRI-493: when a failed analysis carries rerun_eligible: true (it failed during a platform incident, or was flagged by ops), POST here creates a FRESH analysis — same video, prompt/query, model, and options — at no charge (usage stays null on the re-run). One free re-run per failed analysis. 409 rerun_not_eligible otherwise. Normal capacity gates still apply.","tags":["analyses"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Fresh analysis queued (new id). Never billed.","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"}}}},"503":{"description":"inference_unavailable / capacity_exhausted (with Retry-After) — the free re-run is NOT consumed on platform failure.","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/analyses/batch":{"post":{"summary":"Submit 2–10 prompts against the same video (batch discount)","description":"Pricing: first prompt billed at full price; each additional prompt billed at 50% (a 50% discount — config-driven; read the live value from GET /v1/credit-pricing batch_discount_pct). Credits are reserved per-prompt at the discounted rate so the ledger shows the discount directly. Returns an `analysis_batch` containing all analysis DTOs plus a `pricing` summary. Each analysis can be polled individually via `GET /v1/analyses/{id}`. Pass `validate_only: true` for a cost-estimate dry-run (HTTP 200, no credits reserved).","tags":["analyses"],"security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnalysisBatchRequest"}}}},"responses":{"200":{"description":"Dry-run preview (validate_only: true). Prompts parsed; cost estimated; no credits reserved, no jobs created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalysisBatchPreview"}}}},"202":{"description":"Batch queued. All analyses dispatched; poll each `id` individually.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalysisBatch"}}}},"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 / grant_exhausted","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":"capacity_exhausted / inference_unavailable","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. The current default is Darwin-preview-1.3B (status: preview). Preview means early access: detection quality and the set of reliably detected actions are still evolving, and results may improve or change between checkpoint updates. The API contract (request/response shapes, billing) is stable.","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/credit-pricing":{"get":{"summary":"Current credit pricing (public, no auth)","tags":["meta"],"responses":{"200":{"description":"Pricing config. Billing is per processed frame (PRI-552): cost = usage.frames × price_per_frame_cents. Legacy fields (price_per_second_cents etc.) remain served for backward compatibility. Read rates from here, never hardcode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditPricing"}}}}}}},"/v1/parse":{"post":{"summary":"Compile a free-text prompt into a CompiledQuery (no analysis created)","description":"Stateless prompt compiler. Auth optional (anonymous allowed); rate-limited to 30 req/min per IP. Never touches the GPU and never burns credits — use POST /v1/analyses with validate_only:true when you also want cost/assessability estimates.","tags":["meta"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseRequest"}}}},"responses":{"200":{"description":"The compiled query and which parser produced it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseResponse"}}}},"400":{"description":"empty_prompt — prompt missing, not a string, or blank.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"parse_failed — prompt could not be compiled into a query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limit_exceeded (30 req/min per IP).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"parse_unavailable — parse service temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/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 {prompt} (client→server; server confirms with prompt_updated), 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. options.narrative: true adds (a) a narrative_update object ({t_s, text}) to result-frame detections whenever the engine emits a new sentence — event-driven, absent otherwise — and (b) an additive server→client status event {type: \"status\", status: \"prompt_context\"|\"combined_prompt\"|\"recalculating\", message?} for ordering/annotating narrative entries (closed vocabulary; non-opted-in streams never receive status events). No extra cost.","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}/recording":{"get":{"summary":"Fetch the session recording (signed URL, sign-on-read)","description":"PRI-496: returns a signed playback URL for the server-side session recording. Requires the stream to have been created with recording: true and to have ended with a stored recording. 404 resource_not_found when recording was not enabled; 409 resource_conflict while the stream is still active, when capture failed, or when no recording was stored. URL TTL 1h — re-fetch for a fresh one.","tags":["streams"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Signed recording URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamRecording"}}}},"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/keys/upgrade":{"post":{"summary":"Upgrade a test key to a live free-grant key (6,000s credit)","description":"Stage 1 of the key lifecycle. Call with a `pv_test_` key in Authorization; receive a `pv_live_` key with `tier: free_grant` and a 6,000-second credit grant. No email or card required. **GitHub-gated (PRI-479):** calling without a `github_token` returns `403 github_verification_required` whose `details` carry the OAuth `client_id` and device-flow URLs — complete GitHub's device flow and retry with `{\"github_token\": \"...\"}`. One free grant per GitHub account, ever (duplicate → `409 github_account_already_used`). IP caps (3 / IP / 24h) remain as secondary defense.","tags":["provisioning"],"security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpgradeKeyRequest"}}}},"responses":{"201":{"description":"Live key issued. The raw `api_key` appears only in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpgradeKeyResponse"}}}},"401":{"description":"github_token_invalid — the supplied github_token failed verification with GitHub.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"test_mode_only — must call with a pv_test_ key. github_verification_required — GitHub gate enabled and no github_token supplied; `details` carry the device-flow bootstrap (client_id, URLs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"github_account_already_used — this GitHub account has already claimed its free grant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"upgrade_limit_exceeded — per-IP or global daily cap reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/request":{"post":{"summary":"Start a device-code claim flow for a full billed key","description":"Stage 2 of the key lifecycle. Optional auth (test or live key accepted, or none). Returns a `device_code` and `claim_url`. Direct the user to `claim_url` to sign in; poll `GET /v1/keys/request/{code}` until status changes from `pending`. Key is delivered once in the claim response; subsequent polls return 410.","tags":["provisioning"],"responses":{"202":{"description":"Device code issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCodeRequest"}}}},"503":{"description":"db_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/request/{code}":{"get":{"summary":"Poll a device-code claim request","description":"Returns `{status: \"pending\"}` until the user approves in the browser. After approval, returns `status: \"consumed\"` (key was delivered in the claim response). Expired or already-consumed codes return 410.","tags":["provisioning"],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"description":"The `device_code` from POST /v1/keys/request."}],"responses":{"200":{"description":"Current status of the device code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCodeStatus"}}}},"404":{"description":"resource_not_found — code not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"410":{"description":"device_code_expired — code consumed or expired. Start a new request.","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"}}}}}}},"/v1/credits":{"get":{"summary":"Credit balance and transaction history","description":"Agent-facing billing truth (PRI-480 A-5): remaining balance in billable seconds plus a paginated ledger. Check this before submitting work to avoid a 402. Requires the billing:read scope.","tags":["billing"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"Transactions per page (1–100, default 20).","example":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Cursor — return transactions older than this id."},"required":false,"name":"before","in":"query"}],"responses":{"200":{"description":"Balance plus one page of ledger entries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditsResponse"}}}},"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":{}}