{"openapi":"3.1.0","info":{"title":"KinoCloud API","version":"1.0.0","description":"Programmatic access to KinoCloud rendering, simulation, and credits. Authenticate with an API key (`Authorization: Bearer kc_live_*`) or a browser session."},"servers":[{"url":"/api/v1"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Jobs","description":"Submit, estimate, monitor, and control render and simulation jobs."},{"name":"Credits","description":"Read the credit balance and ledger, and start credit top-ups."},{"name":"API Keys","description":"Create, list, and revoke API keys (browser session only)."},{"name":"Settings","description":"Account configuration such as BYO-license servers."},{"name":"Public","description":"Unauthenticated endpoints used by the public site."}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"kc_live_* | kc_test_*","description":"A KinoCloud API key created at POST /v1/keys."}},"schemas":{"ErrorResponse":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message"},"code":{"description":"Machine-readable error code present on actionable failures, e.g. UNAUTHORIZED | INSUFFICIENT_SCOPE | TEST_KEY_LIVE_BILLING | INSUFFICIENT_CREDITS | NO_PAYMENT_METHOD | COST_LIMIT_EXCEEDED | RATE_LIMITED | UNSUPPORTED_SIMULATOR | UNKNOWN_TIER. See the API docs error reference for the full catalogue.","type":"string"}},"required":["error"],"additionalProperties":false},"SubmitJobRequest":{"type":"object","properties":{"jobType":{"default":"render","description":"Job type: render (frame-based DCC render), simulation (task-based), molecular (Molecular Studio publication render), pipeline (simulation→render chain; gated — 422 PIPELINE_DISABLED unless enabled), or inference (AI generation)","type":"string","enum":["render","simulation","molecular","pipeline","inference"]},"sceneFile":{"description":"Storage key of the scene file or input dataset, returned by POST /v1/jobs/upload-url or the multipart /v1/uploads flow. Required for every job type EXCEPT inference.","type":"string"},"frameStart":{"description":"First frame to render, inclusive (render jobs only)","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"frameEnd":{"description":"Last frame to render, inclusive (render jobs only)","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"outputFormat":{"description":"Output image format: EXR, PNG, JPEG (render jobs only)","type":"string"},"simulator":{"description":"Allowlisted simulator id, e.g. autodock-vina, blast, alphafold, swan, wavewatch3 (simulation jobs only)","type":"string"},"taskCount":{"description":"Number of independent parallel tasks (simulation jobs only)","type":"integer","minimum":1,"maximum":9007199254740991},"modelId":{"description":"Catalog model id from GET /v1/models, e.g. wan-2.2-t2v (inference jobs only)","type":"string"},"units":{"description":"Number of outputs to generate, 1..model.maxUnits (inference jobs only)","type":"integer","minimum":1,"maximum":9007199254740991},"params":{"description":"Model-specific generation params, validated against the model schema (inference)","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"metadata":{"description":"Job metadata; unknown keys are preserved for plugin/worker use","type":"object","properties":{"software":{"description":"Render host/engine, e.g. blender, maya arnold — drives script routing + pricing","type":"string"},"renderer":{"description":"Fallback engine string when software is unset","type":"string"},"instanceType":{"type":"string"},"renderTimeMinutes":{"type":"number"},"taskRuntimeMinutes":{"type":"number"},"totalSizeGB":{"type":"number"},"resolution":{"description":"WIDTHxHEIGHT (molecular jobs)","type":"string"},"fps":{"description":"Frame rate (molecular jobs)","type":"number"},"renderEngine":{"description":"Molecular render tier: 'molstar' (raster) or 'cycles' (ray-traced)","type":"string","enum":["molstar","cycles"]},"samples":{"description":"Cycles samples per frame (molecular cinematic)","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"additionalProperties":{}},"priority":{"description":"Job priority 0–100; higher values run sooner. Defaults to 50.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"jobName":{"description":"Human-readable label shown in the dashboard and logs","type":"string"},"attachments":{"description":"Additional storage keys for asset files referenced by the scene","type":"array","items":{"type":"string"}}},"required":["jobType"],"additionalProperties":false},"SubmitJobResponse":{"type":"object","properties":{"jobId":{"type":"string","description":"Unique job identifier — use this for status, outputs, and cancel calls"},"jobType":{"type":"string","description":"Echoed job type: render, simulation, molecular, pipeline, or inference"},"estimatedCost":{"type":"number","description":"Estimated cost in the currency field, e.g. 0.42"},"currency":{"type":"string","description":"Three-letter currency code, e.g. USD"},"status":{"type":"string","description":"Initial job status, always SUBMITTED"},"provider":{"type":"string","description":"Cloud provider handling this job, e.g. aws-deadline or azure-batch"},"idempotentReplay":{"description":"True when this response was replayed from a matching Idempotency-Key","type":"boolean"},"message":{"type":"string","description":"Human-readable confirmation message"}},"required":["jobId","jobType","estimatedCost","currency","status","provider","message"],"additionalProperties":false},"EstimateJobRequest":{"type":"object","properties":{"jobType":{"default":"render","description":"Job type to quote — mirrors POST /v1/jobs/submit","type":"string","enum":["render","simulation","molecular","inference"]},"frameStart":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"frameEnd":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"parameters":{"description":"JobBundle-style params, e.g. { \"FrameRange\": \"1-10\" }","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"simulator":{"description":"Allowlisted simulator id (e.g. autodock-vina, blast, alphafold, swan, wavewatch3)","type":"string"},"taskCount":{"description":"Number of independent tasks","type":"integer","minimum":1,"maximum":9007199254740991},"modelId":{"description":"Catalog model id from GET /v1/models (inference estimates only)","type":"string"},"units":{"description":"Number of outputs to generate (inference estimates only)","type":"integer","minimum":1,"maximum":9007199254740991},"params":{"description":"Model-specific generation params (inference estimates only)","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"metadata":{"type":"object","properties":{"software":{"type":"string"},"renderer":{"type":"string"},"totalSizeGB":{"type":"number"},"instanceType":{"type":"string"},"renderTimeMinutes":{"type":"number"},"taskRuntimeMinutes":{"type":"number"},"resolution":{"description":"WIDTHxHEIGHT (molecular estimates)","type":"string"},"fps":{"description":"Frame rate (molecular estimates)","type":"number"},"renderEngine":{"description":"Molecular render tier: 'molstar' (raster) or 'cycles' (ray-traced)","type":"string","enum":["molstar","cycles"]},"samples":{"description":"Cycles samples per frame (molecular cinematic tier)","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"additionalProperties":false}},"required":["jobType"],"additionalProperties":false},"EstimateJobResponse":{"type":"object","properties":{"estimatedCost":{"type":"number"},"currency":{"type":"string"},"hasLicenseFee":{"type":"boolean"},"licenseNames":{"type":"array","items":{"type":"string"}},"breakdown":{"description":"Per-component cost breakdown (compute, software, storage, egress, start fee)","type":"object","properties":{"compute":{"type":"number"},"software":{"type":"number"},"storage":{"type":"number"},"egress":{"type":"number"},"startFee":{"type":"number"}},"required":["compute","software","storage","egress","startFee"],"additionalProperties":false},"variableRates":{"description":"Resolved per-unit rates used in the estimate","type":"object","properties":{"hardwareRate":{"type":"number"},"egressRate":{"type":"number"}},"required":["hardwareRate","egressRate"],"additionalProperties":false}},"required":["estimatedCost","currency"],"additionalProperties":false},"UploadUrlRequest":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"description":"Original filename; used for content-type detection and output naming"},"contentType":{"type":"string","minLength":1,"description":"MIME type of the file, e.g. application/x-blend, model/obj, application/octet-stream"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"File size in bytes; used to pre-validate storage quota"}},"required":["filename","contentType","size"],"additionalProperties":false},"UploadUrlResponse":{"type":"object","properties":{"uploadUrl":{"type":"string","description":"Presigned PUT URL — upload the file directly from the client to this URL, expires in expiresIn seconds"},"s3Key":{"type":"string","description":"Storage key to pass as sceneFile when calling POST /v1/jobs/submit"},"method":{"type":"string","description":"HTTP method for the upload, always PUT"},"expiresIn":{"type":"number","description":"URL expiry in seconds, typically 3600"},"headers":{"description":"Additional headers to include in the PUT request, e.g. Content-Type","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"provider":{"type":"string","description":"Storage provider handling the upload: aws-s3 or azure-blob"}},"required":["uploadUrl","s3Key","method","expiresIn","provider"],"additionalProperties":false},"MultipartInitRequest":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"description":"Original filename; used for output naming"},"contentType":{"type":"string","minLength":1,"description":"MIME type of the final object"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Total object size in bytes"},"partSize":{"description":"Desired part size in bytes; clamped to provider limits (S3 ≥5 MiB)","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"description":"Optional hex SHA-256 of the whole object; verified at completion","type":"string","pattern":"^[a-fA-F0-9]{64}$"},"jobType":{"description":"Optional job-type hint recorded for observability","type":"string"},"checksumAlgorithm":{"description":"Opt in to store-enforced per-part checksums (S3 only). When active, no inline part URLs are returned — mint each via POST /v1/uploads/{uploadId}/parts with that part's base64 SHA-256, which the store verifies against the uploaded bytes.","type":"string","const":"sha256"}},"required":["filename","contentType","size"],"additionalProperties":false},"MultipartInitResponse":{"type":"object","properties":{"uploadId":{"type":"string","description":"Opaque handle to pass to the status/complete/abort endpoints"},"key":{"type":"string","description":"Storage key the parts assemble into (submit as sceneFile)"},"partSize":{"type":"number","description":"Bytes per part (the last part may be smaller)"},"partCount":{"type":"number","description":"Total number of parts to upload"},"parts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"1-based part number (upload order)"},"url":{"type":"string","description":"Presigned PUT URL for this part’s bytes"}},"required":["partNumber","url"],"additionalProperties":false},"description":"First page of presigned part URLs (at most 1,000; empty in checksum mode). URLs for remaining parts — or replacements for expired ones — come from POST /v1/uploads/{uploadId}/parts."},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Headers to send with each part PUT (usually empty)"},"expiresIn":{"type":"number","description":"Seconds until the presigned part URLs expire"},"provider":{"type":"string","description":"Storage provider handling the upload: aws or azure"},"checksumAlgorithm":{"anyOf":[{"type":"string","enum":["sha256"]},{"type":"null"}],"description":"Store-enforced checksum algorithm in effect, or null when unavailable"}},"required":["uploadId","key","partSize","partCount","parts","headers","expiresIn","provider","checksumAlgorithm"],"additionalProperties":false},"MultipartStatusResponse":{"type":"object","properties":{"uploadId":{"type":"string"},"key":{"type":"string"},"partSize":{"type":"number"},"partCount":{"type":"number"},"received":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"number"},"etag":{"type":"string"},"size":{"type":"number"}},"required":["partNumber","etag"],"additionalProperties":false},"description":"Parts already stored"},"missing":{"type":"array","items":{"type":"number"},"description":"Part numbers still to upload"},"complete":{"type":"boolean","description":"True when every part has been received"},"provider":{"type":"string"}},"required":["uploadId","key","partSize","partCount","received","missing","complete","provider"],"additionalProperties":false},"MultipartPartUrlsRequest":{"type":"object","properties":{"parts":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"1-based part number to mint a URL for"},"checksumSha256":{"description":"Base64 of this part's raw SHA-256 digest. Required (per part) when the upload was initiated with checksumAlgorithm sha256; forbidden otherwise.","type":"string","pattern":"^[A-Za-z0-9+/]{43}=$"}},"required":["partNumber"],"additionalProperties":false},"description":"Parts to mint presigned PUT URLs for (also re-mints expired URLs)"}},"required":["parts"],"additionalProperties":false},"MultipartPartUrlsResponse":{"type":"object","properties":{"uploadId":{"type":"string"},"parts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"1-based part number (upload order)"},"url":{"type":"string","description":"Presigned PUT URL for this part’s bytes"},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Headers this specific part PUT must carry (e.g. the signed checksum)"}},"required":["partNumber","url","headers"],"additionalProperties":false},"description":"Minted part URLs"},"partSize":{"type":"number"},"partCount":{"type":"number"},"expiresIn":{"type":"number","description":"Seconds until the minted URLs expire"},"provider":{"type":"string"}},"required":["uploadId","parts","partSize","partCount","expiresIn","provider"],"additionalProperties":false},"MultipartCompleteRequest":{"type":"object","properties":{"parts":{"minItems":1,"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"etag":{"description":"ETag the part PUT returned (S3); ignored by Azure","type":"string"},"checksumSha256":{"description":"Base64 of this part's raw SHA-256. Required per part for checksum-mode uploads; the store validates each value against the checksum recorded at part upload.","type":"string","pattern":"^[A-Za-z0-9+/]{43}=$"}},"required":["partNumber"],"additionalProperties":false},"description":"The parts the client finished uploading"},"sha256":{"description":"Optional hex SHA-256 of the whole object","type":"string","pattern":"^[a-fA-F0-9]{64}$"}},"required":["parts"],"additionalProperties":false},"MultipartCompleteResponse":{"type":"object","properties":{"key":{"type":"string","description":"Verified storage key; submit as sceneFile to POST /v1/jobs/submit"},"size":{"type":"number","description":"Final assembled object size in bytes"},"etag":{"description":"Final object entity tag","type":"string"},"verified":{"type":"boolean","description":"Always true on success (passed the integrity gate)"},"integrity":{"type":"string","enum":["parts-sha256","size"],"description":"'parts-sha256': the store cryptographically verified every part's bytes (checksum mode). 'size': byte-count integrity gate only."},"provider":{"type":"string"}},"required":["key","size","verified","integrity","provider"],"additionalProperties":false},"CreateApiKeyRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable label for the key, shown in the dashboard"},"environment":{"default":"live","description":"live keys can call billing endpoints and submit real jobs; test keys cannot trigger charges","type":"string","enum":["live","test"]},"scopes":{"minItems":1,"type":"array","items":{"type":"string","enum":["jobs:read","jobs:write","credits:read","convert"]},"description":"Permissions granted to this key: jobs:read, jobs:write, credits:read"}},"required":["name","environment","scopes"],"additionalProperties":false},"CreditBalanceResponse":{"type":"object","properties":{"total_credited_cents":{"type":"number","description":"Lifetime credits loaded, in USD cents"},"held_cents":{"type":"number","description":"Credits currently reserved for in-flight jobs"},"available_cents":{"type":"number","description":"Credits available to spend (total_credited_cents minus held_cents)"},"available_display":{"type":"string","description":"Human-readable available balance, e.g. \"$12.34\""}},"required":["total_credited_cents","held_cents","available_cents","available_display"],"additionalProperties":false},"CreditTopupRequest":{"type":"object","properties":{"tierId":{"type":"string","enum":["topup-10","topup-50","topup-200","topup-500"],"description":"Top-up tier id; the amount and any bonus credits are resolved server-side"},"successUrl":{"description":"Where Stripe redirects after a successful payment; defaults to /dashboard?topup=success","type":"string"},"cancelUrl":{"description":"Where Stripe redirects if the payment is cancelled; defaults to /dashboard?topup=cancelled","type":"string"}},"required":["tierId"],"additionalProperties":false},"CreditTopupResponse":{"type":"object","properties":{"url":{"type":"string","description":"Stripe-hosted Checkout URL — redirect the browser here to complete payment"},"tierId":{"type":"string","description":"Echoed tier id that was purchased"}},"required":["url","tierId"],"additionalProperties":false},"PublicEstimateRequest":{"type":"object","properties":{"mode":{"default":"animation","description":"animation prices a frame range; still prices a fixed image count","type":"string","enum":["animation","still"]},"jobType":{"default":"render","description":"render prices frames/stills; simulation prices per-task node hours","type":"string","enum":["render","simulation"]},"durationMinutes":{"default":0,"description":"Animation length, minutes (animation mode)","type":"number"},"durationSeconds":{"default":0,"description":"Animation length, seconds (animation mode)","type":"number"},"fps":{"default":24,"description":"Frames per second used to derive frame count (animation mode)","type":"number"},"imageCount":{"default":1,"description":"Number of stills to render (still mode)","type":"number"},"renderTimeMinutes":{"default":10,"description":"Estimated render time per frame, minutes","type":"number"},"hardware":{"default":"g4dn.xlarge","description":"Instance type, e.g. g4dn.xlarge","type":"string"},"software":{"default":"blender","description":"Renderer/software, e.g. blender, maya, nuke","type":"string"},"region":{"default":"us-west-2","description":"Cloud region used to resolve per-unit rates","type":"string"},"datasetSizeTB":{"default":1,"description":"Input dataset size in TB; drives storage and egress cost","type":"number"}},"required":["mode","jobType","durationMinutes","durationSeconds","fps","imageCount","renderTimeMinutes","hardware","software","region","datasetSizeTB"],"additionalProperties":false},"PublicEstimateResponse":{"type":"object","properties":{"estimatedCost":{"type":"number","description":"Total estimated cost in the currency field"},"estimatedCostGbp":{"type":"number","description":"GBP equivalent at the live FX rate — what a credit-funded job is billed"},"currency":{"type":"string","description":"Three-letter currency code for estimatedCost, e.g. USD"},"creditCurrency":{"type":"string","description":"Currency credits are billed in (GBP)"},"fxRate":{"type":"number","description":"Live USD→GBP rate applied to derive estimatedCostGbp"},"hasLicenseFee":{"description":"True when the chosen software carries a per-use license fee","type":"boolean"},"licenseNames":{"description":"Names of any licensed software included in the price","type":"array","items":{"type":"string"}},"breakdown":{"description":"Per-component cost breakdown","type":"object","properties":{"compute":{"type":"number"},"software":{"type":"number"},"storage":{"type":"number"},"egress":{"type":"number"},"startFee":{"type":"number"}},"required":["compute","software","storage","egress","startFee"],"additionalProperties":false},"meta":{"description":"Derived inputs (frame count, dataset size) and the resolved per-unit rates","type":"object","properties":{"totalFrames":{"type":"number"},"totalSizeGB":{"type":"number"},"variableRates":{"type":"object","properties":{"hardwareRate":{"type":"number"},"egressRate":{"type":"number"}},"required":["hardwareRate","egressRate"],"additionalProperties":false}},"required":["totalFrames","totalSizeGB"],"additionalProperties":false}},"required":["estimatedCost","estimatedCostGbp","currency","creditCurrency","fxRate"],"additionalProperties":false},"LicenseServerConfig":{"type":"object","properties":{"default":{"description":"Fallback license server for software without a specific entry","type":"string"},"maya":{"description":"Autodesk Maya license server, e.g. 27000@licenses.example.com","type":"string"},"nuke":{"description":"Foundry Nuke license server","type":"string"},"houdini":{"description":"SideFX Houdini license server","type":"string"},"vray":{"description":"Chaos V-Ray license server","type":"string"},"redshift":{"description":"Maxon Redshift license server (RLM port@host)","type":"string"}},"additionalProperties":false},"LicenseServerConfigResponse":{"type":"object","properties":{"licenseServerConfig":{"type":"object","properties":{"default":{"description":"Fallback license server for software without a specific entry","type":"string"},"maya":{"description":"Autodesk Maya license server, e.g. 27000@licenses.example.com","type":"string"},"nuke":{"description":"Foundry Nuke license server","type":"string"},"houdini":{"description":"SideFX Houdini license server","type":"string"},"vray":{"description":"Chaos V-Ray license server","type":"string"},"redshift":{"description":"Maxon Redshift license server (RLM port@host)","type":"string"}},"additionalProperties":false,"description":"The stored license server configuration"},"message":{"description":"Confirmation message (present on PUT)","type":"string"}},"required":["licenseServerConfig"],"additionalProperties":false}}},"paths":{"/jobs/estimate":{"post":{"operationId":"estimateJob","tags":["Jobs"],"summary":"Estimate the cost of a render or simulation job before submitting","security":[{"ApiKeyAuth":["jobs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstimateJobRequest"}}}},"responses":{"200":{"description":"Estimated cost","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstimateJobResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Active subscription or payment method required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key missing the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/generations/report":{"post":{"operationId":"reportGeneration","tags":["Jobs"],"summary":"Report a generated output for human review (abuse / takedown queue)","description":"Files an auditable report against a generation job on the self-host AI tier. Complements the automated input gate and the worker output classifier with a report → review → action trail; operators work the queue (reads are service-role only).","security":[{"ApiKeyAuth":["jobs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jobId"],"properties":{"jobId":{"type":"string","description":"The generation job being reported."},"category":{"type":"string","enum":["csam","ncii","violence","ip_infringement","other"],"default":"other"},"reason":{"type":"string","maxLength":2000}}}}}},"responses":{"201":{"description":"Report received and queued for review","content":{"application/json":{"schema":{"type":"object","properties":{"reportId":{"type":"string"},"status":{"type":"string"},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key missing the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/upload-url":{"post":{"operationId":"createUploadUrl","tags":["Jobs"],"summary":"Presign an upload URL for a scene / input file","security":[{"ApiKeyAuth":["jobs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadUrlRequest"}}}},"responses":{"200":{"description":"Presigned upload URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadUrlResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Active subscription or payment method required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key missing the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads":{"post":{"operationId":"initMultipartUpload","tags":["Jobs"],"summary":"Initiate a resumable multipart upload","description":"Presigns up to 1,000 part PUT URLs inline (none in checksum mode — see POST /uploads/{uploadId}/parts). Upload parts directly to storage (in parallel, resumable), then call POST /uploads/{uploadId}/complete. Bytes never transit the API, so this scales to multi-GB datasets (no 25 MB-class body ceiling).","security":[{"ApiKeyAuth":["jobs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartInitRequest"}}}},"responses":{"201":{"description":"Multipart upload initiated; presigned part URLs returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartInitResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key missing the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Declared size exceeds the maximum","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Content type not permitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads/{uploadId}":{"get":{"operationId":"getMultipartUploadStatus","tags":["Jobs"],"summary":"List received parts and the gaps (resume)","security":[{"ApiKeyAuth":["jobs:read"]}],"parameters":[{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Upload status with received parts and missing part numbers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartStatusResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"abortMultipartUpload","tags":["Jobs"],"summary":"Abort an in-progress multipart upload","security":[{"ApiKeyAuth":["jobs:write"]}],"parameters":[{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Upload aborted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads/{uploadId}/parts":{"post":{"operationId":"mintMultipartPartUrls","tags":["Jobs"],"summary":"Mint (or re-mint) presigned URLs for specific parts","description":"Pages part URLs beyond the 1,000 returned inline by init, replaces expired URLs mid-upload, and — for uploads initiated with checksumAlgorithm sha256 — signs each URL with that part's base64 SHA-256 so the store rejects corrupted bytes.","security":[{"ApiKeyAuth":["jobs:write"]}],"parameters":[{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartPartUrlsRequest"}}}},"responses":{"200":{"description":"Presigned part URLs minted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartPartUrlsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads/{uploadId}/complete":{"post":{"operationId":"completeMultipartUpload","tags":["Jobs"],"summary":"Assemble parts into the final object (integrity gate)","security":[{"ApiKeyAuth":["jobs:write"]}],"parameters":[{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartCompleteRequest"}}}},"responses":{"200":{"description":"Object assembled and verified; key is submittable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartCompleteResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Assembled object exceeds the maximum","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Integrity gate failed (empty or size mismatch)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/submit":{"post":{"operationId":"submitJob","tags":["Jobs"],"summary":"Submit a render or simulation job","security":[{"ApiKeyAuth":["jobs:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Optional client key; retries replay the original job."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitJobRequest"}}}},"responses":{"200":{"description":"Job submitted (or idempotent replay)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitJobResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Insufficient credits / no payment method","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Estimated cost exceeds the allowed maximum","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs":{"get":{"operationId":"listJobs","tags":["Jobs"],"summary":"List the caller's jobs","security":[{"ApiKeyAuth":["jobs:read"]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":25}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of jobs"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/models":{"get":{"operationId":"listModels","tags":["Jobs"],"summary":"List self-host AI generation models available to the caller","description":"Returns the curated open-weight models KinoCloud self-hosts (not a resale of any aggregator). Every entry has passed the fail-closed license gate and carries its license + commercialUse so a client can warn before generating a paid deliverable. Submit a generation via POST /jobs/submit with jobType:\"inference\".","security":[{"ApiKeyAuth":["jobs:read"]}],"parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["text-to-image","text-to-video","image-to-video","image-to-3d","text-to-3d"]},"description":"Filter to one generation category."}],"responses":{"200":{"description":"The catalog of sellable models"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/{id}/status":{"get":{"operationId":"getJobStatus","tags":["Jobs"],"summary":"Get a job's status","security":[{"ApiKeyAuth":["jobs:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job status"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/{id}/status/stream":{"get":{"operationId":"streamJobStatus","tags":["Jobs"],"summary":"Stream a job's status with Server-Sent Events","description":"Opens a text/event-stream channel that emits ready, status, error, and done events. Use this for live dashboards; use GET /jobs/{id}/status for polling clients.","security":[{"ApiKeyAuth":["jobs:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Server-Sent Events stream of job status updates","content":{"text/event-stream":{"schema":{"type":"string","description":"SSE frames containing JSON event payloads."}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key missing the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/{id}/outputs":{"get":{"operationId":"getJobOutputs","tags":["Jobs"],"summary":"Get presigned download URLs for a completed job's outputs","security":[{"ApiKeyAuth":["jobs:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Output files (may be empty if still running)"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Active subscription or payment method required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/{id}/cancel":{"post":{"operationId":"cancelJob","tags":["Jobs"],"summary":"Cancel a job","security":[{"ApiKeyAuth":["jobs:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job cancelled"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/jobs/{id}/resume":{"post":{"operationId":"resumeJob","tags":["Jobs"],"summary":"Resume a paused job (experimental)","description":"EXPERIMENTAL: checkpoint/resume relies on spot→receiver GPU state migration that is proven in R&D but not yet wired into the production fleet. The endpoint contract is stable, but calls may fail until the capability ships — do not build critical flows on it yet.","security":[{"ApiKeyAuth":["jobs:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job resumed"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/credits/balance":{"get":{"operationId":"getCreditBalance","tags":["Credits"],"summary":"Get the caller's credit balance","security":[{"ApiKeyAuth":["credits:read"]}],"responses":{"200":{"description":"Credit balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditBalanceResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/credits/transactions":{"get":{"operationId":"listCreditTransactions","tags":["Credits"],"summary":"List credit ledger transactions","security":[{"ApiKeyAuth":["credits:read"]}],"responses":{"200":{"description":"A page of transactions"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/credits/topup":{"post":{"operationId":"createCreditTopup","tags":["Credits"],"summary":"Start a credit top-up (session only) and get a Stripe Checkout URL","description":"Creates a one-time-payment Stripe Checkout session for a credit top-up tier. Managed from the browser, so it uses the cookie session — API keys cannot call it. Charged in GBP; balances are reported in USD cents (see the \"Currency\" note in the docs).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditTopupRequest"}}}},"responses":{"200":{"description":"Checkout session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditTopupResponse"}}}},"400":{"description":"Unknown or missing tier id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/credits/card-bonus":{"get":{"operationId":"getCardBonusEligibility","tags":["Credits"],"summary":"Check card-on-file bonus eligibility (session only)","description":"Whether the signed-in user can still claim the card-on-file promo bonus, and its current value (operator-configured in promo_config — PRD_CREDIT_EXPOSURE.md).","responses":{"200":{"description":"Eligibility and bonus amount"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createCardBonusSetupSession","tags":["Credits"],"summary":"Start a card-on-file setup (no charge) and get a Stripe Checkout URL","description":"Creates a mode=setup Stripe Checkout session that saves a card without charging it. On completion the webhook grants the card-on-file bonus — once per user and once per physical card. Managed from the browser, so it uses the cookie session — API keys cannot call it.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"successUrl":{"type":"string","description":"Same-origin path to return to on success"},"cancelUrl":{"type":"string","description":"Same-origin path to return to on cancel"}}}}}},"responses":{"200":{"description":"Checkout session created (url)"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Bonus already claimed or promo disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/keys":{"get":{"operationId":"listApiKeys","tags":["API Keys"],"summary":"List the caller's API keys (session only)","responses":{"200":{"description":"API keys"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createApiKey","tags":["API Keys"],"summary":"Create an API key (session only)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}}},"responses":{"201":{"description":"Created; the plaintext key is returned once"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/keys/{id}":{"delete":{"operationId":"revokeApiKey","tags":["API Keys"],"summary":"Revoke an API key (session only)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found or already revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/public/estimate":{"post":{"operationId":"publicEstimate","tags":["Public"],"summary":"Estimate render cost without authentication (marketing calculator)","description":"Unauthenticated pricing estimate used by the public site. Rate limited to 60 requests/min per IP. All body fields are optional and fall back to documented defaults.","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEstimateRequest"}}}},"responses":{"200":{"description":"Estimated cost","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEstimateResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/settings/license-servers":{"get":{"operationId":"getLicenseServers","tags":["Settings"],"summary":"Read BYO-license server configuration (session only)","responses":{"200":{"description":"Current license server configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenseServerConfigResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"operationId":"updateLicenseServers","tags":["Settings"],"summary":"Update BYO-license server addresses (session only)","description":"Stores floating-license server addresses applied to new render jobs. Requires an active Azure Marketplace subscription.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenseServerConfig"}}}},"responses":{"200":{"description":"Configuration saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenseServerConfigResponse"}}}},"400":{"description":"Invalid license server address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No active marketplace subscription","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}