{"openapi":"3.1.0","info":{"title":"Chalkboard API","description":"Chalkboard turns a topic into a narrated explainer video. Authenticate with a chk_live_ key (Authorization: Bearer), POST a topic, then poll or subscribe via webhooks for the finished mp4. Full guide at https://chalkboard.studio/docs/api.","version":"0.2.0"},"paths":{"/api/jobs":{"get":{"summary":"List Jobs","operationId":"list_jobs_api_jobs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/JobResponse"},"type":"array","title":"Response List Jobs Api Jobs Get"}}}}}},"post":{"summary":"Create Job","operationId":"create_job_api_jobs_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJobRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/upload":{"post":{"summary":"Create Job With Files","operationId":"create_job_with_files_api_jobs_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_job_with_files_api_jobs_upload_post"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/estimate":{"post":{"summary":"Estimate Job Cost","description":"Return the (low, high) credit range for a hypothetical render,\nplus your current balance. No job is created and no credits are\nspent. Use it to pre-flight cost before POST /jobs.","operationId":"estimate_job_cost_api_jobs_estimate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}":{"get":{"summary":"Get Job","operationId":"get_job_api_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Cancel Job","operationId":"cancel_job_api_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/log":{"get":{"summary":"Job Log","description":"Return the full pipeline event timeline for a job you own.\n\nThe default `format=text` returns plain text you can paste into a\nsupport email; `format=json` returns the raw event list for\ntooling. One-shot, no streaming. Use /events for the live SSE\nstream that powers the progress page.","operationId":"job_log_api_jobs__job_id__log_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"text","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/events":{"get":{"summary":"Job Events","operationId":"job_events_api_jobs__job_id__events_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/retry":{"post":{"summary":"Retry Job","description":"Create a fresh job with the same parameters as a prior run.\nOnly allowed on terminal (completed/failed/cancelled) jobs —\nretrying an in-flight job would dispatch a second pipeline for\nthe same params, which is usually not what the user wants. The\nnew job gets a new id; spend_log rows from the old run stay put.\n\nTest-mode follows the *retry's* auth, not the source job's. If\nyou retry a live job with a chk_test_ key, the new job is test\nmode (cheap settings); retrying a test job with a chk_live_ key\npromotes it to a real run.","operationId":"retry_job_api_jobs__job_id__retry_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/rerender":{"post":{"summary":"Rerender Job","description":"Re-render a completed job, reusing the script + voiceover.\n\nDistinct from /retry which full-regenerates from the topic (new\nscript, new narration, new visuals). /rerender keeps the narration\nidentical and only regenerates the Manim scene — useful when the\nuser liked the script but the visuals had a layout bug or need\na new take. Cheaper than /retry: only one Claude call (manim_agent)\nplus the Manim render, no TTS.","operationId":"rerender_job_api_jobs__job_id__rerender_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/report-issue":{"post":{"summary":"Report Issue","description":"Relay a \"something's wrong\" report for this job to support.\n\nAlways returns 202: if the message fails to send it is logged, but\nyou still get a success response so the UI can confirm receipt\n(resending would only create a duplicate).","operationId":"report_issue_api_jobs__job_id__report_issue_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportIssueRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/status":{"get":{"summary":"System Status","operationId":"system_status_api_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/meta":{"get":{"summary":"Meta","operationId":"meta_api_meta_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/jobs/{job_id}/files/{filename}":{"get":{"summary":"Get File","operationId":"get_file_api_jobs__job_id__files__filename__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/waitlist":{"post":{"summary":"Join Waitlist","operationId":"join_waitlist_api_waitlist_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/notifications/active":{"get":{"summary":"List Active Notifications","description":"Return the in-app banner notifications you should currently see.\n\nExcludes dismissed, expired, and audience-mismatched notifications.\nAnonymous visitors receive none.","operationId":"list_active_notifications_api_notifications_active_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/notifications/{nid}/view":{"post":{"summary":"Notification View","description":"Record that the user has seen this banner. Idempotent: first\ncall sets viewed_at to NOW(), subsequent calls leave it alone.","operationId":"notification_view_api_notifications__nid__view_post","parameters":[{"name":"nid","in":"path","required":true,"schema":{"type":"string","title":"Nid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/notifications/{nid}/click":{"post":{"summary":"Notification Click","operationId":"notification_click_api_notifications__nid__click_post","parameters":[{"name":"nid","in":"path","required":true,"schema":{"type":"string","title":"Nid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/notifications/{nid}/dismiss":{"post":{"summary":"Notification Dismiss","operationId":"notification_dismiss_api_notifications__nid__dismiss_post","parameters":[{"name":"nid","in":"path","required":true,"schema":{"type":"string","title":"Nid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/library":{"get":{"summary":"List Videos","operationId":"list_videos_api_library_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"newest","title":"Sort"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/library/{run_id}":{"get":{"summary":"Get Video","operationId":"get_video_api_library__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Video","operationId":"delete_video_api_library__run_id__delete","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Files"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/library/public/{run_id}":{"get":{"summary":"Get Video Public","description":"Public mirror of GET /library/{run_id} for homepage featured\nvideos. No auth — any anonymous visitor can pull the video\nmeta + signed URLs needed to play the final.mp4. Only run_ids\nexplicitly listed in config.FEATURED_RUN_IDS are served; all\nothers 404 regardless of whether the row actually exists (404\nrather than 403 avoids leaking existence).\n\nIntentionally omits the spend_log cost/tokens lookup that the\nauthenticated endpoint does — that's internal billing info,\nnot something anon visitors need or should see.","operationId":"get_video_public_api_library_public__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/library/{run_id}/report-visual-issue":{"post":{"summary":"Report Visual Issue","description":"Report a visual issue on a completed video you own.\n\nReturns 201 with the new report id. Not idempotent: submitting\ntwice creates two reports. Distinct from POST\n/api/jobs/{id}/report-issue, which emails a report for a stuck or\nin-progress job.","operationId":"report_visual_issue_api_library__run_id__report_visual_issue_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisualIssueReportRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs":{"get":{"summary":"List Jobs","operationId":"list_jobs_api_v1_jobs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/JobResponse"},"type":"array","title":"Response List Jobs Api V1 Jobs Get"}}}}}},"post":{"summary":"Create Job","operationId":"create_job_api_v1_jobs_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJobRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/upload":{"post":{"summary":"Create Job With Files","operationId":"create_job_with_files_api_v1_jobs_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_job_with_files_api_v1_jobs_upload_post"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/estimate":{"post":{"summary":"Estimate Job Cost","description":"Return the (low, high) credit range for a hypothetical render,\nplus your current balance. No job is created and no credits are\nspent. Use it to pre-flight cost before POST /jobs.","operationId":"estimate_job_cost_api_v1_jobs_estimate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}":{"get":{"summary":"Get Job","operationId":"get_job_api_v1_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Cancel Job","operationId":"cancel_job_api_v1_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}/log":{"get":{"summary":"Job Log","description":"Return the full pipeline event timeline for a job you own.\n\nThe default `format=text` returns plain text you can paste into a\nsupport email; `format=json` returns the raw event list for\ntooling. One-shot, no streaming. Use /events for the live SSE\nstream that powers the progress page.","operationId":"job_log_api_v1_jobs__job_id__log_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"text","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}/events":{"get":{"summary":"Job Events","operationId":"job_events_api_v1_jobs__job_id__events_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}/retry":{"post":{"summary":"Retry Job","description":"Create a fresh job with the same parameters as a prior run.\nOnly allowed on terminal (completed/failed/cancelled) jobs —\nretrying an in-flight job would dispatch a second pipeline for\nthe same params, which is usually not what the user wants. The\nnew job gets a new id; spend_log rows from the old run stay put.\n\nTest-mode follows the *retry's* auth, not the source job's. If\nyou retry a live job with a chk_test_ key, the new job is test\nmode (cheap settings); retrying a test job with a chk_live_ key\npromotes it to a real run.","operationId":"retry_job_api_v1_jobs__job_id__retry_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}/rerender":{"post":{"summary":"Rerender Job","description":"Re-render a completed job, reusing the script + voiceover.\n\nDistinct from /retry which full-regenerates from the topic (new\nscript, new narration, new visuals). /rerender keeps the narration\nidentical and only regenerates the Manim scene — useful when the\nuser liked the script but the visuals had a layout bug or need\na new take. Cheaper than /retry: only one Claude call (manim_agent)\nplus the Manim render, no TTS.","operationId":"rerender_job_api_v1_jobs__job_id__rerender_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/jobs/{job_id}/report-issue":{"post":{"summary":"Report Issue","description":"Relay a \"something's wrong\" report for this job to support.\n\nAlways returns 202: if the message fails to send it is logged, but\nyou still get a success response so the UI can confirm receipt\n(resending would only create a duplicate).","operationId":"report_issue_api_v1_jobs__job_id__report_issue_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportIssueRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/status":{"get":{"summary":"System Status","operationId":"system_status_api_v1_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/meta":{"get":{"summary":"Meta","operationId":"meta_api_v1_meta_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/jobs/{job_id}/files/{filename}":{"get":{"summary":"Get File","operationId":"get_file_api_v1_jobs__job_id__files__filename__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/waitlist":{"post":{"summary":"Join Waitlist","operationId":"join_waitlist_api_v1_waitlist_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications/active":{"get":{"summary":"List Active Notifications","description":"Return the in-app banner notifications you should currently see.\n\nExcludes dismissed, expired, and audience-mismatched notifications.\nAnonymous visitors receive none.","operationId":"list_active_notifications_api_v1_notifications_active_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/notifications/{nid}/view":{"post":{"summary":"Notification View","description":"Record that the user has seen this banner. Idempotent: first\ncall sets viewed_at to NOW(), subsequent calls leave it alone.","operationId":"notification_view_api_v1_notifications__nid__view_post","parameters":[{"name":"nid","in":"path","required":true,"schema":{"type":"string","title":"Nid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications/{nid}/click":{"post":{"summary":"Notification Click","operationId":"notification_click_api_v1_notifications__nid__click_post","parameters":[{"name":"nid","in":"path","required":true,"schema":{"type":"string","title":"Nid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications/{nid}/dismiss":{"post":{"summary":"Notification Dismiss","operationId":"notification_dismiss_api_v1_notifications__nid__dismiss_post","parameters":[{"name":"nid","in":"path","required":true,"schema":{"type":"string","title":"Nid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/library":{"get":{"summary":"List Videos","operationId":"list_videos_api_v1_library_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"newest","title":"Sort"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/library/{run_id}":{"get":{"summary":"Get Video","operationId":"get_video_api_v1_library__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Video","operationId":"delete_video_api_v1_library__run_id__delete","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Files"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/library/public/{run_id}":{"get":{"summary":"Get Video Public","description":"Public mirror of GET /library/{run_id} for homepage featured\nvideos. No auth — any anonymous visitor can pull the video\nmeta + signed URLs needed to play the final.mp4. Only run_ids\nexplicitly listed in config.FEATURED_RUN_IDS are served; all\nothers 404 regardless of whether the row actually exists (404\nrather than 403 avoids leaking existence).\n\nIntentionally omits the spend_log cost/tokens lookup that the\nauthenticated endpoint does — that's internal billing info,\nnot something anon visitors need or should see.","operationId":"get_video_public_api_v1_library_public__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/library/{run_id}/report-visual-issue":{"post":{"summary":"Report Visual Issue","description":"Report a visual issue on a completed video you own.\n\nReturns 201 with the new report id. Not idempotent: submitting\ntwice creates two reports. Distinct from POST\n/api/jobs/{id}/report-issue, which emails a report for a stuck or\nin-progress job.","operationId":"report_visual_issue_api_v1_library__run_id__report_visual_issue_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisualIssueReportRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/library":{"get":{"summary":"Gated Library Html","operationId":"gated_library_html_library_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/generate":{"get":{"summary":"Gated Generate Html","operationId":"gated_generate_html_generate_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/account":{"get":{"summary":"Gated Account Html","operationId":"gated_account_html_account_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/status":{"get":{"summary":"Status Html","operationId":"status_html_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/redeem":{"get":{"summary":"Gated Redeem Html","operationId":"gated_redeem_html_redeem_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/progress/{job_id}":{"get":{"summary":"Progress Page","operationId":"progress_page_progress__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/library/{run_id}":{"get":{"summary":"Video Page","operationId":"video_page_library__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/login":{"get":{"summary":"Login Html","operationId":"login_html_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/reset":{"get":{"summary":"Reset Html","operationId":"reset_html_reset_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/guide":{"get":{"summary":"Guide Html","operationId":"guide_html_guide_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cli":{"get":{"summary":"Cli Html","operationId":"cli_html_cli_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/docs/api":{"get":{"summary":"Api Html","operationId":"api_html_docs_api_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/changelog":{"get":{"summary":"Changelog Html","operationId":"changelog_html_changelog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/404":{"get":{"summary":"404 Html","operationId":"404_html_404_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/pricing":{"get":{"summary":"Pricing Html","operationId":"pricing_html_pricing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/login.html":{"get":{"summary":"Login Html","operationId":"login_html_login_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/guide.html":{"get":{"summary":"Guide Html","operationId":"guide_html_guide_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cli.html":{"get":{"summary":"Cli Html","operationId":"cli_html_cli_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api.html":{"get":{"summary":"Api Html","operationId":"api_html_api_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/changelog.html":{"get":{"summary":"Changelog Html","operationId":"changelog_html_changelog_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/404.html":{"get":{"summary":"404 Html","operationId":"404_html_404_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/":{"get":{"summary":"Index Html","operationId":"index_html__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/index.html":{"get":{"summary":"Index Html","operationId":"index_html_index_html_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/join":{"post":{"summary":"Join","operationId":"join_api_join_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/session":{"post":{"summary":"Create Session","description":"Exchange `Authorization: Bearer <id-token>` for a session cookie.","operationId":"create_session_api_session_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/password-reset":{"post":{"summary":"Request Password Reset","description":"Mint + send a branded password-reset email for `email`.\n\nEnumeration-resistant: always returns 202 regardless of whether\nthe email matches a real account. Rate-limited per-email (5 per\nhour) to bound abuse if a script enumerates the form.\n\nThe branded email points at /reset (our own page) — the user\nnever lands on firebaseapp.com. Replaces the client-side\n`sendPasswordResetEmail()` call which used Firebase's default\ntemplate (\"Reset your password for project-523117438370\",\nspam-flagged, off-brand).","operationId":"request_password_reset_api_auth_password_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/session/logout":{"post":{"summary":"Destroy Session","description":"Clear the session cookie and revoke the user's refresh tokens.\n\nIdempotent — works whether or not a valid cookie was sent. Revocation\nfailures are logged and swallowed; clearing the browser-side cookie\nis the primary user-visible effect.","operationId":"destroy_session_api_session_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/account":{"delete":{"summary":"Delete Account","description":"Delete the authenticated user and all their data. Returns 204\non success. Idempotent-ish: a second call will 403 via\nget_current_user because the user row is gone.","operationId":"delete_account_api_account_delete","responses":{"204":{"description":"Successful Response"}}}},"/api/account/spend":{"get":{"summary":"Get Account Spend","description":"Your 24h rolling spend, effective cap, and reset time.\n\nCheap to poll, so the nav chip can show what's coming before the\n402 fires.\n\n`next_reset_at` is the earliest moment any of your current spend\nages out of the rolling 24h window (null when you have no spend in\nthe window). It is approximate: any single charge aging out frees\nits share of headroom.","operationId":"get_account_spend_api_account_spend_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/account/spend-trajectory":{"get":{"summary":"Get Account Spend Trajectory","description":"Per-day rollup of THIS user's own spend over the last `days`.\n\nSame shape as the admin /users/{uid}/spend-trajectory endpoint —\ndays with no spend still surface as zero-rows so the bar chart\non /account has a fixed-width axis. Capped at 30 days.","operationId":"get_account_spend_trajectory_api_account_spend_trajectory_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/redeem":{"post":{"summary":"Redeem Code","operationId":"redeem_code_api_billing_redeem_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemCodeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/credits-ledger":{"get":{"summary":"Get Credits Ledger","description":"Most-recent N credits_ledger rows for the authenticated user.\nDrives the per-user history panel on /account. Per-user scope\nmeans the natural index (user_id, created_at DESC) does the\nheavy lifting; no need to paginate beyond 'recent activity'.\n\n`limit` is clamped to [1, 200] — beyond that the panel becomes\nunwieldy and any operator-side investigation should use admin\nendpoints + raw SQL instead.","operationId":"get_credits_ledger_api_account_credits_ledger_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/email-prefs":{"get":{"summary":"Get Email Prefs","operationId":"get_email_prefs_api_account_email_prefs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPrefs"}}}}}},"patch":{"summary":"Patch Email Prefs","operationId":"patch_email_prefs_api_account_email_prefs_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPrefsPatch"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPrefs"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/api-keys":{"get":{"summary":"List Api Keys","description":"Newest-first list of every key the user has ever created\n(active + revoked + expired). Never returns a raw key.","operationId":"list_api_keys_api_account_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ApiKeyMeta"},"type":"array","title":"Response List Api Keys Api Account Api Keys Get"}}}}}},"post":{"summary":"Create Api Key","description":"Mint a new API key. The raw key is returned exactly once in\nthe `key` field of the response — the server never shows it\nagain. Subsequent listings only return the prefix hint.","operationId":"create_api_key_api_account_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/api-keys/{key_id}":{"delete":{"summary":"Revoke Api Key","description":"Soft-revoke a key. Idempotent: revoking an already-revoked\nkey (or one owned by another user) returns 404 either way so we\ndon't leak which case it was.","operationId":"revoke_api_key_api_account_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/api-keys/{key_id}/permanent":{"delete":{"summary":"Hard Delete Api Key","description":"Permanently remove a revoked API key from the user's table.\n\nTwo safety rails:\n  1. Only revoked keys can be hard-deleted (api_keys.hard_delete\n     enforces this with `revoked_at IS NOT NULL`). Forces the\n     user to revoke first so any integration using the key sees\n     a proper 401 + has time to rotate before the row vanishes.\n  2. user_id ownership check (same as soft-revoke). 404 collapses\n     \"not yours\" / \"doesn't exist\" / \"still active\" into one\n     response so the response doesn't leak which case it was.\n\nSurfaces as the small \"Remove\" affordance next to each revoked\nrow in the /account API-keys table — keeps the table from\ngrowing without bound after a few rotations.","operationId":"hard_delete_api_key_api_account_api_keys__key_id__permanent_delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/email/unsubscribe":{"get":{"summary":"Unsubscribe Get","operationId":"unsubscribe_get_api_email_unsubscribe_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Unsubscribe Post","operationId":"unsubscribe_post_api_email_unsubscribe_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/export":{"get":{"summary":"Export User Data","description":"DSAR (Data Subject Access Request) — GDPR Art. 15 right of\naccess. Returns the authenticated user's full data as JSON.\nBearer-token auth requires the actual user (not a leaked API\nkey) so a stolen key can't exfiltrate the whole account state.\n\nData sourced from every table that references firebase_uid:\nusers + jobs + videos + spend_log + credits_ledger +\npromo_code_redemptions + invite_redemptions. Other tables\n(audit_log entries WHERE actor_admin_uid = uid, when the user\nIS an admin) excluded — admin actions are operator data, not\npersonal data the user is entitled to under Art. 15.","operationId":"export_user_data_api_account_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/webhooks":{"get":{"summary":"List Webhooks","operationId":"list_webhooks_api_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WebhookMeta"},"type":"array","title":"Response List Webhooks Api Webhooks Get"}}}}}},"post":{"summary":"Create Webhook","operationId":"create_webhook_api_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/{webhook_id}":{"delete":{"summary":"Delete Webhook","operationId":"delete_webhook_api_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/{webhook_id}/deliveries":{"get":{"summary":"Get Deliveries","description":"Last 50 deliveries for debugging. Empty list if the webhook\ndoesn't exist or doesn't belong to the caller — same shape as\n`webhooks.list_recent_deliveries` enforces ownership.","operationId":"get_deliveries_api_webhooks__webhook_id__deliveries_get","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/webhooks":{"get":{"summary":"List Webhooks","operationId":"list_webhooks_api_v1_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WebhookMeta"},"type":"array","title":"Response List Webhooks Api V1 Webhooks Get"}}}}}},"post":{"summary":"Create Webhook","operationId":"create_webhook_api_v1_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/webhooks/{webhook_id}":{"delete":{"summary":"Delete Webhook","operationId":"delete_webhook_api_v1_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/webhooks/{webhook_id}/deliveries":{"get":{"summary":"Get Deliveries","description":"Last 50 deliveries for debugging. Empty list if the webhook\ndoesn't exist or doesn't belong to the caller — same shape as\n`webhooks.list_recent_deliveries` enforces ownership.","operationId":"get_deliveries_api_v1_webhooks__webhook_id__deliveries_get","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ApiKeyMeta":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"hint":{"type":"string","title":"Hint"},"created_at":{"type":"string","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","name","prefix","hint","created_at","last_used_at","revoked_at","expires_at","is_active"],"title":"ApiKeyMeta","description":"List/get representation. No raw key — safe to return repeatedly."},"ApiKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"key":{"type":"string","title":"Key"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"hint":{"type":"string","title":"Hint"},"created_at":{"type":"string","title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","key","name","prefix","hint","created_at","expires_at"],"title":"ApiKeyResponse","description":"Returned at create time; contains the raw key. The user MUST copy\nthis — the server never shows it again. Subsequent list endpoints\nreturn ApiKeyMeta (no `key` field)."},"Body_create_job_with_files_api_jobs_upload_post":{"properties":{"topic":{"type":"string","title":"Topic"},"effort":{"type":"string","title":"Effort","default":"medium"},"audience":{"type":"string","title":"Audience","default":"intermediate"},"tone":{"type":"string","title":"Tone","default":"casual"},"theme":{"type":"string","title":"Theme","default":"chalkboard"},"template":{"type":"string","title":"Template","default":""},"speed":{"type":"number","title":"Speed","default":1.0},"burn_captions":{"type":"boolean","title":"Burn Captions","default":false},"quiz":{"type":"boolean","title":"Quiz","default":false},"qa_density":{"type":"string","title":"Qa Density","default":"normal"},"urls":{"items":{"type":"string"},"type":"array","title":"Urls","default":[]},"github":{"items":{"type":"string"},"type":"array","title":"Github","default":[]},"email_on_complete":{"type":"boolean","title":"Email On Complete","default":true},"quality":{"type":"string","title":"Quality","default":"medium"},"model":{"type":"string","title":"Model","default":""},"reliability":{"type":"string","title":"Reliability","default":"normal"},"narrator":{"type":"string","title":"Narrator","default":"basic"},"files":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Files","default":[]}},"type":"object","required":["topic"],"title":"Body_create_job_with_files_api_jobs_upload_post"},"Body_create_job_with_files_api_v1_jobs_upload_post":{"properties":{"topic":{"type":"string","title":"Topic"},"effort":{"type":"string","title":"Effort","default":"medium"},"audience":{"type":"string","title":"Audience","default":"intermediate"},"tone":{"type":"string","title":"Tone","default":"casual"},"theme":{"type":"string","title":"Theme","default":"chalkboard"},"template":{"type":"string","title":"Template","default":""},"speed":{"type":"number","title":"Speed","default":1.0},"burn_captions":{"type":"boolean","title":"Burn Captions","default":false},"quiz":{"type":"boolean","title":"Quiz","default":false},"qa_density":{"type":"string","title":"Qa Density","default":"normal"},"urls":{"items":{"type":"string"},"type":"array","title":"Urls","default":[]},"github":{"items":{"type":"string"},"type":"array","title":"Github","default":[]},"email_on_complete":{"type":"boolean","title":"Email On Complete","default":true},"quality":{"type":"string","title":"Quality","default":"medium"},"model":{"type":"string","title":"Model","default":""},"reliability":{"type":"string","title":"Reliability","default":"normal"},"narrator":{"type":"string","title":"Narrator","default":"basic"},"files":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Files","default":[]}},"type":"object","required":["topic"],"title":"Body_create_job_with_files_api_v1_jobs_upload_post"},"CreateApiKeyRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"expires_in":{"type":"string","enum":["30d","90d","1y","never"],"title":"Expires In","default":"90d"},"test_mode":{"type":"boolean","title":"Test Mode","default":false}},"type":"object","required":["name"],"title":"CreateApiKeyRequest"},"CreateJobRequest":{"properties":{"topic":{"type":"string","maxLength":64000,"minLength":1,"title":"Topic"},"effort":{"type":"string","enum":["low","medium","high"],"title":"Effort","default":"medium"},"audience":{"type":"string","enum":["beginner","intermediate","expert"],"title":"Audience","default":"intermediate"},"tone":{"type":"string","enum":["casual","formal","socratic"],"title":"Tone","default":"casual"},"theme":{"type":"string","enum":["chalkboard","light","colorful"],"title":"Theme","default":"chalkboard"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template"},"speed":{"type":"number","title":"Speed","default":1.0},"burn_captions":{"type":"boolean","title":"Burn Captions","default":false},"quiz":{"type":"boolean","title":"Quiz","default":false},"urls":{"items":{"type":"string"},"type":"array","title":"Urls","default":[]},"github":{"items":{"type":"string"},"type":"array","title":"Github","default":[]},"qa_density":{"type":"string","enum":["zero","normal","high"],"title":"Qa Density","default":"normal"},"email_on_complete":{"type":"boolean","title":"Email On Complete","default":true},"quality":{"type":"string","enum":["low","medium","high","production"],"title":"Quality","default":"medium"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"reliability":{"type":"string","enum":["normal","max"],"title":"Reliability","default":"normal"},"narrator":{"type":"string","enum":["basic","grace","milo","aria","reid"],"title":"Narrator","default":"basic"}},"type":"object","required":["topic"],"title":"CreateJobRequest"},"CreateWebhookRequest":{"properties":{"url":{"type":"string","maxLength":2048,"minLength":8,"title":"Url"},"events":{"items":{"type":"string","enum":["job.completed","job.failed","job.cancelled"]},"type":"array","maxItems":3,"title":"Events"},"description":{"type":"string","maxLength":200,"title":"Description","default":""}},"type":"object","required":["url"],"title":"CreateWebhookRequest"},"EmailPrefs":{"properties":{"job_complete":{"type":"boolean","title":"Job Complete","default":true},"job_failed":{"type":"boolean","title":"Job Failed","default":true},"spend_step":{"type":"boolean","title":"Spend Step","default":true},"credit_grant":{"type":"boolean","title":"Credit Grant","default":true},"spend_step_credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spend Step Credits"}},"type":"object","title":"EmailPrefs","description":"A signed-in user's preferences for the automated emails Chalkboard\ncan send them.\n\n`job_complete`, `job_failed`, `spend_step`, and `credit_grant` are\non/off toggles (all default on). `spend_step_credits` overrides the\ncredit-usage interval that triggers a spend heads-up email; null\nfalls back to the system default."},"EmailPrefsPatch":{"properties":{"job_complete":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Job Complete"},"job_failed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Job Failed"},"spend_step":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spend Step"},"credit_grant":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Credit Grant"},"spend_step_credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spend Step Credits"}},"type":"object","title":"EmailPrefsPatch","description":"Partial update for `EmailPrefs`. Each field is independent so\nthe UI can fire one PATCH per change without round-tripping the\nothers.\n\n`spend_step_credits` accepts an int to set/replace the override or\n`null` to clear it (revert to the global default). The route\ndistinguishes \"field absent\" from \"field=null\" via\n`model_fields_set` so a clear is explicit, not implicit."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","enum":["pending","running","completed","failed","cancelled"],"title":"Status"},"topic":{"type":"string","title":"Topic"},"events":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Events"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"output_files":{"items":{"type":"string"},"type":"array","title":"Output Files"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"mode":{"type":"string","enum":["live","test"],"title":"Mode","default":"live"}},"type":"object","required":["id","status","topic","events","error","output_files"],"title":"JobResponse"},"JoinRequest":{"properties":{"invite_code":{"type":"string","title":"Invite Code"},"firebase_token":{"type":"string","title":"Firebase Token"}},"type":"object","required":["invite_code","firebase_token"],"title":"JoinRequest"},"PasswordResetRequest":{"properties":{"email":{"type":"string","maxLength":320,"minLength":3,"title":"Email"}},"type":"object","required":["email"],"title":"PasswordResetRequest"},"RedeemCodeRequest":{"properties":{"code":{"type":"string","maxLength":64,"minLength":1,"title":"Code"}},"type":"object","required":["code"],"title":"RedeemCodeRequest"},"ReportIssueRequest":{"properties":{"message":{"type":"string","maxLength":4000,"title":"Message","default":""}},"type":"object","title":"ReportIssueRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VisualIssueReportRequest":{"properties":{"description":{"type":"string","maxLength":4000,"minLength":1,"title":"Description"},"segment_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Segment Index"}},"type":"object","required":["description"],"title":"VisualIssueReportRequest","description":"A report flagging a visual issue on a completed video.\n\nDistinct from `ReportIssueRequest` above, which is for stuck or\nin-progress jobs and sends an email."},"WaitlistRequest":{"properties":{"email":{"type":"string","maxLength":320,"title":"Email"},"name":{"type":"string","maxLength":200,"title":"Name","default":""},"message":{"type":"string","maxLength":2000,"title":"Message","default":""},"turnstile_token":{"type":"string","maxLength":4096,"title":"Turnstile Token","default":""}},"type":"object","required":["email"],"title":"WaitlistRequest"},"WebhookCreated":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"description":{"type":"string","title":"Description"},"signing_secret":{"type":"string","title":"Signing Secret"},"created_at":{"type":"string","title":"Created At"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","url","events","description","signing_secret","created_at","is_active"],"title":"WebhookCreated","description":"Returned at create time; contains the signing secret. The user\nMUST copy this — the server never shows it again."},"WebhookMeta":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"description":{"type":"string","title":"Description"},"created_at":{"type":"string","title":"Created At"},"disabled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disabled At"},"consecutive_failures":{"type":"integer","title":"Consecutive Failures"},"last_delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Delivered At"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","url","events","description","created_at","disabled_at","consecutive_failures","last_delivered_at","is_active"],"title":"WebhookMeta"}}}}