← All guides

Guide API

Your first API call, in three requests

Ask for somewhere to put the file, upload the bytes, submit the job — in curl, Python and TypeScript, with the parts that usually go wrong.

Three API requests, or one if you use an SDK client. Your API key is issued from inside the web app, and the API address is shown there and in the reference.

export CV_API_KEY="cv_..."
export CV_API_BASE="https://api.craterview.ai"   # the address shown in the web app

1 — Ask for somewhere to put the file

You do not upload through the API. You ask it for a presigned URL and upload straight to storage, so nothing proxies your bytes.

LEN=$(wc -c < image.jpg)
UPLOAD=$(curl -s -X POST "$CV_API_BASE/v1/uploads" \
  -H "Authorization: Bearer $CV_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"content_type\": \"image/jpeg\", \"content_length\": $LEN}")
echo "$UPLOAD"   # -> {"upload_url": "...", "input_key": "..."}
UPLOAD_URL=$(echo "$UPLOAD" | jq -r .upload_url)
INPUT_KEY=$(echo "$UPLOAD" | jq -r .input_key)

content_type and content_length are both required, and both are signed into the URL that comes back, which is what the next step has to match. The next two steps use upload_url and input_key from the answer; jq pulls them out here, or copy them by hand.

2 — Upload the bytes

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @image.jpg

The content type has to be the one you declared. It is part of the signature, so omitting the header or sending a different value is refused by storage rather than by us, and the error will not mention CraterView.

Do not send your API key to the upload URL. The URL is signed already, and an Authorization header beside it is refused by storage with 400 and Missing x-amz-content-sha256, which names neither the key nor the header. The links to a finished job’s result are the same, so fetch those with no key too — a client that adds the key to every request it makes needs a plain one for these two.

3 — Submit the job

Build against echo first. It costs no credits, runs without a GPU, and returns a real result — a plain upscale — so the request, the parameters and the response are the ones a paid model gives, and your integration needs no change when you switch. When you are ready, swap the model name for the one you want.

curl -X POST "$CV_API_BASE/v1/jobs?wait=30" \
  -H "Authorization: Bearer $CV_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"model\": \"echo\", \"input_key\": \"$INPUT_KEY\",
       \"params\": {\"scale\": 2}}"
# echo is free, for building against. Swap in a paid model when you are ready —
# cv-enhance-v3 to enlarge, cv-denoise-v1 to remove grain, cv-restore-v1 to repair,
# cv-headshot-v1 for portraits.

?wait=30 holds the response open for up to thirty seconds, so a fast job comes back finished in the same request. A job that takes longer comes back when the wait runs out, with its id to poll and an eta_seconds — its estimated time to finish (ETA), in seconds — to show while you do.

Tell it how big the file is. Add input_megapixels to the submit (width × height ÷ 1,000,000) and eta_seconds is estimated for your file’s size rather than a typical one — the same request body as above, one field wider:

{"model": "echo", "input_key": "inputs/…", "params": {"scale": 2}, "input_megapixels": 12.2}

It is optional and it only changes the estimate. The price, the queue your job lands in and the size limits are all decided from the file itself, so a wrong figure misquotes your own progress bar and nothing else. Both SDK clients read the size from the file’s header and send it for you.

The same thing with an SDK client

# pip install craterview
from craterview import CraterView

cv = CraterView(api_key="cv_...")

job = cv.run(
    # echo is free, for building against. Swap in a paid model when you are ready —
    # cv-enhance-v3 to enlarge, cv-denoise-v1 to remove grain, cv-restore-v1 to repair,
    # cv-headshot-v1 for portraits.
    "image.jpg", model="echo",
    scale=2, wait=30,
)

job.save("image-restored.jpg")
// npm install craterview
// TypeScript source: bundle it, or run it with tsx — plain node cannot load it
import { CraterView } from "craterview";

const cv = new CraterView({ apiKey: "cv_..." });

const job = await cv.run(file, {
  // echo is free, for building against. Swap in a paid model when you are ready —
  // cv-enhance-v3 to enlarge, cv-denoise-v1 to remove grain, cv-restore-v1 to repair,
  // cv-headshot-v1 for portraits.
  model: "echo",
  scale: 2, wait: 30,
});

const blob = await job.blob();

Things that catch people out

Branch on code, not on the sentence. Every refusal is {"detail": …, "code": …}. The sentence may be reworded; the code will not. The reference lists every code.

-d without a JSON content type arrives form-encoded and never parses. Both curl examples above set the header for that reason.

Single quotes matter in the shell. "$INPUT_KEY" inside single quotes is not expanded, which produces a request that looks right and refers to nothing.

Do not hard-code a price or a parameter list. Every model publishes its own price and its own parameter schema from the API itself. Use them from there rather than from any page, including this one — a value copied into your code is a value that goes stale.

A balance of zero does not stop a job. With no credit and no subscription, a submit is accepted and runs on the community queue, which is free and can wait longer at busy times; nothing past your balance is collected. community on GET /v1/usage says which queue your next job enters. Paying moves it to paid compute that scales with demand.

Keep the job’s id, and quote it when something is wrong. Write to support@craterview.ai with the id and we can read the job, its charge and its run; for a request the API refused, quote the X-Trace-Id header every API response carries instead — the Python and TypeScript clients put it on the error they raise, as trace_id and traceId. An upload that storage refuses carries no such header — storage answers it, not the API — so for one of those, quote the input_key it was for and the time. A description from memory gives us a model and a day, which is every job that day.

Model names are stable. A model name usually includes the model version so an integration written today will always work. Keep an eye on the catalogue for updated versions.

Next

For batching a directory rather than a single file, a signed-in account can have up to 5 jobs queued or running at once, and 2 as a guest. A submit past that is answered 429, and max_jobs_in_flight on GET /v1/usage says which applies to you. If you need more at once, write to sales@craterview.ai. Also, if you would rather ask for the work than write the call, an assistant can use models on your account over MCP or by using one of our agent skills.

Try it on your own file

No signup and no card — a workspace opens for your browser the moment you do.

Open Web App