Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: CraterView.ai Gallery API
description: 'CraterView.ai image enhancement and restoration API. ## How a job works
Three calls — upload to a presigned URL, run a job, and download the result. 1. `POST /v1/uploads` — get a presigned URL and an `input_key`
2. `PUT` your bytes to that URL
3. `POST /v1/jobs` — submit `input_key` plus parameters
Inference is asynchronous, because a job can occupy a GPU for minutes. Add `?wait=30` to
step 3 and a fast job comes back finished in the same response; otherwise poll
`GET /v1/jobs/{id}` or supply a `webhook_url`. ## Collecting the result
A finished job carries two links to the same object under `result.output`, both presigned
and both expiring. `url` serves it inline, for displaying a result in a page. `download_url` carries `Content-Disposition: attachment`, so a browser saves it under a
filename instead. Pick by what you are doing with it; a client cannot convert one into the
other, because the disposition is signed into the URL. ## Output format
By default the result comes back in the format you sent — a JPEG upload returns a JPEG —
except that a HEIC or HEIF photograph prefers JPEG and a TIFF scan comes back as PNG: the
platform reads those formats and does not write them. `output_format` names another. Either is a preference: kept wherever that format holds the
picture, and PNG where it does not — an alpha channel asked for as JPEG, more than 8 bits asked
for as JPEG or WebP, a result wider than the format holds. `result.output.content_type`
says which format came back. ## When a job fails
A failed job carries two fields. `error` is a sentence meant for a person and is reworded
whenever it can be made clearer, so show it rather than match on it. `error_code` is the
stable half, and it is what to branch on. **A job that produces no output is not charged**, whoever''s fault it was, so a failed job
always leaves your balance where it started. What the code tells you is whether retrying is
worth anything. `inference_failed` means the fault was ours and the same request is worth
sending again. Every other code refuses the request as you sent it, so an identical retry
fails identically — change something or tell the person who made it. A job that succeeded
with a result that is *wrong* is not a failure the API can see; report it with the job''s
`id` (see *Reporting a problem*). This list is not closed. 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 client that rejects a code it does not know breaks on the next one added. | `error_code` | What it means |
| --- | --- |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `no_face_found` | The model frames a headshot around a face and found none.'
version: 0.1.0
servers:
- url: https://api.craterview.ai
tags:
- name: Gallery
paths:
/v1/gallery:
post:
tags:
- Gallery
summary: Submit
description: 'Offer a finished job to the gallery. Nothing is visible until it is approved.
Sharing keeps the job: its result and original are retained past the ordinary schedule,
exactly as asking to keep them would, and the gallery shows those retained copies. So a
post never outlives what it shows, your own links to a shared result do not expire
under it, and withdrawing changes nothing about what you keep. While a job is in the
gallery 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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: no such finished job on this account, a job with no image left to publish, or one whose original has expired
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: this job already has a submission awaiting moderation or published, or its pictures were deleted while it was being offered
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: the request did not validate
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: storage was briefly unavailable while preparing the submission; retry
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: missing, malformed or unrecognized API key
headers:
WWW-Authenticate:
schema:
type: string
description: '`Bearer`'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'413':
description: the request body is larger than 1 MiB. The request was not read
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- bearerAuth: []
get:
tags:
- Gallery
summary: Gallery
description: 'Approved submissions, newest first.
Links are minted per request and expire; the objects behind them do not. That is the
right way round for a page anyone signed in can read — a URL that leaks soon expires,
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
description: How many posts a page holds. Page on with `next_before`.
default: 30
title: Limit
description: How many posts a page holds. Page on with `next_before`.
- name: before
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Return posts older than this post id.
title: Before
description: Return posts older than this post id.
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/GalleryPage'
'403':
description: a guest account; the gallery needs a sign-in
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: the calling key's request rate is exceeded
headers:
Retry-After:
description: Seconds to wait before trying again.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: missing, malformed or unrecognized API key
headers:
WWW-Authenticate:
schema:
type: string
description: '`Bearer`'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'413':
description: the request body is larger than 1 MiB. The request was not read
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- bearerAuth: []
/v1/gallery/submissions:
get:
tags:
- Gallery
summary: Mine
description: 'This account''s own submissions, in whatever state.
Rejected ones are included. A queue that silently swallows a submission teaches people
to 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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: the calling key's request rate is exceeded
headers:
Retry-After:
description: Seconds to wait before trying again.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: missing, malformed or unrecognized API key
headers:
WWW-Authenticate:
schema:
type: string
description: '`Bearer`'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'413':
description: the request body is larger than 1 MiB. The request was not read
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- bearerAuth: []
/v1/gallery/{post_id}:
delete:
tags:
- Gallery
summary: Withdraw
description: 'Take a submission down, published or not.
Withdrawing is not being rejected. It is recorded as your own decision, counts against
nothing, and leaves you free to submit the same job again.
Nothing is deleted: the gallery showed your job''s retained copies, and those stay yours
— kept, until you turn auto-delete back on or delete the job''s pictures. The post leaves
the gallery on the next read.'
operationId: withdraw_v1_gallery__post_id__delete
parameters:
- name: post_id
in: path
required: true
schema:
type: string
description: The submission's `id`, as the submit returned it.
title: Post Id
description: The submission's `id`, as the submit returned it.
responses:
'204':
description: Successful Response
'404':
description: no such submission on this account
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: a guest account; the gallery needs a sign-in
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: the calling key's request rate is exceeded
headers:
Retry-After:
description: Seconds to wait before trying again.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: missing, malformed or unrecognized API key
headers:
WWW-Authenticate:
schema:
type: string
description: '`Bearer`'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'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
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'413':
description: the request body is larger than 1 MiB. The request was not read
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- bearerAuth: []
components:
schemas:
Error:
type: object
required:
- detail
- code
properties:
detail:
description: What went wrong, for a person. It may change; do not match on it. A validation refusal lists each field.
anyOf:
- type: string
- type: array
items:
type: object
required:
- loc
- msg
- type
properties:
loc:
type: array
items:
anyOf:
- type: string
- type: integer
description: 'Where: `body`, `query` or `header`, then the field, then an index where the field is a list.'
msg:
type: string
description: What is wrong there, for a person.
type:
type: string
description: The kind of failure, for a program.
code:
type: string
description: 'What went wrong, for a program: stable, so branch on this. Handle a code you do not know by its status.
- `missing_api_key`: no `Authorization: Bearer` header
- `invalid_api_key`: the key is not recognized, or was revoked
- `rate_limited`: too many requests a minute on this key; wait for `Retry-After`
- `too_many_uploads`: this account''s upload URLs a minute; wait for `Retry-After`
- `too_many_jobs`: this account''s jobs queued or running; wait for one to finish
- `queue_full`: the queue this job would join is full for now; wait for `Retry-After`
- `too_many_keys`: new keys this hour; wait for `Retry-After`
- `gallery_allowance_spent`: gallery submissions in the last 24 hours
- `sign_in_required`: a guest account cannot do this
- `consent_required`: `consent` was not true
- `model_not_found`: no model by that name
- `model_coming_soon`: the model is listed and not yet in service
- `video_coming_soon`: video is not taken yet
- `type_not_accepted`: the content type is not one taken here
- `upload_too_large`: the declared length is over the upload limit
- `upload_not_found`: nothing was uploaded under `input_key`
- `invalid_params`: `params` did not validate against the model''s schema
- `invalid_request`: the request did not validate; `detail` lists where
- `idempotency_conflict`: the `Idempotency-Key` was used with a different body
- `job_not_found`: no job of yours with that id
- `no_finished_job`: no finished job of yours with that id
- `job_not_settled`: the job is still queued or running
- `no_image`: the model returns no image
- `media_deleted`: the job''s pictures are gone
- `already_submitted`: the job is already awaiting moderation or published
- `submission_not_found`: no submission of yours with that id
- `key_not_found`: no active key of yours with that id
- `current_key`: that is the key you are calling with
- `only_key`: that is the account''s only way in
- `jobs_in_flight`: jobs still queued or running stop the account closing
- `account_not_found`: the key no longer reaches an account
- `unavailable`: briefly unavailable and nothing was done; send the same request again after a pause
- `malformed_request`: the request could not be read
- `body_too_large`: the request body is over the limit
- `internal_error`: a fault of ours; it may or may not have taken effect, so check before sending it again; quote `X-Trace-Id`
- `bad_request`: the request was refused
- `not_found`: nothing is at that address
- `method_not_allowed`: the address does not take that method'
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 in 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
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
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
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
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.'