Skip to content
Centshot

Getting started

Generate images from prompts with SDXL running as prebuilt TensorRT engines, and get back the server-measured timings behind every run. Three steps: create a key, queue a generation, poll it.

1Create an API key

Keys are created from the console at /console/keys. The secret is shown once, at creation, and is not recoverable afterwards. Store it somewhere your code can read, then keep it out of version control.

shell
export CENTSHOT_API_KEY="fd_live_..."

Every request authenticates with Authorization: Bearer $CENTSHOT_API_KEY. Keys are stored hashed, so a lost key is replaced rather than recovered.

2Queue a generation

Generation is asynchronous. POST returns 202 with a queued job immediately, rather than holding the connection open. Generations run one at a time on a single warm GPU, so a synchronous call would simply time out when anyone else was mid-render.

curl
# 1. queue a generation
curl -sS -X POST https://centshot.ai/v1/generations \
  -H "Authorization: Bearer $CENTSHOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "a lighthouse at dusk, dramatic sky",
    "variantId": "fp16-base",
    "steps": 30
  }'

# -> 202 { "id": "7f3c...", "status": "queued", ... }

# 2. poll until it settles
curl -sS https://centshot.ai/v1/generations/7f3c... \
  -H "Authorization: Bearer $CENTSHOT_API_KEY"

Call GET /v1/variants for the engines you can pass as variantId, along with their size, VRAM, throughput, and quality.

3Poll until it settles

Poll the job until status is succeeded or failed. On success, imageUrl is a base64 data URL and metrics holds the timings measured around the real denoise loop.

javascript
const BASE = "https://centshot.ai";
const headers = {
  Authorization: `Bearer ${process.env.CENTSHOT_API_KEY}`,
  "Content-Type": "application/json",
};

const queued = await fetch(`${BASE}/v1/generations`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    prompt: "a lighthouse at dusk, dramatic sky",
    variantId: "fp16-base",
    steps: 30,
  }),
}).then((r) => r.json());

// Poll until the job settles. Generations are serialized on one GPU, so a
// job can sit queued while another finishes.
let job = queued;
while (job.status === "queued" || job.status === "running") {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  job = await fetch(`${BASE}/v1/generations/${queued.id}`, { headers })
    .then((r) => r.json());
}

if (job.status !== "succeeded") throw new Error(job.error ?? job.status);

console.log(job.metrics.latencyMs.denoise, "ms denoising");
// job.imageUrl is a base64 data URL
python
import os, time, base64, urllib.request, json

BASE = "https://centshot.ai"
HEADERS = {
    "Authorization": f"Bearer {os.environ['CENTSHOT_API_KEY']}",
    "Content-Type": "application/json",
}


def call(path, method="GET", body=None):
    request = urllib.request.Request(
        BASE + path,
        method=method,
        headers=HEADERS,
        data=json.dumps(body).encode() if body else None,
    )
    with urllib.request.urlopen(request) as response:
        return json.load(response)


job = call("/v1/generations", "POST", {
    "prompt": "a lighthouse at dusk, dramatic sky",
    "variantId": "fp16-base",
    "steps": 30,
})

while job["status"] in ("queued", "running"):
    time.sleep(1)
    job = call(f"/v1/generations/{job['id']}")

if job["status"] != "succeeded":
    raise RuntimeError(job.get("error") or job["status"])

print(job["metrics"]["latencyMs"]["denoise"], "ms denoising")

_, _, payload = job["imageUrl"].partition("base64,")
open("out.png", "wb").write(base64.b64decode(payload))

4Handle errors

Every non-2xx response uses the same envelope. Branch on code, which is stable; message is for humans and may be reworded. Quote requestId when reporting a problem, and it identifies the exact request in our logs.

400 Bad Request
{
  "error": {
    "code": "invalid_request",
    "message": "Request body failed validation.",
    "details": [
      { "path": "steps", "message": "Too big: expected number to be <=60" }
    ],
    "requestId": "9c1e6a4f-2b77-4b0e-9a1f-3c5d8e2b7a10"
  }
}
  • invalid_request — body or query failed validation; details lists the offending fields.
  • unauthorized — missing, malformed, or revoked key.
  • rate_limited — too many requests; honour Retry-After.
  • quota_exceeded — monthly generation ceiling reached; resets at the start of next month.
  • upstream_error — the GPU backend is unreachable or refused the job. Safe to retry.