Quickstart
Create a key, find a model, submit a run, poll it and read the result.
This walk-through takes you from no key to a generated image. You need an AITOPIA account with credits.
#1. Create an API key
Sign in at aitopia.ai, open the profile menu > API keys and create a developer key. You get two secrets, each shown only once:
- the API key (
aitk_live_...), sent with every request; - the webhook secret, used to verify webhooks.
Store both on your server (for example in environment variables). Never put the key in a browser or mobile app: anyone holding it spends your credits.
Try it free first. Create a test key (
aitk_test_...) instead and usehttps://api-sandbox.aitopia.aias the base URL in every step below. The sandbox accepts the same requests, validates them the same way and returns sample files, without spending credits.
export AITOPIA_KEY="aitk_live_..."#2. List models
curl "https://api-live.aitopia.ai/v1/models?type=image&limit=5" \
-H "Authorization: Key $AITOPIA_KEY"const BASE = "https://api-live.aitopia.ai/v1";
const headers = { Authorization: `Key ${process.env.AITOPIA_KEY}` };
const res = await fetch(`${BASE}/models?type=image&limit=5`, { headers });
const { models, has_more, next_offset } = await res.json();
for (const m of models) console.log(m.id, m.price?.credits);import os, requests
BASE = "https://api-live.aitopia.ai/v1"
HEADERS = {"Authorization": f"Key {os.environ['AITOPIA_KEY']}"}
r = requests.get(f"{BASE}/models", params={"type": "image", "limit": 5}, headers=HEADERS)
for m in r.json()["models"]:
print(m["id"], (m["price"] or {}).get("credits"))Each model has an id, a type (image, video, audio or text), a price and a recommended flag. Page through the list with limit and offset while has_more is true.
#3. Read the model's input schema
Every model has its own input fields. Fetch them before the first run:
curl "https://api-live.aitopia.ai/v1/models/google/nano-banana-2" \
-H "Authorization: Key $AITOPIA_KEY"The answer has id, input_schema (a JSON Schema of the request body) and price. The examples below send a prompt; use the fields your model's input_schema lists.
#4. Check the price (optional)
Add ?dry_run=true to any submit call to get an estimate. Nothing runs and nothing is charged:
curl -X POST "https://api-live.aitopia.ai/v1/models/google/nano-banana-2?dry_run=true" \
-H "Authorization: Key $AITOPIA_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "a lighthouse at dusk, watercolor"}'{
"dry_run": true,
"endpoint": "google/nano-banana-2",
"credits_estimated": 4,
"affordable": true,
"credits_available": 100
}#5. Submit a run
The request body is the model input. The answer is 202 Accepted with the request id and the URLs to follow it:
curl -X POST "https://api-live.aitopia.ai/v1/models/google/nano-banana-2" \
-H "Authorization: Key $AITOPIA_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "a lighthouse at dusk, watercolor"}'const submit = await fetch(`${BASE}/models/google/nano-banana-2`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ prompt: "a lighthouse at dusk, watercolor" }),
});
const request = await submit.json();
if (!submit.ok) throw new Error(`${request.error.code}: ${request.error.message}`);
console.log(request.request_id, request.status);r = requests.post(
f"{BASE}/models/google/nano-banana-2",
headers=HEADERS,
json={"prompt": "a lighthouse at dusk, watercolor"},
)
request = r.json()
print(request["request_id"], request["status"]){
"request_id": "0d6f3c1e-5b7a-4c2e-9d1f-2a8b6e4c9f10",
"status": "queued",
"endpoint": "google/nano-banana-2",
"created_at": "2026-10-23T10:00:00.000Z",
"completed_at": null,
"credits_estimated": 4,
"output": { "images": [], "videos": [], "audio": [], "files": [] },
"status_url": "https://api-live.aitopia.ai/v1/requests/0d6f3c1e-5b7a-4c2e-9d1f-2a8b6e4c9f10",
"result_url": "https://api-live.aitopia.ai/v1/requests/0d6f3c1e-5b7a-4c2e-9d1f-2a8b6e4c9f10",
"cancel_url": "https://api-live.aitopia.ai/v1/requests/0d6f3c1e-5b7a-4c2e-9d1f-2a8b6e4c9f10/cancel"
}#6. Poll until it finishes
GET the status_url. With ?wait=30 the call holds for up to 30 seconds and returns as soon as the request is finished:
curl "https://api-live.aitopia.ai/v1/requests/$REQUEST_ID?wait=30" \
-H "Authorization: Key $AITOPIA_KEY"const TERMINAL = ["completed", "partial", "failed", "cancelled"];
let result = request;
while (!TERMINAL.includes(result.status)) {
const r = await fetch(`${result.status_url}?wait=30`, { headers });
result = await r.json();
}
console.log(result.status, result.output.images.map((i) => i.url));TERMINAL = {"completed", "partial", "failed", "cancelled"}
result = request
while result["status"] not in TERMINAL:
result = requests.get(result["status_url"], params={"wait": 30}, headers=HEADERS).json()
print(result["status"], [i["url"] for i in result["output"]["images"]])#7. Use the result
A finished request lists its files under output, grouped by kind:
{
"request_id": "0d6f3c1e-5b7a-4c2e-9d1f-2a8b6e4c9f10",
"status": "completed",
"endpoint": "google/nano-banana-2",
"created_at": "2026-10-23T10:00:00.000Z",
"completed_at": "2026-10-23T10:00:14.000Z",
"credits_estimated": 4,
"output": {
"images": [{ "url": "https://cdn.aitopia.ai/...", "content_type": "image/png" }],
"videos": [],
"audio": [],
"files": []
},
"status_url": "...",
"result_url": "...",
"cancel_url": "..."
}If the status is failed, the error object says why (see Errors & retries).
#Next steps
- Skip polling with webhooks.
- Send your own images or videos as model input with file uploads.
- Edit an existing file with
POST /v1/edit, or run several generations at once withPOST /v1/batch.