{"openapi":"3.1.0","info":{"title":"CraterView.ai API","description":"\nCraterView image enhancement and restoration API.\n\n## How a job works\n\nThree calls — upload to a presigned URL, run a job, and download the result.\n\n1. `POST /v1/uploads` — get a presigned URL and an `input_key`\n2. `PUT` your bytes to that URL\n3. `POST /v1/jobs` — submit `input_key` plus parameters\n\nInference is asynchronous, because a job can occupy a GPU for minutes. Add `?wait=30` to\nstep 3 and a fast job comes back finished in the same response; otherwise poll\n`GET /v1/jobs/{id}` or supply a `webhook_url`.\n\n## Collecting the result\n\nA finished job carries two links to the same object under `result.output`, both presigned\nand both expiring. `url` serves it inline, for displaying a result in a page.\n`download_url` carries `Content-Disposition: attachment`, so a browser saves it under a\nfilename instead. Pick by what you are doing with it; a client cannot convert one into the\nother, because the disposition is signed into the URL.\n\n## Output format\n\nBy default the result comes back in the format you sent — a JPEG upload returns a JPEG —\nexcept that a HEIC or HEIF photograph prefers JPEG and a TIFF scan comes back as PNG: the\nplatform reads those formats and does not write them. `output_format` names another. Either is a preference: kept wherever that format holds the\npicture, and PNG where it does not — an alpha channel asked for as JPEG, more than 8 bits asked\nfor as JPEG or WebP, a result wider than the format holds. `result.output.content_type`\nsays which format came back.\n\n## When a job fails\n\nA failed job carries two fields. `error` is a sentence meant for a person and is reworded\nwhenever it can be made clearer, so show it rather than match on it. `error_code` is the\nstable half, and it is what to branch on.\n\n**A job that produces no output is not charged**, whoever's fault it was, so a failed job\nalways leaves your balance where it started. What the code tells you is whether retrying is\nworth anything. `inference_failed` means the fault was ours and the same request is worth\nsending again. Every other code refuses the request as you sent it, so an identical retry\nfails identically — change something or tell the person who made it. A job that succeeded\nwith a result that is *wrong* is not a failure the API can see; report it with the job's\n`id` (see *Reporting a problem*).\n\nThis list is not closed. New codes appear as new things become worth telling apart, so treat\nan unfamiliar one as a failure you have no special handling for rather than as an error in\nitself; a client that rejects a code it does not know breaks on the next one added.\n\n| `error_code` | What it means |\n| --- | --- |\n| `inference_failed` | Something on our side failed. Nothing was charged — a job is charged only when it succeeds — and the same request is worth sending again. |\n| `unreadable_image` | The file is not an image the service can read: it may be damaged, in a format the service does not take, or not an image at all. Re-save or re-export it as PNG or JPEG; the same request will not succeed as it stands. |\n| `undecodable_image` | The file starts as an image but its pixels cannot be read to the end: it is damaged, or was cut short, for instance by an upload that did not finish. Re-save or re-export it; the same request will not succeed as it stands. |\n| `unsupported_tiff` | The TIFF stores its pixels in a form the service does not read: floating-point, signed or 32-bit samples; 16-bit CMYK, Lab or white-is-zero greyscale; colour planes stored separately; premultiplied transparency; or a compression other than LZW, ZIP or PackBits at 16 bits. Re-save it as 8- or 16-bit RGB or greyscale; the same request will not succeed as it stands. |\n| `format_not_accepted` | The file is in a format the model does not take, whatever type it was uploaded as. The model's `accepts` lists the formats it takes; convert the file to one of them. The same request will not succeed as it stands. |\n| `no_face_found` | The model frames a headshot around a face and found none. A face turned well away from the camera, very small in the frame or mostly covered is not found; the same request will not succeed as it stands. |\n| `no_small_face_found` | The model redraws faces too small to show clearly and found none. A face large enough to show clearly is left as it is, and a face turned well away from the camera or mostly covered is not found; the same request will not succeed as it stands. |\n| `input_expired` | Uploads are kept for a limited time, and this one was removed before the job reached a worker — which takes a queue longer than that time, so it is rare. Upload the file again and submit a new job; the same request will not succeed as it stands, because the file it names is gone. |\n| `frame_too_large` | A frame is larger than the megapixel ceiling that applies to your key. The ceiling is `max_frame_megapixels` on the model. |\n| `result_too_large` | The image and the enlargement asked for would produce a frame larger than the ceiling that applies to your key. The ceiling is `max_output_megapixels` on the model, and the result is the frame you send times the enlargement squared. |\n| `result_size_out_of_range` | A `width` or `height` that would enlarge the image you sent, or its region, by more than the model's largest enlargement or less than its smallest. The range is the one the model publishes for `scale` in its `params_schema`. |\n| `input_too_large` | The image you sent, or the region of it you named, is larger than the ceiling that applies to your key. The ceiling is `max_input_megapixels` on the model, and naming a region within it is how part of a larger image is worked on. |\n| `video_coming_soon` | video for this model is coming soon |\n| `video_not_accepted` | this model does not accept video |\n| `asked_to_fail` | this job asked to fail with the `fail` parameter, and it did. Remove `fail` to have it succeed. |\n\n## Webhooks\n\nSupply a `webhook_url` on submit and we `POST` to it once the job settles, whether it\nsucceeded or failed. The body is the `WebhookEvent` schema: it states the same facts a read\nof the job states, by the same names, and a field that does not apply is null rather than\nabsent so the shape is identical on every delivery. That includes the links to the file,\nunder `result.output` — and, like every link the API hands out, they are signed and expire,\nso fetch the file rather than storing the address. A read of the job returns fresh ones.\n\nA delivery may arrive more than once. Every copy states what the job's record states, so\ntwo copies disagree in nothing but those signed links, which are minted per delivery.\nDedupe on `id`.\n\nEvery delivery carries `CV-Timestamp` and `CV-Signature`. Get your signing secret from\n`GET /v1/webhooks/secret` — it is created the first time you ask, and unlike an API key it\ncan be read back whenever you need it.\n\nTo verify: compute the hex HMAC-SHA256 of `<CV-Timestamp>.<body>` under that secret, over\nthe bytes exactly as received and before any parsing, and look for `sha256=<that value>`\namong the comma-separated entries in `CV-Signature`. Accept the delivery if any entry\nmatches.\n\n`CV-Signature` is a list because rotating must not break you. `POST /v1/webhooks/secret/rotate`\nissues a new secret and keeps the previous one for 24 hours, during which every delivery is\nsigned under both — so you deploy the new secret when it suits you rather than the moment\nyou asked for it.\n\nAlso reject a delivery whose `CV-Timestamp` is old; five minutes is a good bound. The\ntimestamp is inside the signed material precisely so that a delivery someone captured cannot\nbe replayed at you later, and that only holds if you check it.\n\n## Retries\n\nSend an `Idempotency-Key` header on submit. A repeat with the same key returns the\noriginal job rather than running inference again; reusing a key with a different body is\na `409`.\n\n## Limits\n\nAn account may have up to 5 jobs queued or running at once, or 2\nas a guest, with nobody signed in to it (`is_guest` on `GET /v1/usage`). The figure\nis the same on the community queue and on paid compute: paying moves your work onto compute\nthat scales with demand, and does not change how much of it may wait at once. A submit past\nit is refused with a `429` and `Retry-After`, and a slot frees as each of your jobs finishes.\n`max_jobs_in_flight` on `GET /v1/usage` is the figure that applies to your account now, so\nread it there rather than copying it from here.\n\nIf you need more at once, write to sales@craterview.ai and tell us what you are building and\nthe volume you expect.\n\n## Authentication\n\n`Authorization: Bearer cv_...`. Rate limits are set per key; the credit balance and the\nnumber of jobs you may have queued or running at once belong to the account behind it. See\n`GET /v1/usage` for what you have spent and what you have in flight.\n\nAn account may hold several keys, and one per service or per environment is the ordinary\narrangement — revoking one then leaves the others working. Manage them with `GET /v1/keys`,\n`POST /v1/keys` and `DELETE /v1/keys/{id}`. A key is returned in full only by the call that\ncreates it, so rotating without downtime is: create the new one, move your clients across,\nrevoke the old one.\n\n## Reporting a problem\n\nWrite to support@craterview.ai. Quote the job's `id` for anything about a job — a result\nthat looks wrong, one that never settled, a charge you cannot place — and we can read the\njob, its charge and its run without you having to describe it. For a request that produced\nno job (a refusal, an upload that failed, a 5xx), quote the `X-Trace-Id` every response\ncarries instead; it finds the request in our logs. If you already send a `traceparent`\nheader, we use its trace id as ours, so the two sides line up.\n","version":"0.1.0"},"servers":[{"url":"https://api.craterview.ai"}],"paths":{"/v1/models":{"get":{"tags":["models"],"summary":"List Models","description":"Every available model: what it takes, what it answers with, prices, limits, and your queue.\n\nThe one route that answers without a key. Called with none, it answers as it does for an\naccount that has not paid: `community` true and the community queue's limits.\n\n**`community` and the limits are about the queue your key would use**, not about the\nplatform as a whole. Jobs are routed at submit by what the account has paid for: an\naccount that has paid runs on paid compute, which scales with demand, and one that has\nnot goes to the community queue, which runs on shared, free\ncapacity and can wait longer at busy times. So this endpoint looks up where your account stands\nand answers for the queue you would land in — `community` tells you which that is, and\nthe limits are the ones that apply there.\n\nHow long a wait will be is answered on the job, once you have one: `eta_seconds` on\nevery read of it. `community` means the same thing there as here. Paying moves you\nbetween queues, so it can change for reasons other than other people's jobs.\n\nA model that is available is not always listed here — a model in trial, or being\nretired, stays usable by name while absent from this catalog.\n\nThe reverse also happens: a model with `coming_soon` set is listed before it can be\nused, and submitting to it is refused with 409 until it launches. Everything else\npublished about it is final, so an integration can be written against it in advance.","operationId":"list_models_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ModelInfo"},"title":"Response List Models V1 Models Get"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{},{"bearerAuth":[]}]}},"/v1/uploads":{"post":{"tags":["jobs"],"summary":"Create Upload","description":"Presigned PUT, so your image bytes go straight to object storage and never through\nthis API.\n\nTwo limits apply. Your account may mint a bounded number of upload URLs a minute, and each\nURL accepts one exact size: `content_length` is signed into it, so the upload must be\nexactly that many bytes. A length beyond the platform's maximum is refused here rather than\nat the PUT, which means an oversized file costs you a rejected request instead of a stored\nobject you have to clean up.\n\nThe size limit is the same for every model — this endpoint is never told which model the\nupload is for.","operationId":"create_upload_v1_uploads_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"415":{"description":"no model on this platform accepts the declared content type"},"413":{"description":"the declared length exceeds the platform's upload limit; or the request body is larger than 1 MiB. The request was not read"},"429":{"description":"this account has minted as many upload URLs as it may this minute","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/jobs":{"post":{"tags":["jobs"],"summary":"Create Job","description":"Queue an inference job.\n\nAnswers 202 with the job, which by default is still queued: enhancement runs on the\nfleet rather than in this request, and a large job can take minutes. There are three ways\nto collect the result, and the first is usually the one you want:\n\n- Set `wait` and this call holds the response open for that many seconds, up to 30. A job\n  that finishes inside the window comes back finished, with its output, and needs no\n  second call at all. One that does not comes back queued or running, exactly as if you\n  had not asked.\n- Poll GET /v1/jobs/{id}.\n- Supply a `webhook_url` and let the result come to you.\n\nThe status code is 202 either way, including when the job is already finished: it says the\nsubmission was accepted, not that the work is still outstanding. Read `status` for that.\n\nSend an `Idempotency-Key` header to make retries safe: a repeat with the same key\nreturns the original job instead of running inference again.\n\nSend a `custom_id` to have your own identifier come back on every read of this job and\non the webhook, so a result can be matched to your record without keeping a table of\nours. It is a label: not interpreted, not required to be unique, and not a key jobs can\nbe looked up by. It is not the `Idempotency-Key` — that one decides whether a job is\ncreated at all.\n\nSend `input_megapixels` — the size of the file you uploaded — and `eta_seconds` is\nestimated for your file rather than for a typical one. It is advisory: the price, the\nqueue and the size limits are decided from the file itself.","operationId":"create_job_v1_jobs_post","parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"number","maximum":30.0,"minimum":0,"description":"Seconds to hold the response open waiting for the job to finish. A fast job then needs no polling at all; if it is still running when the time is up you get the queued/running job back as usual.","default":0,"title":"Wait"},"description":"Seconds to hold the response open waiting for the job to finish. A fast job then needs no polling at all; if it is still running when the time is up you get the queued/running job back as usual."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255,"pattern":"^[ -~]*$","description":"Makes a retry safe: a later request with the same key and the same body answers with the job the first one made, instead of making another; the same key with a different body is refused. Any value of your own, up to 255 printable ASCII characters — a UUID is a good choice. Keys are your API key's own, so another caller's cannot collide with yours.","default":"","title":"Idempotency-Key"},"description":"Makes a retry safe: a later request with the same key and the same body answers with the job the first one made, instead of making another; the same key with a different body is refused. Any value of your own, up to 255 printable ASCII characters — a UUID is a good choice. Keys are your API key's own, so another caller's cannot collide with yours."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobSubmission"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"400":{"description":"input_key was never uploaded; or the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"404":{"description":"unknown model"},"409":{"description":"Idempotency-Key reused with a different request body; or the model is in the catalog but not yet in service, per its `coming_soon` flag"},"415":{"description":"the declared content type is not in the model's `accepts`"},"422":{"description":"params did not validate against the model's schema, or webhook_url is not an absolute http(s) URL with a valid host and port"},"503":{"description":"storage could not be reached to find the upload; nothing was queued or charged, and the same request is worth sending again"},"429":{"description":"the calling key's request rate is exceeded; the account already has as many jobs queued or running as it may; or the queue this job would join is full and taking no work for the moment. Nothing sent was wrong in any of the three.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]},"get":{"tags":["jobs"],"summary":"List Jobs","description":"Your jobs, most recent first.\n\nServed from the durable record, so history outlives the live job state.","operationId":"list_jobs_v1_jobs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return jobs older than this job id.","title":"Before"},"description":"Return jobs older than this job id."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/JobStatus"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/jobs/{job_id}":{"get":{"tags":["jobs"],"summary":"Get Job","description":"The whole job in one read: status, the charge and the result.\n\nA job belonging to another account answers 404 rather than 403. Confirming that someone\nelse's job exists would itself be a disclosure, so the two cases are deliberately\nindistinguishable from outside.","operationId":"get_job_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"}}}},"404":{"description":"unknown job, or not yours"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/jobs/{job_id}/keep":{"post":{"tags":["jobs"],"summary":"Keep","description":"Retain this job's images past the ordinary expiry, because the owner asked.\n\nOpt-in and after the fact: the decision a person makes is \"keep *this* result\", and they\ncan only make it once they have seen it. A kept copy is not deleted on the ordinary\nschedule; it stays until the owner removes it.\n\nIdempotent. Asking twice returns the copies already made rather than making a second\npair, so a double-clicked button costs nothing.","operationId":"keep_v1_jobs__job_id__keep_post","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/KeepResponse"}}}},"404":{"description":"no finished job of yours with that id, or a job with no image to keep"},"503":{"description":"storage was briefly unavailable; nothing was kept, and the request may be retried"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["jobs"],"summary":"Forget","description":"Delete the retained copies and return the result to the ordinary schedule.\n\nNot reversible: these are the copies, and the originals they were taken from have their\nown expiry. Safe to repeat: a job of yours that holds no kept copy — never kept, or\nalready forgotten — answers 204 as a kept one does. A job you shared leaves the gallery\nwith its copies: the gallery showed them.","operationId":"forget_v1_jobs__job_id__keep_delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"no job of yours with that id"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/jobs/{job_id}/media":{"delete":{"tags":["jobs"],"summary":"Delete Media","description":"Delete this job's pictures now — the copy of your image the job worked from, the\nresult, its preview, and any kept copy — rather than waiting for them to expire on\nschedule. A job you shared leaves the gallery with them. The file you uploaded is not\npart of the job: it is deleted on its own, shortly after the upload.\n\nThe job itself stays in your history, with what it cost and when it ran, but with no\npictures: reading it afterwards gives no links, exactly as an expired job does. Not\nreversible, and safe to repeat: a second call, or a call on a job whose pictures have\nalready gone, is 204 as the first was. A job that is still queued or running cannot be\ndeleted until it settles.","operationId":"delete_media_v1_jobs__job_id__media_delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"no job of yours with that id"},"409":{"description":"the job is still queued or running"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/usage":{"get":{"tags":["usage"],"summary":"Get Usage","description":"The account's credit balance, and what it has spent, per model.\n\nAccount-wide rather than per key, because that is what the balance belongs to.\nA job is charged when it succeeds, so work still queued or running is not in these\nfigures yet, and a job that fails costs nothing. The balance here is the one your next\nsubmission is judged by.","operationId":"get_usage_v1_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/keys":{"get":{"tags":["keys"],"summary":"List Keys","description":"The keys on your account, newest first, a page at a time — revoked ones included.\n\nNo usable key is ever in this response — only the prefix, which tells one from another\nwithout authorizing anything. A key is returned in full exactly once, by the call that\ncreated it.\n\nRevoked keys stay listed on purpose: when a client starts getting 401s, the useful thing\nto see is that its key was revoked and when. For the key you are calling with, ask\n`GET /v1/keys/current` rather than paging for it.","operationId":"list_keys_v1_keys_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return keys older than this key id.","title":"Before"},"description":"Return keys older than this key id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]},"post":{"tags":["keys"],"summary":"Mint Key","description":"Create another key on your account.\n\n**The value is in this response and nowhere else.** Keys are stored as hashes, so a key\nthat is not written down is a key that has to be replaced rather than recovered.\n\nEvery key on an account shares that account's credits, job history and job ceilings, and\ncarries its own request rate limit. Several is the ordinary arrangement: one per service\nor per environment, so that revoking one does not stop the others.\n\nThis is also how you rotate without downtime — create the new key, move your clients onto\nit, then revoke the old one. There is no window in which nothing works, which is why\nthere is no single \"rotate\" call.\n\n**An account is given a limited number of new keys in any hour**, whichever of its keys\nasks; past it this is refused with 429 and `Retry-After`. Revoking is never limited. Each\nrequest also counts against the calling key's own request rate, as a submission does.","operationId":"mint_key_v1_keys_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewKey"}}}},"404":{"description":"the key you are calling with no longer reaches an account"},"429":{"description":"the calling key's request rate is exceeded, or the account has been given as many new keys this hour as it may","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"502":{"description":"the request could not be applied; ask again"},"503":{"description":"key management is briefly unavailable; retry the request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/keys/current":{"get":{"tags":["keys"],"summary":"Current Key","description":"The key this request is made with, as the listing describes it.\n\nFor finding your own key's id and name without paging the listing for it — to label a\nreplacement the same, or to know which id not to revoke.","operationId":"current_key_v1_keys_current_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyInfo"}}}},"404":{"description":"the key you are calling with no longer reaches an account"},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["keys"],"summary":"Revoke Current Key","description":"Stop the key this request is made with. Immediate, and the last thing it will do.\n\nFor a client signing out of a device that holds nothing but its own key. Revoking a key\nby its id refuses the one you are calling with, so that rotating can never leave you\nwith none; this is that refusal lifted because you asked for exactly this.\n\nIt is refused while this is the account's only key and no sign-in is attached, because\nnothing could reach the account afterwards. Create another key first, or sign in.","operationId":"revoke_current_key_v1_keys_current_delete","responses":{"204":{"description":"Successful Response"},"404":{"description":"the key you are calling with no longer reaches an account"},"409":{"description":"this key is the account's only way back in"},"502":{"description":"the request could not be applied; ask again"},"503":{"description":"key management is briefly unavailable; retry the request"},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/keys/{key_id}":{"delete":{"tags":["keys"],"summary":"Revoke Key","description":"Stop a key working. Immediate, and not reversible — mint a new one instead.\n\nYou cannot revoke the key you are calling with by its id: the request would succeed and\nleave you unable to make another. Create a replacement first, move your clients to it,\nthen revoke this one — or, to end the key you hold on purpose, `DELETE /v1/keys/current`.\n\nJobs already submitted with the revoked key are unaffected. They belong to the account,\nand so does their history.","operationId":"revoke_key_v1_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"no active key of yours with that id"},"409":{"description":"that is the key you are calling with"},"502":{"description":"the request could not be applied; ask again"},"503":{"description":"key management is briefly unavailable; retry the request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/account":{"delete":{"tags":["keys"],"summary":"Close Account","description":"Close your account. Immediate, and there is no way back.\n\nEverything this account holds is deleted: every job's copy of its image, every result and\nevery preview, including the ones you asked us to keep. A file you uploaded is deleted on\nits own, shortly after the upload. Every key stops working the moment this returns,\nincluding the one you are calling with, and anything you had published is taken down.\n\nWhat is left is the accounting — how many credits were granted and what they were spent\non. Your name and address are removed from it, and your sign-in is taken off it, which is\nwhy signing in again gives you a new account rather than this one. A payment made on the\nweb keeps its reference to the payment processor's record of it, which names the buyer, so\nthat a refund or a dispute after closing can still be matched to the payment.\n\n**Finish or cancel your work first.** A job that is still queued or running writes its\nresult after this would have deleted it, so an account with work in flight is refused\nrather than closed into an inconsistent state. Wait for it to settle and ask again.\n\n**A subscription bought on the web is ended with the account.** It is set to end when\nits current period does, so nothing further is charged; what remains of the period is\nforfeited. If that cannot be arranged at the moment, the account is not closed and you\nare asked to try again. A subscription bought in the app is not ended here — cancel it\nin Settings on the iPhone or iPad.\n\nWhere the sign-in provider asks to be told that the account is gone, we tell them, and\nthe account closes either way.","operationId":"close_account_v1_account_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Closed"}}}},"404":{"description":"the key you are calling with no longer reaches an account"},"409":{"description":"work is still queued or running on this account; wait for it to finish"},"502":{"description":"the account could not be closed and is still open; your files may have been deleted, and any subscription bought on the web has already been set to end when its current period does — ask again to finish closing"},"503":{"description":"closing did not finish just now — ask again. Any subscription bought on the web may already be set to end when its current period does, and if the key you called with has stopped working, the account was closed after all"},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/webhooks/secret":{"get":{"tags":["keys"],"summary":"Get Webhook Secret","description":"The secret your callbacks are signed with, created the first time you ask.\n\nUnlike an API key this can be read back whenever you need it — the platform signs with\nit, so it is not stored as a hash. Treat it as a credential all the same: anyone holding\nit can produce a delivery your receiver will accept as genuine.\n\nA signature is `sha256=` followed by the hex HMAC-SHA256 of `<CV-Timestamp>.<body>`,\ncomputed over the bytes exactly as received. `CV-Signature` is a comma-separated list —\naccept the delivery if any value in it matches.","operationId":"get_webhook_secret_v1_webhooks_secret_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecret"}}}},"404":{"description":"the account behind this key no longer exists"},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/webhooks/secret/rotate":{"post":{"tags":["keys"],"summary":"Rotate Webhook Secret","description":"Replace the signing secret, without breaking the receiver you have deployed.\n\nThe secret you were using keeps being sent alongside the new one for a window, so every\ndelivery in that period carries a signature under each and a receiver verifying against\neither passes. Deploy the new secret whenever it suits you; nothing fails in between.\n\nRotating twice inside one window drops the older of the two — the window is what a\nreceiver is given, not a chain to accumulate.","operationId":"rotate_webhook_secret_v1_webhooks_secret_rotate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecret"}}}},"404":{"description":"the account behind this key no longer exists"},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/account/product-updates":{"get":{"tags":["account"],"summary":"Get Product Updates","description":"Whether we may email you about product improvements and new guides.\n\nNothing is sent until you say yes, and a guest account is never asked: there is no\naddress to send to until it is signed in.","operationId":"get_product_updates_v1_account_product_updates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductUpdates"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]},"put":{"tags":["account"],"summary":"Put Product Updates","description":"Say whether we may email you about product improvements and new guides.\n\nThe emails go to the address you signed in with. The last answer holds, and you can\nchange it whenever you like; once you have answered, the website and the app stop asking.","operationId":"put_product_updates_v1_account_product_updates_put","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductUpdatesAnswer"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductUpdates"}}}},"403":{"description":"a guest account; there is no address to email until it is signed in"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/gallery":{"post":{"tags":["gallery"],"summary":"Submit","description":"Offer a finished job to the gallery. Nothing is visible until it is approved.\n\nSharing keeps the job: its result and original are retained past the ordinary schedule,\nexactly as asking to keep them would, and the gallery shows those retained copies. So a\npost never outlives what it shows, your own links to a shared result do not expire\nunder it, and withdrawing changes nothing about what you keep. While a job is on the\ngallery or awaiting review, its retention cannot be turned off — withdraw it first.","operationId":"submit_v1_gallery_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostResponse"}}}},"403":{"description":"a guest account, or consent not given"},"404":{"description":"no such finished job on this account, a job with no image left to publish, or one whose original has expired"},"409":{"description":"this job already has a submission awaiting moderation or published, or its pictures were deleted while it was being offered"},"422":{"description":"the request did not validate"},"503":{"description":"storage was briefly unavailable while preparing the submission; retry"},"429":{"description":"the calling key's request rate is exceeded, or this account has made as many gallery submissions as it may in 24 hours. The allowance counts a rolling day, so a slot returns once the oldest submission is a day old, and Retry-After says when, for either.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]},"get":{"tags":["gallery"],"summary":"Gallery","description":"Approved submissions, newest first.\n\nLinks are minted per request and expire; the objects behind them do not. That is the\nright way round for a page anyone signed in can read — a URL that leaks is useless in a\nweek, while the picture stays where its owner put it.","operationId":"gallery_v1_gallery_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":30,"title":"Limit"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Before"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryPage"}}}},"403":{"description":"a guest account; the gallery needs a sign-in"},"429":{"description":"the calling key's request rate is exceeded","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/gallery/submissions":{"get":{"tags":["gallery"],"summary":"Mine","description":"This account's own submissions, in whatever state.\n\nRejected ones are included. A queue that silently swallows a submission teaches people\nto submit again, which is the moderation queue's problem rather than theirs.","operationId":"mine_v1_gallery_submissions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PostResponse"},"title":"Response Mine V1 Gallery Submissions Get"}}}},"403":{"description":"a guest account; the gallery needs a sign-in"},"429":{"description":"the calling key's request rate is exceeded","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}},"/v1/gallery/{post_id}":{"delete":{"tags":["gallery"],"summary":"Withdraw","description":"Take a submission down, published or not.\n\nWithdrawing is not being rejected. It is recorded as your own decision, counts against\nnothing, and leaves you free to submit the same job again.\n\nNothing is deleted: the gallery showed your job's retained copies, and those stay yours\n— kept, until you turn auto-delete back on or delete the job's pictures. The post leaves\nthe gallery on the next read.","operationId":"withdraw_v1_gallery__post_id__delete","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","title":"Post Id"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"no such submission on this account"},"403":{"description":"a guest account; the gallery needs a sign-in"},"429":{"description":"the calling key's request rate is exceeded","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"missing, malformed or unrecognised API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer`"}}},"400":{"description":"the path, query or a JSON string in the body contains a NUL character or an unpaired surrogate; the body is not JSON encoded as UTF-8, or carries a number JSON cannot represent (NaN, Infinity, or one too large) or nesting too deep to read; or an Idempotency-Key header is longer than 255 characters or not printable ASCII. The request was not read"},"413":{"description":"the request body is larger than 1 MiB. The request was not read"}},"security":[{"bearerAuth":[]}]}}},"webhooks":{"job.settled":{"post":{"tags":["jobs"],"summary":"Job settled","description":"Sent to your `webhook_url` once a job settles, whether it succeeded or failed.\n\nAnswer `2xx` as soon as you have the body, and do the work afterwards. A delivery that\ntimes out, or that is answered with a `5xx` or a `429`, is retried with the delay\ndoubling between attempts; any other `4xx` is read as a refusal and not retried. A\nredirect is not followed — register the address you want called.\n\nDeliveries are at-least-once: an endpoint that is slow enough to time out is retried\nwhile it is still working on the first one. Make your handler idempotent on `id`.","operationId":"job_settledjob_settled_post","parameters":[{"description":"When this delivery was signed, in whole seconds since the Unix epoch, written in digits only. Part of the signed material, and worth rejecting when it is old.","required":true,"schema":{"type":"string","title":"Cv-Timestamp","description":"When this delivery was signed, in whole seconds since the Unix epoch, written in digits only. Part of the signed material, and worth rejecting when it is old."},"name":"CV-Timestamp","in":"header"},{"description":"`sha256=` and the hex HMAC-SHA256 of `<CV-Timestamp>.<body>` under your signing secret. Absent when no secret is in place for your endpoint.","required":false,"schema":{"type":"string","title":"Cv-Signature","description":"`sha256=` and the hex HMAC-SHA256 of `<CV-Timestamp>.<body>` under your signing secret. Absent when no secret is in place for your endpoint.","default":""},"name":"CV-Signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Closed":{"properties":{"closed":{"type":"boolean","title":"Closed","description":"Always true. The account is closed and no key on it will authenticate again."},"keys_revoked":{"type":"integer","title":"Keys Revoked","description":"How many keys stopped working, including the one this request used."},"objects_deleted":{"type":"integer","title":"Objects Deleted","description":"How many stored files were deleted — every job's copy of its image, every result and every preview this account had, including ones it had asked to keep. Files you uploaded are deleted on their own, shortly after each upload."},"subscriptions_ended":{"type":"integer","title":"Subscriptions Ended","description":"How many subscriptions bought on the web were set to end when their current period does. Nothing further is charged; the rest of the period is forfeited with the account. A subscription bought in the app is not counted here: cancel it in Settings on the iPhone or iPad."}},"type":"object","required":["closed","keys_revoked","objects_deleted","subscriptions_ended"],"title":"Closed","description":"What closing the account did, so a client can say so rather than guess."},"GalleryItem":{"properties":{"id":{"type":"string","title":"Id","description":"Identifies this gallery entry. The gallery page shows it with each entry, so a report about one can name it."},"public_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Model","description":"The model that produced the result shown."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"What the submitter wrote to go with the picture, as published. Absent when there is none."},"before_url":{"type":"string","title":"Before Url","description":"A temporary link to the image as it was submitted."},"after_url":{"type":"string","title":"After Url","description":"A temporary link to the result."},"after_thumb_url":{"type":"string","title":"After Thumb Url","description":"A small preview of the result, for a listing that would otherwise fetch every full-size image."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"When the post was published. ISO-8601, UTC."}},"type":"object","required":["id","before_url","after_url","after_thumb_url"],"title":"GalleryItem"},"GalleryPage":{"properties":{"data":{"items":{"$ref":"#/components/schemas/GalleryItem"},"type":"array","title":"Data","description":"This page of gallery entries, newest first."},"has_more":{"type":"boolean","title":"Has More","description":"Whether more entries remain past this page."},"next_before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Before","description":"Pass as `before` to fetch the next page. Absent on the last one."}},"type":"object","required":["data","has_more"],"title":"GalleryPage"},"GalleryState":{"properties":{"status":{"type":"string","enum":["pending","approved"],"title":"Status","description":"`pending` while the submission is being reviewed, `approved` once it is on the gallery."},"post_id":{"type":"string","title":"Post Id","description":"The submission's id — what `DELETE /v1/gallery/{post_id}` withdraws."}},"type":"object","required":["status","post_id"],"title":"GalleryState","description":"A job's live gallery submission, as a read of the job reports it."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobFile":{"properties":{"url":{"type":"string","title":"Url","description":"A temporary link that serves the file inline. Expires; fetch the file rather than keeping the link."},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"The file's media type."},"bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bytes","description":"The file's size, in bytes."}},"type":"object","required":["url"],"title":"JobFile","description":"A file a job produced, other than the principal output."},"JobList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/JobResponse"},"type":"array","title":"Data","description":"This page of jobs, newest first."},"has_more":{"type":"boolean","title":"Has More","description":"Whether more jobs remain past this page."},"next_before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Before","description":"Pass as `before` to fetch the next page. Absent on the last one."}},"type":"object","required":["data","has_more"],"title":"JobList"},"JobOutput":{"properties":{"url":{"type":"string","title":"Url","description":"A temporary link that serves the file inline, for showing a result in a page. Expires; fetch the file rather than keeping the link."},"download_url":{"type":"string","title":"Download Url","description":"The same file, served as a download so a browser saves it under a filename. A separate link because the disposition is signed in; neither can be turned into the other."},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"The file's media type."},"bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bytes","description":"The file's size, in bytes."}},"type":"object","required":["url","download_url"],"title":"JobOutput","description":"Where a finished job's file is, and what it is."},"JobProgress":{"properties":{"stage":{"type":"string","enum":["starting","processing","saving"],"title":"Stage","description":"`starting` while your file is fetched and read, `processing` while the model runs, `saving` while the result is stored."},"fraction":{"anyOf":[{"type":"number","exclusiveMaximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Fraction","description":"Roughly how much of the run is done, from 0 up to but never reaching 1, estimated from how long recent jobs like this one have taken. Null when no estimate holds: none is available yet, or the job has run past it."}},"type":"object","required":["stage"],"title":"JobProgress","description":"How far a running job has got."},"JobResponse":{"properties":{"id":{"type":"string","title":"Id"},"custom_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Id","description":"Your own identifier for the job, if you sent one."},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"The model this job was sent to."},"status":{"$ref":"#/components/schemas/JobStatus","description":"`queued`, `running`, `succeeded` or `failed`. A callback is only sent for the last two, since it is sent once a job has settled."},"flagged":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Flagged","description":"Whether an automated check thought this job's images may fall outside what the service allows: the part of your image the job worked on (all of it, unless you named a region), and for some jobs the one that came back. `null` means they were not all checked."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why a failed job failed, in words meant for a person."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"The same fact as `error`, as a stable identifier to branch on. New codes appear as new things become worth telling apart, so treat an unfamiliar one as a failure you have no special handling for rather than as an error in itself. A failed job is never charged, whatever the code; `inference_failed` is the one worth singling out, because it means the fault was ours and the same request is worth retrying."},"community":{"type":"boolean","title":"Community","description":"Whether the job runs on the community queue, which runs on shared, free capacity and can wait longer at busy times. Explains a long wait that is not a fault.","default":false},"credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits","description":"What this job was billed. A job is charged when it succeeds — a still at the model's image price, a video by its length — so this reads 0 until then, and stays 0 for a job that fails."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the job was submitted. ISO-8601, UTC."},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At","description":"When the job settled. ISO-8601, UTC. Not stated while a job is still queued or running."},"result":{"anyOf":[{"$ref":"#/components/schemas/JobResult"},{"type":"null"}],"description":"The model's own answer, with an `output` object carrying links to the file when the job produced one. Not stated until the job succeeds."},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"A temporary link to a small preview of the job's picture — the result, or the image the model worked from when the job produced no file — for showing a list of jobs without fetching each one whole. Absent until the job succeeds, where no preview could be made, and once the job's pictures are deleted."},"kept":{"type":"boolean","title":"Kept","description":"Whether this result was asked to outlive the ordinary retention schedule.","default":false},"gallery":{"anyOf":[{"$ref":"#/components/schemas/GalleryState"},{"type":"null"}],"description":"Where this job stands with the public gallery, when you have offered it: `pending` while it is being reviewed, `approved` once it is shown. Absent when it is not offered, including after you withdraw it."},"progress":{"anyOf":[{"$ref":"#/components/schemas/JobProgress"},{"type":"null"}],"description":"How far the job has got while it runs: its stage, and roughly what fraction is done. Absent while the job is queued and once it has settled, so a callback never carries one."},"eta_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eta Seconds","description":"Rough seconds until the job finishes — the wait for capacity plus the time running. Not stated once a job has settled, so a callback never carries one."}},"type":"object","required":["id","status"],"title":"JobResponse","description":"A job as the API states it.\n\nAn absent field means it does not apply to this job — a queued one has no result, a\nfinished one has no estimate — rather than that it is unknown."},"JobResult":{"properties":{"output":{"anyOf":[{"$ref":"#/components/schemas/JobOutput"},{"type":"null"}],"description":"Links to the file the job produced, when it produced one."},"input":{"anyOf":[{"$ref":"#/components/schemas/JobFile"},{"type":"null"}],"description":"The image the model worked from. Where you named a region, this is that region — so what was used is something you can look at rather than something to take on trust. It is not the file you uploaded: yours is yours, and is removed on its own schedule. It is written in the format `auto` would return for what you sent, whatever `output_format` asked of the result."}},"additionalProperties":true,"type":"object","title":"JobResult","description":"A job's whole answer: the model's own fields, and the files it produced.\n\nA model that answers only in fields carries no `output`; one that produces only a file\ncarries `output` and nothing else; one doing both needs nothing new. The model's fields\nare its own, and the model's `result_schema` in the catalog describes them."},"JobStatus":{"type":"string","enum":["queued","running","succeeded","failed"],"title":"JobStatus"},"JobSubmission":{"properties":{"model":{"type":"string","title":"Model","description":"Which model to run, by the `name` from the model catalog."},"input_key":{"type":"string","title":"Input Key","description":"Object key returned by POST /v1/uploads."},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Options for the model, validated against its published `params_schema`."},"webhook_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Webhook Url","description":"Absolute http or https URL to receive a signed POST once the job settles. See the webhooks section for the body and its signature."},"custom_id":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Custom Id","description":"Your own identifier for this job, returned on every read of it and on the webhook. Not interpreted, not required to be unique, and not something jobs can be searched by."},"input_megapixels":{"anyOf":[{"type":"number","maximum":100000.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Input Megapixels","description":"The size of the file you uploaded, in megapixels (width × height ÷ 1,000,000; for a clip, one frame times the number of frames). Optional. Used only to make `eta_seconds` an estimate for your file rather than for a typical one — it does not affect the price, the queue or whether the job is accepted, all of which read the file itself."}},"additionalProperties":false,"type":"object","required":["model","input_key"],"title":"JobSubmission","description":"The public request body for POST /v1/jobs."},"KeepResponse":{"properties":{"kept":{"type":"boolean","title":"Kept","description":"Whether this job's files are now retained past the ordinary schedule."},"output_url":{"type":"string","title":"Output Url","description":"A fresh temporary link to the retained result."},"input_url":{"type":"string","title":"Input Url","description":"A fresh temporary link to the retained picture the model worked from."}},"type":"object","required":["kept","output_url","input_url"],"title":"KeepResponse","description":"The retained copies, with fresh links to them."},"KeyInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Identifies this key, for revoking it. Not the key itself."},"prefix":{"type":"string","title":"Prefix","description":"The first few characters, which identify a key without authorizing anything. It is what a listing can show and what to match against your own records."},"name":{"type":"string","title":"Name","description":"The label given when it was created."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"When the key was issued. ISO-8601, UTC."},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At","description":"When this key stopped working; absent while it still works. A revoked key is kept so a listing can explain a client that started failing."},"rate_limit_per_minute":{"type":"integer","title":"Rate Limit Per Minute","description":"Requests a minute this key may make before it is throttled."},"current":{"type":"boolean","title":"Current","description":"Whether this is the key that authenticated this request. It cannot be revoked by id while it is the one you are calling with; `DELETE /v1/keys/current` ends it on request, unless it is the account's only way back in."}},"type":"object","required":["id","prefix","name","rate_limit_per_minute","current"],"title":"KeyInfo","description":"One key on the account. Never the key itself — see `NewKey` for the one response\nthat carries a usable value."},"KeyList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeyInfo"},"type":"array","title":"Data","description":"This page of keys, newest first, revoked ones included."},"has_more":{"type":"boolean","title":"Has More","description":"Whether more keys remain past this page."},"next_before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Before","description":"Pass as `before` to fetch the next page. Absent on the last one."}},"type":"object","required":["data","has_more"],"title":"KeyList","description":"A page of the account's keys, in the shape every listing here takes."},"MintRequest":{"properties":{"name":{"type":"string","maxLength":64,"title":"Name","description":"A label to tell this key from your others in a listing. Not a secret and not interpreted.","default":"api"}},"additionalProperties":false,"type":"object","title":"MintRequest"},"ModelInfo":{"properties":{"name":{"type":"string","title":"Name","description":"The value to send as `model` when submitting a job."},"version":{"type":"string","title":"Version","description":"Which revision of this model is in service. It changes when the results change, so record it if you need to explain an old result later."},"title":{"type":"string","title":"Title","description":"A short name for this model, for showing to a person."},"description":{"type":"string","title":"Description","description":"What this model is for, in a sentence or two, for showing to a person."},"accepts":{"items":{"type":"string"},"type":"array","title":"Accepts","description":"Media types this model takes. Anything else is refused at submit."},"produces":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Produces","description":"The media type a result is usually returned as. Advisory: a model that preserves the format you sent returns what you sent. Null on a model that returns no file at all — its answer is in the job's `result`, and there is nothing to download."},"coming_soon":{"type":"boolean","title":"Coming Soon","description":"This model is announced but not yet in service: submitting to it is refused with 409 until it launches. Everything else here is final.","default":false},"video_coming_soon":{"type":"boolean","title":"Video Coming Soon","description":"Video for this model is announced but not yet on, so `accepts` names no video type yet. False on a model that will not take video at all.","default":false},"tenure":{"type":"string","enum":["fixed","comet"],"title":"Tenure","description":"fixed is a lasting part of the service. comet is a utility or fun experiment.","default":"fixed"},"max_input_bytes":{"type":"integer","title":"Max Input Bytes","description":"The largest file this model accepts, in bytes. Declared when you ask for somewhere to upload, and enforced by storage on the way in."},"max_frame_megapixels":{"type":"number","title":"Max Frame Megapixels","description":"Largest single frame this key may send, in megapixels. A still is one frame, so it applies to images too. 0 means unbounded."},"max_frames":{"type":"number","title":"Max Frames","description":"Most frames this key may send in one job. A still is one frame. Divide by reference_fps for the equivalent in seconds. 0 means unbounded."},"max_output_megapixels":{"type":"number","title":"Max Output Megapixels","description":"Largest single frame this key may receive back, in megapixels. For a model that enlarges, the result is the image or region you send at the size you ask for — so this and `max_input_megapixels`, and not the frame ceiling, decide whether a job is accepted: send a smaller region, or ask for less enlargement. 0 means unbounded."},"max_input_megapixels":{"type":"number","title":"Max Input Megapixels","description":"Largest part of an image this key may have worked on in one job, in megapixels: the region you name with `roi`, or the whole image. A job over it is refused, whatever size of result it asks for: name a smaller region, or send a smaller image. 0 means unbounded."},"max_denoise_input_megapixels":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Denoise Input Megapixels","description":"Largest part of an image this key may have worked on in one job with `denoise` sent as true, in megapixels; `max_input_megapixels` applies otherwise. Null for a model that takes no `denoise`. 0 means unbounded."},"credits_per_image":{"type":"integer","title":"Credits Per Image","description":"Credits charged for one still image. Charged when the job succeeds; a job that does not succeed costs nothing."},"credits_per_video_second":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Per Video Second","description":"Credits charged per second of video, or null for a model that takes stills only. Charged when the job succeeds, from the clip's length."},"reference_fps":{"type":"number","title":"Reference Fps","description":"The frame rate `max_frames` is expressed in, so a limit in frames can be read as a limit in seconds."},"params_schema":{"additionalProperties":true,"type":"object","title":"Params Schema","description":"JSON Schema for this model's `params`: everything a job may be given, with defaults and permitted values. Validate against it rather than discovering a refusal."},"params_order":{"items":{"type":"string"},"type":"array","title":"Params Order","description":"Every name in `params_schema`'s `properties`, in the order a form presents them. JSON does not order an object's keys, so read the order from here."},"result_schema":{"additionalProperties":true,"type":"object","title":"Result Schema","description":"JSON Schema for the fields a finished job of this model states under `result`, beside any files it links. Empty for a model whose whole answer is its file."},"community":{"type":"boolean","title":"Community","description":"Whether your key's work goes to the community queue, which runs on shared, free capacity and can wait longer at busy times. False once the account is paying, by holding credit or by subscribing, which puts its work on paid compute that scales with demand. The limits above are the ones that apply on that queue."},"image_only_params":{"items":{"type":"string"},"type":"array","title":"Image Only Params","description":"Parameters that apply to stills only. A video job that sets one is refused, so a client offering video hides them."}},"type":"object","required":["name","version","title","description","accepts","produces","max_input_bytes","max_frame_megapixels","max_frames","max_output_megapixels","max_input_megapixels","max_denoise_input_megapixels","credits_per_image","credits_per_video_second","reference_fps","params_schema","params_order","result_schema","community","image_only_params"],"title":"ModelInfo"},"ModelUsage":{"properties":{"model":{"type":"string","title":"Model","description":"The model the jobs were submitted to."},"jobs":{"type":"integer","title":"Jobs","description":"Jobs submitted to this model, all time, whatever their outcome."},"failed":{"type":"integer","title":"Failed","description":"Of those, how many failed. A failed job costs nothing."},"credits":{"type":"integer","title":"Credits","description":"Credits spent on this model, all time."},"output_bytes":{"type":"integer","title":"Output Bytes","description":"Total size of this model's results, in bytes."}},"type":"object","required":["model","jobs","failed","credits","output_bytes"],"title":"ModelUsage"},"NewKey":{"properties":{"id":{"type":"string","title":"Id","description":"Identifies this key, for revoking it."},"key":{"type":"string","title":"Key","description":"The key itself. This is the only time it is returned — it is stored as a hash, so it cannot be shown again and cannot be recovered for you."},"prefix":{"type":"string","title":"Prefix","description":"The opening characters of the key, for telling your keys apart once the secret is no longer shown."},"name":{"type":"string","title":"Name","description":"The label you gave this key."},"rate_limit_per_minute":{"type":"integer","title":"Rate Limit Per Minute","description":"Requests a minute this key may make before it is throttled."}},"type":"object","required":["id","key","prefix","name","rate_limit_per_minute"],"title":"NewKey","description":"A freshly minted key. The only response that contains a usable value."},"PostRequest":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"The job whose before-and-after you are offering."},"consent":{"type":"boolean","title":"Consent","description":"Required. Owning a file is not the consent of whoever appears in it, so this must be set deliberately and is never inferred.","default":false},"keep":{"type":"boolean","title":"Keep","description":"No longer needed: sharing keeps the job's files past the ordinary schedule, so this is accepted and has no effect.","default":true},"description":{"anyOf":[{"type":"string","maxLength":280},{"type":"null"}],"title":"Description","description":"Optional. A sentence or two to show with the picture on the gallery. The reviewer reads it with the submission and may shorten or correct it before publishing."}},"additionalProperties":false,"type":"object","required":["job_id"],"title":"PostRequest"},"PostResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Identifies this submission, for withdrawing it later."},"status":{"type":"string","title":"Status","description":"`pending` until a person decides, then `approved` or `rejected`. Nothing appears in the gallery without that decision."},"job_id":{"type":"string","title":"Job Id","description":"The job this post shows."},"public_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Model","description":"The model that produced it."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"When the post was submitted. ISO-8601, UTC."},"decided_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided Note","description":"What you were told about the decision, when there is anything to say."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"The description this post goes out with: yours as sent, or as the reviewer edited it on approval."},"withdrawn":{"type":"boolean","title":"Withdrawn","description":"Whether you have taken this post back. A withdrawn post is not shown and is not reconsidered.","default":false},"kept":{"type":"boolean","title":"Kept","description":"Whether the job's files are retained — kept past the ordinary schedule. True for every new submission: sharing keeps the job.","default":false}},"type":"object","required":["id","status","job_id"],"title":"PostResponse"},"ProductUpdates":{"properties":{"opted_in":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Opted In","description":"Whether we may email you about product improvements and new guides: your last answer, or null if you have not given one. Nothing is sent until it is true. Receipts and notices about your account are sent either way."},"ask":{"type":"boolean","title":"Ask","description":"Whether the CraterView website and app ask you this now: true until you answer, once the account is signed in and has run 5 jobs."}},"type":"object","required":["opted_in","ask"],"title":"ProductUpdates"},"ProductUpdatesAnswer":{"properties":{"opted_in":{"type":"boolean","title":"Opted In","description":"`true` to receive product improvement and guide update emails, `false` not to. The last answer holds; change it whenever you like."}},"additionalProperties":false,"type":"object","required":["opted_in"],"title":"ProductUpdatesAnswer"},"UploadRequest":{"properties":{"content_type":{"type":"string","title":"Content Type","description":"The media type you are about to upload. Signed into the URL, so the PUT must send the same one.","default":"image/png"},"content_length":{"type":"integer","maximum":5368709120.0,"exclusiveMinimum":0.0,"title":"Content Length","description":"Bytes you are about to upload. The presigned URL accepts this figure and no other, so send exactly it. One past the upload limit the catalog states as `max_input_bytes` is refused with a 413; one past 5 GiB, more than any single upload can be, is refused with a 422."}},"additionalProperties":false,"type":"object","required":["content_length"],"title":"UploadRequest"},"UploadResponse":{"properties":{"input_key":{"type":"string","title":"Input Key","description":"Send this as `input_key` when you submit the job."},"upload_url":{"type":"string","title":"Upload Url","description":"PUT the file here, with the content type and length you declared. It goes straight to storage and expires shortly, so upload promptly."}},"type":"object","required":["input_key","upload_url"],"title":"UploadResponse"},"UsageResponse":{"properties":{"credits":{"type":"integer","title":"Credits","description":"Credits spent, all time."},"credits_granted":{"type":"integer","title":"Credits Granted","description":"Credits added to this account, all time — bought, granted, and arrears written off — less any refunded purchase."},"credits_remaining":{"type":"integer","title":"Credits Remaining","description":"Credits left to spend. Never below zero: work on the community queue is charged and may take an account past its balance, and the arrears are written off the next time credit arrives."},"subscribed_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Subscribed Until","description":"When the current subscription's paid period ends, ISO-8601 — null when there is no subscription. Informational: `subscribed` is the answer to which queue your work enters, decided here rather than by comparing this against your own clock."},"subscribed":{"type":"boolean","title":"Subscribed","description":"Whether a subscription is currently putting this account's work on paid compute. While it is true, work runs there at any balance — and the balance means what it always did, because a subscription grants no credit and jobs are charged as usual. A capped subscription gives it for `subscription_allowance` credits of work a period; past that, work runs on the community queue until the period renews.","default":false},"subscription_allowance":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Subscription Allowance","description":"How many credits of work a period the subscription puts on paid compute, or null for one with no cap and when there is no subscription. Nothing is granted: jobs are charged as usual, and the window holds until the period's charges reach this figure."},"subscription_allowance_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Subscription Allowance Remaining","description":"Under a capped subscription, how much of `subscription_allowance` is left this period: how many more credits of work it will put on paid compute. Credit on the account is counted separately, as `credits_remaining`, and is spent first. Null where there is no cap."},"is_guest":{"type":"boolean","title":"Is Guest","description":"Whether this account has no sign-in attached to it. An account created in a browser or on a device starts this way and works normally — it submits, it spends credit, it holds keys. What it cannot do is buy or publish, and it cannot be reached from anywhere else, because there is nothing to sign in with. Signing in attaches one and this becomes false, keeping the same account, keys and history.","default":false},"jobs":{"type":"integer","title":"Jobs","description":"Jobs submitted, all time, whatever their outcome."},"output_bytes":{"type":"integer","title":"Output Bytes","description":"Total size of the results produced, in bytes — the results only, not the input or the preview kept beside them."},"by_model":{"items":{"$ref":"#/components/schemas/ModelUsage"},"type":"array","title":"By Model","description":"The same figures broken down per model, largest spend first."},"jobs_in_flight":{"type":"integer","title":"Jobs In Flight","description":"Your jobs queued or running right now.","default":0},"max_jobs_in_flight":{"type":"integer","minimum":1.0,"title":"Max Jobs In Flight","description":"The most you may have queued or running at once. Submitting past it is refused with a 429. It is higher once the account is signed in, so read it rather than assuming it."}},"type":"object","required":["credits","credits_granted","credits_remaining","jobs","output_bytes","by_model","max_jobs_in_flight"],"title":"UsageResponse"},"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"},"WebhookEvent":{"properties":{"id":{"type":"string","title":"Id"},"custom_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Id","description":"Your own identifier for the job, if you sent one."},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"The model this job was sent to."},"status":{"$ref":"#/components/schemas/JobStatus","description":"`queued`, `running`, `succeeded` or `failed`. A callback is only sent for the last two, since it is sent once a job has settled."},"flagged":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Flagged","description":"Whether an automated check thought this job's images may fall outside what the service allows: the part of your image the job worked on (all of it, unless you named a region), and for some jobs the one that came back. `null` means they were not all checked."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why a failed job failed, in words meant for a person."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"The same fact as `error`, as a stable identifier to branch on. New codes appear as new things become worth telling apart, so treat an unfamiliar one as a failure you have no special handling for rather than as an error in itself. A failed job is never charged, whatever the code; `inference_failed` is the one worth singling out, because it means the fault was ours and the same request is worth retrying."},"community":{"type":"boolean","title":"Community","description":"Whether the job runs on the community queue, which runs on shared, free capacity and can wait longer at busy times. Explains a long wait that is not a fault.","default":false},"credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits","description":"What this job was billed. A job is charged when it succeeds — a still at the model's image price, a video by its length — so this reads 0 until then, and stays 0 for a job that fails."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the job was submitted. ISO-8601, UTC."},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At","description":"When the job settled. ISO-8601, UTC. Not stated while a job is still queued or running."},"result":{"anyOf":[{"$ref":"#/components/schemas/JobResult"},{"type":"null"}],"description":"The model's own answer, with an `output` object carrying links to the file when the job produced one. Not stated until the job succeeds."},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"A temporary link to a small preview of the job's picture — the result, or the image the model worked from when the job produced no file — for showing a list of jobs without fetching each one whole. Absent until the job succeeds, where no preview could be made, and once the job's pictures are deleted."},"kept":{"type":"boolean","title":"Kept","description":"Whether this result was asked to outlive the ordinary retention schedule.","default":false},"gallery":{"anyOf":[{"$ref":"#/components/schemas/GalleryState"},{"type":"null"}],"description":"Where this job stands with the public gallery, when you have offered it: `pending` while it is being reviewed, `approved` once it is shown. Absent when it is not offered, including after you withdraw it."},"progress":{"anyOf":[{"$ref":"#/components/schemas/JobProgress"},{"type":"null"}],"description":"How far the job has got while it runs: its stage, and roughly what fraction is done. Absent while the job is queued and once it has settled, so a callback never carries one."},"eta_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eta Seconds","description":"Rough seconds until the job finishes — the wait for capacity plus the time running. Not stated once a job has settled, so a callback never carries one."}},"type":"object","required":["id","status"],"title":"WebhookEvent","description":"The body delivered to a caller's `webhook_url` once a job settles.\n\nIt is a job, delivered rather than fetched: the same fields a read of the job states,\nunder the same names. Every one of them is present on every delivery, null where it does\nnot apply, so a subscriber parses one shape rather than branching on which keys arrived."},"WebhookSecret":{"properties":{"secret":{"type":"string","title":"Secret","description":"Verify a delivery by computing HMAC-SHA256 of `<CV-Timestamp>.<body>` under this value and looking for the result among the comma-separated values in `CV-Signature`."},"previous":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous","description":"The secret this one replaced, while it is still being sent alongside. Null unless a rotation's window is open."},"overlap_ends_in_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overlap Ends In Seconds","description":"How much longer deliveries carry a signature under `previous`. Null when no rotation is in progress."}},"type":"object","required":["secret"],"title":"WebhookSecret","description":"The secret this account's callbacks are signed with."}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key, as `Authorization: Bearer cv_...`. Every `/v1` route needs one except the model catalogue, which answers without a key as it would for an account that has not paid."}}},"tags":[{"name":"models","description":"What is available and what it accepts."},{"name":"jobs","description":"Submit work and read results."},{"name":"usage","description":"What this account has consumed, all time."},{"name":"keys","description":"The keys your account authenticates with."},{"name":"account","description":"What you have told us about your account."}]}