仕様API reference
Every endpoint, documented completely.
https://animeapi.ai. JSON in, JSON out. Images are synchronous: the response arrives when the image exists. Videos are jobs: start one, then poll it until the MP4 is ready.01Authentication
Every request needs an API key from Dashboard → API keys in the Authorization header:
Authorization: Bearer ank_your_key_here- Keys are
ank_followed by 64 hex characters. We store only a SHA-256 hash, so a key is shown once when you create it. - Revoke a key from the dashboard at any time; requests with it then return 401 immediately.
- Keep keys on the server. Anyone holding a key can spend your balance.
02POST /api/generate
Generate one image. The body is JSON:
| Field | Type | Default | Description |
|---|---|---|---|
| prompt | string | required | What to draw, in plain English. Trimmed. Max 1,000 characters. |
| model | "anime" | "ultra" | "ultra-max" | "anime" | Which model draws it. See models. |
| orientation | "portrait" | "square" | "landscape" | "portrait" | Aspect ratio. Exact pixel sizes depend on the model. |
| enhance | boolean | true | Anime model only. Rewrites your prompt into Danbooru tags before generating. Ultra tiers ignore it. |
| allowNSFW | boolean | true | Anime model only. When false, images flagged as NSFW fail with 500 and are refunded. Ultra tiers ignore it and use the provider’s moderation. |
Every field
curl https://animeapi.ai/api/generate \
-H "Authorization: Bearer $ANIMEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a girl with a red umbrella at a rainy bus stop",
"model": "anime",
"orientation": "landscape",
"enhance": true,
"allowNSFW": false
}'An ultra request
curl https://animeapi.ai/api/generate \
-H "Authorization: Bearer $ANIMEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "anime illustration, a cafe chalkboard that reads OPEN LATE", "model": "ultra", "orientation": "square"}'In your language
curl https://animeapi.ai/api/generate \
-H "Authorization: Bearer $ANIMEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "a girl with a red umbrella at a rainy bus stop", "model": "anime", "orientation": "portrait"}'import os
import requests
res = requests.post(
"https://animeapi.ai/api/generate",
headers={"Authorization": f"Bearer {os.environ['ANIMEAPI_KEY']}"},
json={"prompt": "a girl with a red umbrella at a rainy bus stop", "model": "anime", "orientation": "portrait"},
timeout=120,
)
data = res.json()
if not res.ok:
raise SystemExit(f"{res.status_code} {data.get('code', '')}: {data['error']}")
print(data["image_url"])
print(data["enhanced_prompt"])// generate.mjs · Node 18+ · run: node generate.mjs
const res = await fetch("https://animeapi.ai/api/generate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANIMEAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "a girl with a red umbrella at a rainy bus stop",
model: "anime",
orientation: "portrait",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.code ?? ""}: ${data.error}`);
console.log(data.image_url);
console.log(data.enhanced_prompt);03Response
200 OK with:
{
"image_url": "https://…/5e1c9a4f.webp",
"enhanced_prompt": "masterpiece, best quality, absurdres, 1girl, solo, red_umbrella, holding_umbrella, bus_stop, rain, night, …",
"balance_usd": 9.991,
"generation_time_ms": 4121
}| Field | Type | Always present | Description |
|---|---|---|---|
| image_url | string | yes | URL of the image. WebP from the anime model, PNG from ultra tiers. |
| enhanced_prompt | string | yes | The prompt the model actually received: Danbooru tags when enhancement ran on the anime model, your prompt plus the anime style on ultra tiers, otherwise your prompt as sent. |
| balance_usd | number | yes | Your balance after this charge. A raw float that may show noise like 9.991000000000001; round it for display. |
| generation_time_ms | number | yes | Milliseconds spent generating, including enhancement. Excludes network time to you. |
04Models & pricing
Prices are per successful image (videos are priced per second, see below). Add $5–$1,000 at a time on Billing; the balance never expires.
| Model | Price (any orientation) | Sizes (P · S · L) | Format | Measured median |
|---|---|---|---|---|
| animeFast and cheap. Best at characters. Your prompt is rewritten into Danbooru tags first. | $0.003 | 832×1280 · 1024×1024 · 1280×832 | WebP | 4.3s |
| ultraFollows long sentences and renders legible text. | $0.01 | 1024×1536 · 1024×1024 · 1536×1024 | PNG | 10.5s |
| ultra-maxUltra with more care: finer detail, cleaner hands, real typography. | $0.02 | 1024×1536 · 1024×1024 · 1536×1024 | PNG | 11.5s |
- anime: best for characters. Understands tags; can’t render legible text; strongly prefers drawing a person.
- ultra: follows long, compositional sentences and renders text. An anime style is added to every prompt, so plain descriptions come out as anime.
- ultra-max: ultra with more care: finer detail, better hands and typography.
Latency measured end to end on 2026-09-24 over 16 anime, 5 ultra and 5 ultra-max calls. Not a guarantee.
05Prompt enhancement
The anime model was trained on booru tags, not sentences. With enhance: true (the default), a fast language model rewrites your prompt into Danbooru-style tags before generation, adding about a second. The tags come back in enhanced_prompt.
- To reuse a look exactly, send a previous
enhanced_promptback as the prompt withenhance: false. - If you already write tags, turn enhancement off. It occasionally adds contradictory tags (for example
1girlnext tono_humans). - Ultra and ultra-max don’t use tags: they keep your sentence and append an anime style, shown in
enhanced_prompt.
06POST /api/videos
Start one anime video: text-to-video, or animated from a first and/or last frame you supply. Rendering takes a while, so this returns 202 with a job id straight away; poll GET /api/videos/{id} until it finishes. The price is charged when the job starts and refunded automatically if it fails.
| Field | Type | Default | Description |
|---|---|---|---|
| prompt | string | required | What happens in the clip, in plain English. Trimmed. Max 1,000 characters. An anime style is added automatically. |
| resolution | "480p" | "768p" | "768p" | Output resolution. Priced per second, see pricing. |
| duration | integer | 5 | Length in seconds, 5 to 15. |
| orientation | "portrait" | "square" | "landscape" | "landscape" | 9:16, 1:1 or 16:9. When first_frame is set, the image sets the shape instead. |
| enhance | boolean | true | Lets the video model expand your prompt with detail before rendering. |
| first_frame | string | none | Image the clip starts on: an http(s) URL or a base64 data URI (data:image/png;base64,…), up to 10 MB. |
| last_frame | string | none | Image the clip ends on. Same format. |
Send neither frame for text-to-video. Send one or both to animate from, toward, or between your own images, for example a character you generated with /api/generate.
Text to video
curl https://animeapi.ai/api/videos \
-H "Authorization: Bearer $ANIMEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a girl with a red umbrella waits at a rainy bus stop, then looks up as the bus pulls in",
"resolution": "480p",
"duration": 5,
"orientation": "landscape"
}'From a first frame
curl https://animeapi.ai/api/videos \
-H "Authorization: Bearer $ANIMEAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "she rides down the hill toward the sea, hair and ribbon blowing in the wind",
"first_frame": "https://animeapi.ai/gen/wall-bike-sea.webp",
"resolution": "480p",
"duration": 5
}'202 Accepted
{
"id": "j57c2x9k…",
"status": "processing",
"poll_url": "https://animeapi.ai/api/videos/j57c2x9k…",
"enhanced_prompt": "a girl with a red umbrella waits at a rainy bus stop, then looks up as the bus pulls in, anime style, …",
"resolution": "480p",
"duration": 5,
"orientation": "landscape",
"cost_usd": 0.08,
"balance_usd": 9.92
}| Field | Type | Always present | Description |
|---|---|---|---|
| id | string | yes | The job id. Poll it with GET /api/videos/{id}. |
| status | "processing" | yes | Always processing on creation. |
| poll_url | string | yes | The full GET URL for this job. |
| enhanced_prompt | string | yes | Your prompt plus the anime style, as sent to the video model. With enhance on, the model may expand it further. |
| resolution, duration, orientation | string · number · string | yes | The settings used, with defaults filled in. |
| cost_usd | number | yes | What this video costs. Already charged. |
| balance_usd | number | yes | Your balance after the charge. A raw float; round it for display. |
Pricing
Per requested second, charged up front, refunded if the video fails.
| Resolution | Per second | 5s clip | 15s clip | Sizes (P · S · L) |
|---|---|---|---|---|
| 480p | $0.016 | $0.08 | $0.24 | 480×832 · 480×480 · 832×480 |
| 768p | $0.04 | $0.20 | $0.60 | 768×1344 · 768×768 · 1344×768 |
Sizes are nominal. With a first frame, the image sets the canvas.
07GET /api/videos/{id}
Poll with the same key, about every 5 seconds, until status is succeeded or failed. Only the account that created a video can read it.
curl https://animeapi.ai/api/videos/$VIDEO_ID \
-H "Authorization: Bearer $ANIMEAPI_KEY"{
"id": "j57c2x9k…",
"status": "processing",
"video_url": null,
"prompt": "a girl with a red umbrella waits at a rainy bus stop, then looks up as the bus pulls in",
"enhanced_prompt": "a girl with a red umbrella waits at a rainy bus stop, then looks up as the bus pulls in, anime style, …",
"resolution": "480p",
"duration": 5,
"orientation": "landscape",
"first_frame": false,
"last_frame": false,
"cost_usd": 0.08,
"refunded": false,
"generation_time_ms": null,
"error": null,
"created_at": "2026-09-29T09:14:03.512Z"
}| Field | Type | Always present | Description |
|---|---|---|---|
| status | "processing" | "succeeded" | "failed" | yes | Where the job is. |
| video_url | string | null | yes | The MP4 once succeeded, otherwise null. |
| prompt, enhanced_prompt | string | yes | What you sent, and what the video model received. |
| resolution, duration, orientation | string · number · string | yes | The settings used. |
| first_frame, last_frame | boolean | yes | Whether the job was started from those frames. |
| cost_usd | number | yes | What the video cost. |
| refunded | boolean | yes | true when the job failed and the charge went back to your balance. |
| generation_time_ms | number | null | yes | Time from start to finished file, once succeeded. |
| error | string | null | yes | Why it failed. See failed video jobs. |
| created_at | string | yes | ISO 8601 timestamp. |
- Videos are MP4, 24 fps, with generated sound.
- A failed job has
refunded: true; start a new one if you want to retry. Don’t start duplicates while one is still processing: each start is charged.
Start and wait, in your language
// video.mjs · Node 18+ · run: node video.mjs
const API = "https://animeapi.ai/api/videos";
const headers = {
Authorization: `Bearer ${process.env.ANIMEAPI_KEY}`,
"Content-Type": "application/json",
};
// 1. Start the job. The price is charged now and refunded if it fails.
const res = await fetch(API, {
method: "POST",
headers,
body: JSON.stringify({
prompt: "a girl with a red umbrella waits at a rainy bus stop, then looks up as the bus pulls in",
resolution: "480p",
duration: 5,
orientation: "landscape",
}),
});
let video = await res.json();
if (!res.ok) throw new Error(`${res.status} ${video.code ?? ""}: ${video.error}`);
console.log(`started ${video.id}, charged ${video.cost_usd} USD`);
// 2. Poll every 5 seconds until it finishes.
while (video.status === "processing") {
await new Promise((resolve) => setTimeout(resolve, 5000));
const poll = await fetch(`${API}/${video.id}`, { headers });
video = await poll.json();
if (!poll.ok) throw new Error(`${poll.status} ${video.code ?? ""}: ${video.error}`);
}
if (video.status === "failed") throw new Error(`Failed and refunded: ${video.error}`);
console.log(video.video_url);# video.py · run: python video.py [first-frame.png]
import base64
import mimetypes
import os
import sys
import time
import requests
API = "https://animeapi.ai/api/videos"
HEADERS = {"Authorization": f"Bearer {os.environ['ANIMEAPI_KEY']}"}
def data_uri(path: str) -> str:
mime = mimetypes.guess_type(path)[0] or "image/png"
with open(path, "rb") as f:
return f"data:{mime};base64,{base64.b64encode(f.read()).decode()}"
body = {
"prompt": "a girl with a red umbrella waits at a rainy bus stop, then looks up as the bus pulls in",
"resolution": "480p",
"duration": 5,
"orientation": "landscape",
}
# Optional: start the clip on a local image. The image sets the video's shape.
if len(sys.argv) > 1:
body["first_frame"] = data_uri(sys.argv[1])
# 1. Start the job. The price is charged now and refunded if it fails.
res = requests.post(API, headers=HEADERS, json=body, timeout=60)
video = res.json()
if res.status_code != 202:
raise SystemExit(f"{res.status_code} {video.get('code', '')}: {video['error']}")
print(f"started {video['id']}, charged {video['cost_usd']} USD")
# 2. Poll every 5 seconds until it finishes.
while video["status"] == "processing":
time.sleep(5)
res = requests.get(f"{API}/{video['id']}", headers=HEADERS, timeout=30)
video = res.json()
if not res.ok:
raise SystemExit(f"{res.status_code} {video.get('code', '')}: {video['error']}")
if video["status"] == "failed":
raise SystemExit(f"Failed and refunded: {video['error']}")
print(video["video_url"])08Errors
Errors are JSON with an error message and, for most, a machine-readable code. Validation errors happen before anything is charged. If generation fails after charging, the charge is refunded automatically.
{ "error": "Insufficient balance. Please add funds to your account.", "code": "INSUFFICIENT_BALANCE" }POST /api/generate
| Status | code | error | When | Charged |
|---|---|---|---|---|
| 400 | — | Invalid JSON body | The body isn't valid JSON. | No |
| 400 | — | Missing 'prompt' field in request body | prompt is missing or empty. | No |
| 400 | — | Prompt exceeds maximum length of 1000 characters | prompt is longer than 1,000 characters. | No |
| 400 | — | Invalid model. Must be 'anime', 'ultra', or 'ultra-max' | Unknown model. | No |
| 400 | — | Invalid orientation. Must be 'portrait', 'square', or 'landscape' | Unknown orientation. | No |
| 400 | MODERATION_BLOCKED | Your prompt was blocked by the content safety system. Please revise it and try again. | Ultra tiers: the provider's safety system refused the prompt. | Refunded |
| 401 | — | Missing or invalid Authorization header. Use: Bearer ank_xxxxx | No Authorization header, or not in Bearer form. | No |
| 401 | — | Invalid API key format. Keys should start with 'ank_' | The token doesn't start with ank_. | No |
| 401 | INVALID_KEY | Invalid API key | Key not found. | No |
| 401 | INVALID_KEY | API key has been revoked | The key was revoked in the dashboard. | No |
| 402 | INSUFFICIENT_BALANCE | Insufficient balance. Please add funds to your account. | Balance is below the price of the requested image. | No |
| 500 | GENERATION_FAILED | (message from the image provider) or Internal server error | Generation failed upstream, or the request couldn't be processed. | Refunded |
POST /api/videos
| Status | code | error | When | Charged |
|---|---|---|---|---|
| 400 | — | Invalid JSON body | The body isn't valid JSON. | No |
| 400 | — | Missing 'prompt' field in request body | prompt is missing or empty. | No |
| 400 | — | Prompt exceeds maximum length of 1000 characters | prompt is longer than 1,000 characters. | No |
| 400 | — | Invalid resolution. Must be '480p' or '768p' | Unknown resolution. | No |
| 400 | — | Invalid duration. Must be a whole number of seconds from 5 to 15 | duration isn't an integer in range. | No |
| 400 | — | Invalid orientation. Must be 'portrait', 'square', or 'landscape' | Unknown orientation. | No |
| 400 | INVALID_FRAME | 'first_frame' must be an image URL or a base64 data URI | Not an http(s) URL or a data:image/…;base64 URI. Same for last_frame. | No |
| 400 | INVALID_FRAME | 'first_frame' is larger than 10 MB | A data URI decodes to more than 10 MB. Same for last_frame. | No |
| 400 | INVALID_FRAME | Couldn't download first_frame from <url> | The frame URL couldn't be reached. Same for last_frame. | No |
| 400 | INVALID_FRAME | Couldn't download first_frame: HTTP <status> | The frame URL answered with an error status. Same for last_frame. | No |
| 400 | INVALID_FRAME | first_frame must be an image (got <content-type>) | The frame URL isn't served as image/*. Same for last_frame. | No |
| 400 | INVALID_FRAME | first_frame is larger than 10 MB | The downloaded frame is over 10 MB. Same for last_frame. | No |
| 401 | — | Missing or invalid Authorization header. Use: Bearer ank_xxxxx | No Authorization header, or not in Bearer form. | No |
| 401 | — | Invalid API key format. Keys should start with 'ank_' | The token doesn't start with ank_. | No |
| 401 | INVALID_KEY | Invalid API key | Key not found. | No |
| 401 | INVALID_KEY | API key has been revoked | The key was revoked in the dashboard. | No |
| 402 | INSUFFICIENT_BALANCE | Insufficient balance. Please add funds to your account. | Balance is below the price of the requested video. | No |
| 500 | GENERATION_FAILED | The prompt or a frame was blocked by the content safety system. Please revise it and try again. | The video model's safety system refused the prompt or a frame. | Refunded |
| 500 | GENERATION_FAILED | The video couldn't be generated. The charge was refunded; retrying once is fine. | The job couldn't be started. | Refunded |
| 500 | GENERATION_FAILED | Internal server error | The request couldn't be processed. | No |
GET /api/videos/{id}
| Status | code | error | When | Charged |
|---|---|---|---|---|
| 401 | — | Missing or invalid Authorization header. Use: Bearer ank_xxxxx | No Authorization header, not in Bearer form, or the token doesn't start with ank_. | No |
| 401 | INVALID_KEY | Invalid API key | Key not found or revoked. | No |
| 404 | NOT_FOUND | Video not found | No video with that id, or it belongs to another account. | No |
Failed video jobs
A video that fails while rendering isn’t an HTTP error: GET /api/videos/{id} returns 200 with status: "failed", refunded: true and one of these in error. The charge is already back in your balance.
| Status | code | error | When | Charged |
|---|---|---|---|---|
| 200 | — | The prompt or a frame was blocked by the content safety system. Please revise it and try again. | The video model's safety system refused the prompt or a frame. | Refunded |
| 200 | — | The video couldn't be generated. The charge was refunded; retrying once is fine. | Rendering failed. | Refunded |
| 200 | — | The video took too long to render. The charge was refunded. | The job was still running after 30 minutes. | Refunded |
Retrying
Only a 500 (or a failed video job) is worth retrying automatically; you were refunded. 400, 401, 402 and 404 won’t change on retry.
import os
import time
import requests
def generate(prompt: str, model: str = "anime", attempts: int = 3) -> dict:
for attempt in range(1, attempts + 1):
res = requests.post(
"https://animeapi.ai/api/generate",
headers={"Authorization": f"Bearer {os.environ['ANIMEAPI_KEY']}"},
json={"prompt": prompt, "model": model, "allowNSFW": False},
timeout=180,
)
data = res.json()
if res.ok:
return data
# 500 means the provider failed and you were refunded: safe to retry.
if res.status_code == 500 and attempt < attempts:
time.sleep(2 * attempt)
continue
# 400/401/402 won't fix themselves by retrying.
raise RuntimeError(f"{res.status_code} {data.get('code', '')}: {data['error']}")
raise RuntimeError("unreachable")
if __name__ == "__main__":
print(generate("a girl with a red umbrella at a rainy bus stop")["image_url"])09Limits
- No daily or per-minute cap. Generate as many images and videos as your balance covers.
- If you ever get a 429, back off and retry.
- Prompts up to 1,000 characters.
- One image per request. For batches, send requests in parallel from your server.
- One video per job, 5 to 15 seconds. Jobs run in parallel; frames up to 10 MB each.
10Image & video URLs
- Anime images are WebP files on our generation provider’s CDN. Download anything you want to keep; don’t treat the URL as permanent storage.
- Ultra and ultra-max images are PNG files in AnimeAPI’s storage.
- Videos are MP4 files in AnimeAPI’s storage.
- All are public URLs that anyone with the link can open.
11Demo endpoint
The homepage demo uses a public endpoint with no key: anime model, portrait, enhancement on, NSFW off, and 3 images per IP per day. It’s for trying things, not for production.
curl https://animeapi.ai/api/demo/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "a knight cat drinking tea in a sunflower field"}'- Body:
promptonly, up to 500 characters. - Response:
image_url,enhanced_prompt,generation_time_msanddemo_remaining. - Over the limit: 429 with
code: "RATE_LIMITED". A failed generation still uses one of the three.
12CORS & browsers
Responses include Access-Control-Allow-Origin: *, and preflight allows Content-Type and Authorization. That makes local tools and prototypes easy, but don’t ship an API key in public browser code: call AnimeAPI from your server and pass the image or video URL to the client.