API reference
OpenAI-compatible inference over an open network of providers. Point any OpenAI SDK at our base URL, keep your existing code, and pay per use in credits.
Introduction
#The core endpoints match the OpenAI /v1/* shape one-for-one. Swap the base_url on the OpenAI Python or Node SDK and the rest of your application stays unchanged. OpenAlchemy extensions (voices, audio watermark, image, video, OCR, garment segmentation) are marked as such.
Three things differ from OpenAI:
- You pay in credits (1 credit = USD 0.001). Top up in /billing; prices by capability are on the pricing page.
- Every JSON response has an
x_openalchemyblock with the request id, worker id, engine latency, and usage breakdown — useful for debugging and cost attribution. - Inference is served by independent providers running real GPUs against your traffic. The model catalog at /models shows the live capacity per model.
Authentication
#The API uses bearer tokens. Provision a key in /api-keys — keys are shown exactly once at creation; we store only a salted hash. Treat them like passwords.
Each request must include an Authorization header. Keys can be scoped to a subset of models; requests for models outside that scope return 403 model_not_allowed.
Never ship a live key to a browser bundle, a mobile app, or a public Git repo. If a key leaks, revoke it immediately from /api-keys.
Authorization: Bearer $OPENALCHEMY_API_KEYBase URL
#The only network endpoint you need is:
https://api.openalchemy.ioAppend /v1/… for the API surface. The server selects a worker, dispatches your request, returns the result, and bills you on completion — all in the same response.
# Python — works for chat, vision, embeddings, audio.
from openai import OpenAI
client = OpenAI(
base_url="https://api.openalchemy.io/v1",
api_key=os.environ["OPENALCHEMY_API_KEY"],
)Response metadata
#Every JSON response carries an x_openalchemy block. Standard SDKs ignore unknown fields, so this is invisible to existing code — but it is the first place to look when debugging.
request_id— quote this when filing a ticket.worker_id— which provider served you.engine_latency_ms— pure GPU time, excludes network.usage.cost— credits debited, as a fixed-point decimal string.
The /logs page indexes the same fields so you can search across requests.
{
"id": "cmpl-…",
"object": "chat.completion",
"model": "llama-3.1-70b-instruct",
"choices": [ … ],
"usage": { "prompt_tokens": 24, "completion_tokens": 19, "total_tokens": 43 },
"x_openalchemy": {
"request_id": "req_01HG…",
"tier": "m",
"worker_id": "wrk_4f…",
"engine_request_id": "vllm-…",
"engine_latency_ms": 412,
"upstream_latency_ms": 438,
"usage": {
"input_tokens": 24,
"output_tokens": 19,
"total_tokens": 43,
"cost": "0.000086"
}
}
}All endpoints
#Every public endpoint. Append any path to the base URL. Jump to a documented endpoint below, or open the live model catalog to see which models serve each one.
/v1/chat/completionsstreamingPOST/v1/chat/completionsvisionbetaGET/v1/modelslistPOST/v1/embeddingsvectorbetaPOST/v1/rerankrerankbetaPOST/v1/audio/transcriptionsspeech-to-textbetaPOST/v1/audio/speechtext-to-speechbetaPOST/v1/audio/speech/cancelcancelbetaGET/v1/audio/voicesvoicesbetaPOST/v1/audio/voicesclonebetaGET/v1/audio/voices/{voice_id}/clipclipbetaDELETE/v1/audio/voices/{voice_id}deletebetaPOST/v1/audio/watermark/embedwatermarkbetaPOST/v1/audio/watermark/detectdetectbetaPOST/v1/images/local/generationsimagebetaPOST/v1/images/local/editsimage editbetaPOST/v1/videosvideobetaGET/v1/videoslistbetaGET/v1/videos/availabilityavailabilitybetaGET/v1/videos/{id}statusbetaGET/v1/videos/{id}/contentdownloadbetaPOST/v1/videos/{id}/cancelcancelbetaPOST/v1/ocrocrbetaPOST/v1/garment/segmentgarmentbetaList models
#/v1/modelsReturns every model the network currently serves, with capacity, tier, and capability metadata. Use endpoint_type to filter (chat / embedding / rerank / stt / tts / image / video …), and live_workers to gate fallbacks.
curl https://api.openalchemy.io/v1/models \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY"{
"object": "list",
"data": [
{
"id": "llama-3.1-70b-instruct",
"object": "model",
"endpoint_type": "chat",
"tier": "m",
"family": "llama-3.1",
"context_window": 131072,
"params_b": 70,
"live_workers": 4,
"online": true
},
…
]
}Chat completions
#/v1/chat/completionsGenerate a model response for a conversation. The bread-and-butter endpoint — OpenAI-compatible.
OpenAI v1 compatible — drop in `base_url` + key in any OpenAI SDK.
| Name | Type | Description |
|---|---|---|
modelrequired | string | Model id from /v1/models. |
messagesrequired | array<{role, content}> | Conversation turns. role is system / user / assistant. |
temperature | number 0–2default 1 | Sampling temperature. |
top_p | number 0–1default 1 | Nucleus sampling cumulative probability. |
max_tokens | integerdefault 2048 | Maximum tokens to generate. |
presence_penalty | number -2…2default 0 | Penalise tokens already present in the text. |
frequency_penalty | number -2…2default 0 | Penalise tokens in proportion to their frequency. |
stop | string | string[] | Sequences that halt generation. |
response_format | { type: 'text' | 'json_object' } | Force JSON output. |
stream | booleandefault false | Stream the response as server-sent events; the final chunk carries usage. |
{
"id": "cmpl-…",
"object": "chat.completion",
"model": "<model>",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "…" },
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": N, "completion_tokens": N, "total_tokens": N },
"x_openalchemy": {
"request_id": "req_…",
"tier": "s",
"worker_id": "…",
"engine_latency_ms": …,
"usage": { "input_tokens": N, "output_tokens": N, "cost": "0.0008" }
}
}invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.engine_unreachableThe inference engine could not be reached or timed out.curl https://api.openalchemy.io/v1/chat/completions \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "llama-3.1-70b-instruct",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Say hi in one sentence."
}
],
"temperature": 0.7,
"max_tokens": 256
}'Vision
#/v1/chat/completionsSame endpoint as chat completions, with image content parts. Use a vision-capable model.
OpenAI Vision-style — pick a vision-capable model from /v1/models.
| Name | Type | Description |
|---|---|---|
modelrequired | string | A vision-capable model id. |
messagesrequired | array<{role, content: (string | ContentPart[])}> | Each content part is {type:'text', text} or {type:'image_url', image_url:{url, detail}}. |
image_url.url | string | https://… URL or data:image/...;base64,… (max ~6 MB per image after base64). |
image_url.detail | 'low' | 'auto' | 'high'default 'auto' | low: 85 tokens per image flat; high: 85 + 170 × tiles (~765 for 768×768). |
temperature | number 0–2default 0.2 | Same as chat. |
max_tokens | integerdefault 1024 | Same as chat. |
// Same shape as /v1/chat/completions.
// x_openalchemy.usage.image_tokens is the per-request image token total.invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.curl https://api.openalchemy.io/v1/chat/completions \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5-vl-72b",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What'\''s in this image?"
},
{
"type": "image_url",
"image_url": {
"url": "https://images.example.com/cat.jpg",
"detail": "auto"
}
}
]
}
],
"max_tokens": 512
}'Embeddings
#/v1/embeddingsTurn one or many strings into dense vectors for search, clustering, or RAG.
OpenAI Embeddings v1 compatible.
| Name | Type | Description |
|---|---|---|
modelrequired | string | Embedding model id. |
inputrequired | string | string[] | One or many strings to embed. |
dimensions | integer | For Matryoshka-trained models, truncate the vector to this many dimensions. Ignored otherwise. |
encoding_format | 'float' | 'base64'default 'float' | Wire format for the returned vectors. |
{
"object": "list",
"data": [
{ "object": "embedding", "embedding": [0.0123, -0.045, …], "index": 0 },
…
],
"model": "<model>",
"usage": { "prompt_tokens": N, "total_tokens": N },
"x_openalchemy": { "usage": { "input_tokens": N, "cost": "…" } }
}invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.curl https://api.openalchemy.io/v1/embeddings \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "nomic-embed-text-v1.5",
"input": [
"The quick brown fox",
"jumps over the lazy dog"
]
}'Reranking
#/v1/rerankRerank N candidate documents against a query. Cohere-compatible request shape.
Cohere /v2/rerank compatible (`model`, `query`, `documents`, `top_n`).
| Name | Type | Description |
|---|---|---|
modelrequired | string | Reranker model id. |
queryrequired | string | The search query. |
documentsrequired | string[] | { text: string }[] | Candidate documents to rerank. |
top_n | integer | Return only the top-N results. Omit for all. |
return_documents | booleandefault false | Include each document's text in the response. |
{
"results": [
{ "index": 2, "relevance_score": 0.97 },
{ "index": 0, "relevance_score": 0.81 },
…
],
"x_openalchemy": {
"usage": {
"query_tokens": Q,
"document_tokens": Σ,
"total_tokens": Q + Σ,
"cost": "…"
}
}
}invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.curl https://api.openalchemy.io/v1/rerank \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bge-reranker-v2-m3",
"query": "What is OpenAlchemy?",
"documents": [
"OpenAlchemy is a distributed inference network where independent providers run open models for credits.",
"Cats are popular household pets.",
"Workers connect to the grid and serve OpenAI-compatible traffic."
],
"top_n": 2
}'Audio transcriptions
#/v1/audio/transcriptionsSpeech-to-text. Multipart upload of an audio file, Whisper-compatible response.
OpenAI Whisper v1 compatible. multipart/form-data.
| Name | Type | Description |
|---|---|---|
filerequired | binary | mp3 / wav / m4a / webm / flac. Up to 25 MB per request. |
modelrequired | string | Speech-to-text model id. |
language | string | ISO-639-1 (e.g. 'en', 'ja'). Omit for auto-detect. |
prompt | string | Optional bias text. Useful for technical vocabulary. |
response_format | 'json' | 'text' | 'srt' | 'vtt' | 'verbose_json'default 'json' | Output format. |
temperature | number 0–1default 0 | Sampling temperature (rarely needed). |
// json:
{ "text": "…transcript…" }
// verbose_json (adds duration + segments):
{ "text": "…", "duration": 12.34, "language": "en", "segments": [ … ] }
// srt / vtt: plain text subtitle file in the matching format.invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.curl https://api.openalchemy.io/v1/audio/transcriptions \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-F file=@/path/to/audio.mp3 \
-F model=whisper-large-v3 \
-F response_format=jsonAudio speech
#/v1/audio/speechText-to-speech. Returns binary audio bytes with usage metadata in response headers.
OpenAI TTS v1 compatible. WAV output today.
| Name | Type | Description |
|---|---|---|
modelrequired | string | Text-to-speech model id. |
inputrequired | string | Text to synthesise. |
voice | string | Voice id — a built-in voice or one of your cloned voices; list them with GET /v1/audio/voices. |
response_format | 'wav' | 'mp3' | 'opus'default 'wav' | Audio container. mp3 / opus return 501 until engine transcoding ships. |
speed | number 0.5–2.0default 1 | Playback speed multiplier. |
stream | booleandefault false | Relay chunked WAV as it is generated (low-latency playback). Audio already generated is billed if you disconnect. |
// Body is binary audio (Content-Type: audio/wav).
// Inspect the response headers:
// X-Openalchemy-Request-Id
// X-Openalchemy-Audio-Seconds
// X-Openalchemy-Cost
// X-Engine-Request-Id (pass to /v1/audio/speech/cancel)invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.curl https://api.openalchemy.io/v1/audio/speech \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "cosyvoice2",
"input": "Hello from OpenAlchemy.",
"voice": "en_alice",
"response_format": "wav"
}' \
--output speech.wavCancel speech
#/v1/audio/speech/cancelStop an in-flight /v1/audio/speech synthesis so the worker frees its GPU. Best-effort: always returns 202. Audio already streamed is still billed.
OpenAlchemy extension (not an OpenAI endpoint).
| Name | Type | Description |
|---|---|---|
request_idrequired | string | The X-Engine-Request-Id returned by /v1/audio/speech (or the X-Request-Id you set on that call). |
// 202 Accepted (empty body)invalid_api_keyBearer token missing, malformed, or revoked.curl -X POST https://api.openalchemy.io/v1/audio/speech/cancel \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"request_id": "<X-Engine-Request-Id>"}'Voices
#/v1/audio/voicesList built-in voices and your cloned voices, clone a voice from a short reference clip (zero-shot, usable immediately), fetch a clone's reference clip, or delete a clone.
OpenAlchemy extension. Cloned voices are private to your account.
/v1/audio/voicesBuilt-in and cloned voices: {data: [{id, name, kind, language}]}./v1/audio/voicesClone a voice (multipart form fields below)./v1/audio/voices/{voice_id}/clipA cloned voice's reference clip (audio_b64) and transcript. Your own clones only./v1/audio/voices/{voice_id}Delete one of your cloned voices. Built-in voices cannot be deleted.| Name | Type | Description |
|---|---|---|
filerequired | binary | Reference clip, WAV or MP3, up to 20 MB. |
namerequired | string | 1–64 characters: letters, digits, _ or -. Becomes the voice id. |
languagerequired | string | One of zh, en, ja, zh_en, ja_en, zh_ja. |
reference_textrequired | string | Exact transcript of the clip. |
// GET /v1/audio/voices
{ "data": [
{ "id": "en_alice", "name": "en_alice", "kind": "builtin", "language": "en" },
{ "id": "my_voice", "name": "my_voice", "kind": "cloned", "language": "ja" }
] }
// POST /v1/audio/voices
{ "id": "my_voice", "name": "my_voice", "kind": "cloned", "language": "ja", "status": "ready" }invalid_api_keyBearer token missing, malformed, or revoked.invalid_namename is not 1–64 letters, digits, _ or -.invalid_languagelanguage is not one of the supported values.not_foundNo cloned voice with that id in your account.curl https://api.openalchemy.io/v1/audio/voices \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-F file=@reference.wav \
-F name=my_voice \
-F language=ja \
-F reference_text="こんにちは、今日はいい天気ですね。"Audio watermark
#/v1/audio/watermark/embedEmbed an inaudible 16-bit watermark into audio, or detect one (AudioSeal). Billed per audio second.
OpenAlchemy extension (not an OpenAI endpoint).
/v1/audio/watermark/embedReturns the watermarked file itself, in the same container you sent (audio/mpeg or audio/wav). Request id, message and cost are in the response headers./v1/audio/watermark/detectReturns whether the audio carries a watermark, the confidence and the decoded message.| Name | Type | Description |
|---|---|---|
audio_b64required | string | Base64 of WAV or MP3 audio (up to 50 MB); a data: URL prefix is accepted. |
message | integer 0–65535 | embed only (required there): the 16-bit value to embed. Mapping it to your content is up to you. |
// embed → audio bytes; headers:
// X-OpenAlchemy-Request-Id, X-OpenAlchemy-Cost,
// X-Watermark-Message, X-Audio-Seconds
// detect →
{ "watermarked": true, "confidence": 0.98, "message": 4242,
"x_openalchemy": { "request_id": "req_…", "worker_id": "…",
"usage": { "audio_seconds": 12, "cost": "…" } } }missing_audioaudio_b64 is missing or empty.bad_messageembed: message must be an integer 0–65535.invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.audio_too_largeAudio exceeds 50 MB.curl https://api.openalchemy.io/v1/audio/watermark/embed \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"audio_b64\": \"$(base64 -w0 song.mp3)\", \"message\": 4242}" \
-o song.watermarked.mp3Image generation
#/v1/images/local/generationsGenerate one PNG on Grid compute. Returns JSON when generation finishes; no streaming.
OpenAI Images request/response shape on a custom path: with an SDK, call client.post('/images/local/generations', …) — images.generate() targets a different path.
| Name | Type | Description |
|---|---|---|
model | string | An image model from /v1/models (endpoint_type=image). |
promptrequired | string | Image description, 1–32,000 characters. |
n | integerdefault 1 | Must be 1 (one image per request). |
size | stringdefault 1024x1024 | WIDTHxHEIGHT, multiples of 16, up to 1536 per side. |
response_format | stringdefault b64_json | Only b64_json. Decode data[0].b64_json to get the PNG. |
seed | integer | 0–4294967295. Random when omitted. |
steps | integerdefault 4 | Sampling steps, 1–50. |
guidance_scale | numberdefault 4 | Guidance scale, 0–30. |
{
"created": 1713833628,
"data": [{ "b64_json": "<base64 PNG>" }],
"x_openalchemy": {
"request_id": "…", "worker_id": "…", "provider": "local",
"usage": { "images": 1, "cost": "…" }
}
}invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.validation_errorUnsupported or invalid parameter (quality, background, output_format, stream and masks are not supported).engine_unreachableThe inference engine could not be reached or timed out.curl https://api.openalchemy.io/v1/images/local/generations \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "a lighthouse at dusk, watercolor", "size": "1024x1024"}'Image editing
#/v1/images/local/editsGenerate a PNG from one reference image and a prompt. Send multipart/form-data; upload the reference again for each edit.
Single-reference subset of OpenAI Images edits on a custom path. No mask and no image arrays.
| Name | Type | Description |
|---|---|---|
imagerequired | file | One PNG, JPEG or WebP, up to 25 MiB (form field image). |
model | string | An image model from /v1/models (endpoint_type=image). |
promptrequired | string | Image description, 1–32,000 characters. |
n | integerdefault 1 | Must be 1 (one image per request). |
size | stringdefault 1024x1024 | WIDTHxHEIGHT, multiples of 16, up to 1536 per side. |
response_format | stringdefault b64_json | Only b64_json. Decode data[0].b64_json to get the PNG. |
seed | integer | 0–4294967295. Random when omitted. |
steps | integerdefault 4 | Sampling steps, 1–50. |
guidance_scale | numberdefault 4 | Guidance scale, 0–30. |
{
"created": 1713833628,
"data": [{ "b64_json": "<base64 PNG>" }],
"x_openalchemy": {
"request_id": "…", "worker_id": "…", "provider": "local",
"usage": { "images": 1, "cost": "…" }
}
}invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.validation_errorUnsupported or invalid parameter (quality, background, output_format, stream and masks are not supported).engine_unreachableThe inference engine could not be reached or timed out.empty_imageThe reference image is empty.image_too_largeThe reference image exceeds 25 MiB.curl https://api.openalchemy.io/v1/images/local/edits \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-F image=@input.png \
-F prompt="same scene, at night"Video generation
#/v1/videosRender one video shot on Grid compute. Models: minimax-h3 (4–15 s with sound, 768P; or still: true for one PNG keyframe), wan2.2-s2v (a singing / talking shot from a keyframe + audio, 480P / 576P / 720P), wan2.2-i2v (a keyframe moving as the prompt says, 2–10 s, 16 fps, 480P / 576P / 720P), wan2.2-animate (a keyframe character performing a driving video's motion, 480P) and latentsync-1.6 (re-sync the mouth of a finished clip to a new speech track). minimax-h3 also takes audio_url: a voice in mode ref, a soundtrack in t2v / i2v. Asynchronous: the POST returns 202 with an id; poll GET /v1/videos/{id} until status is succeeded, then download from output.url or GET /v1/videos/{id}/content.
OpenAlchemy extension shaped like Kie's MiniMax H3 API. Billed per output second at each model and resolution's price (a still: per image); the full estimate is held on submit and released if the shot fails or is cancelled. Beta.
/v1/videosSubmit a shot → 202 {id, status: "queued"}. With no worker online it fails at once with 503 no_video_worker and nothing is charged./v1/videosYour shots in this project, newest first. Query: limit (1–100, default 20), after (last_id of the previous page), status./v1/videos/availability?model=Whether a shot can start now: available, workers_online, workers_free, queue_depth. model: minimax-h3 (minimax-h3-ref for mode ref), wan2.2-s2v, wan2.2-i2v, wan2.2-animate or latentsync-1.6./v1/videos/{id}status, stage, progress (sampling percent 0–100), credits, and — once succeeded — output with a short-lived output.url./v1/videos/{id}/content302 redirect to a short-lived signed mp4 URL. 409 until the shot has succeeded; 410 after the retention period (default 7 days)./v1/videos/{id}/cancelCancel a queued or running shot; the held credits are released in full.| Name | Type | Description |
|---|---|---|
model | stringdefault "minimax-h3" | minimax-h3, wan2.2-s2v, wan2.2-i2v, wan2.2-animate or latentsync-1.6. |
mode | stringdefault "t2v" | minimax-h3: t2v (prompt only), i2v (first and/or last frame) or ref (1–9 reference images, addressed in the prompt as <Picture 1>, <Picture 2>…). wan2.2-s2v: s2v. wan2.2-i2v: i2v. wan2.2-animate: animate. latentsync-1.6: lipsync. |
promptrequired | string | What happens in the shot, including sound. Up to 8,000 characters. Not used by latentsync-1.6. |
duration | integer 4–15default 5 | minimax-h3: length in seconds (24 fps); omit for still. wan2.2-s2v: omit (the length is the audio, 2–45 s). wan2.2-i2v: 2–10 s (default 5, 16 fps). wan2.2-animate: optional, the first N seconds of the driving video (default the whole clip, 2–30 s). latentsync-1.6: omit (the length is the audio). |
resolution | stringdefault "768P" | minimax-h3: 768P only (2K returns 422). wan2.2-s2v: 480P (default), 576P or 720P, each at its own per-second price. wan2.2-i2v: 480P (default), 576P or 720P. wan2.2-animate: 480P. latentsync-1.6: omit (keeps the input video's size). |
aspect_ratio | stringdefault "16:9" | For t2v and ref; i2v follows its frame. |
seed | integer | Fix for repeatable output; random when omitted. |
first_frame_url | string | i2v (minimax-h3, wan2.2-i2v), s2v and animate: https URL, base64 or data: URL of the PNG/JPEG frame the shot starts from. |
last_frame_url | string | i2v: the frame the shot ends on. |
reference_image_urls | string[] (1–9) | ref: reference images (https, base64 or data: URLs). |
still | booleandefault false | minimax-h3 t2v / ref: render one PNG keyframe instead of a video (billed per image); optional control_image_url (pose / depth / lineart template) and control_strength 0–2. |
audio_url | string | wan2.2-s2v: driving audio (wav / mp3 / aac, https or data: URL, ≤ 50 MB, 2–45 s); first_frame_url is the keyframe. The audio is muxed into the 16 fps output. latentsync-1.6: the new speech track (wav / mp3 / aac / m4a, ≤ 20 MB, at least 0.5 s, at most 0.5 s longer than the video). minimax-h3 (≤ 30 s, not with still): in mode ref a voice the prompt calls <Audio 1>; in t2v / i2v a soundtrack guide anchored from the first frame (H3 still writes the final audio). The clip keeps duration. |
driving_video_url | string | wan2.2-animate: the motion to perform (mp4 / mov / webm, https or data: URL, ≤ 50 MB), same aspect ratio as the output (else 422 driving_aspect_mismatch); first_frame_url is the character. No audio in the output. |
video_url | string | latentsync-1.6: the clip to re-sync (mp4 / mov / webm, https or data: URL, ≤ 50 MB, ≤ 30 s). The picture is kept; only the mouth region changes. Output: 25 fps with audio_url muxed in. |
steps | integer 1–50 | Sampling steps instead of the model's default (minimax-h3 8, mode ref 4, wan2.2-s2v 4 — 20 with turbo false, wan2.2-i2v 6, wan2.2-animate 4). Changes render time and quality, not the price. |
turbo | booleandefault true | wan2.2-s2v only: false renders without the 4-step distillation LoRA (full sampler, 20 steps, CFG 6 — several times slower, closer to the base model). false on other models is 400 turbo_not_supported. |
// POST /v1/videos → 202
{ "id": "video_…", "object": "video", "status": "queued", "model": "minimax-h3",
"mode": "t2v", "duration": 5, "resolution": "768P", "estimated_credits": "…" }
// GET /v1/videos/{id}
{ "id": "video_…", "object": "video",
"status": "running", // queued | running | uploading | succeeded | failed | cancelled
"stage": "rendering", // queued | evicting | uploading_frames | rendering | decoding | storing; null once finished
"progress": 37, // sampling percent 0–100 (0 until sampling starts, 100 when succeeded)
"credits": null, // settled credits once succeeded
"output": null, // when succeeded:
// { "mime_type": "video/mp4", "duration": 5, "width": 1344, "height": 768, "fps": 24,
// "has_audio": true, "bytes": 2400000, "seed": 7, "expires_at": 1760600000,
// "url": "https://…signed…", "url_expires_in": 900 }
"error": null } // { "code", "message" } when failed / cancelled
// GET /v1/videos?limit=20&after=video_…&status=succeeded
{ "object": "list", "data": [ /* same shape as GET /v1/videos/{id} */ ],
"has_more": true, "first_id": "video_…", "last_id": "video_…" }
// GET /v1/videos/availability?model=minimax-h3
{ "object": "video.availability", "model": "minimax-h3",
"available": true, "workers_online": 2, "workers_free": 1, "queue_depth": 0 }invalid_durationduration is outside 4–15 seconds.invalid_modemode is not t2v, i2v or ref.missing_promptprompt is empty.invalid_stepssteps is outside 1–50.turbo_not_supportedturbo: false was sent for a model other than wan2.2-s2v.invalid_resolutionresolution is not one the model supports (see resolution).driving_aspect_mismatchwan2.2-animate: the driving video's aspect ratio differs from aspect_ratio.audio_needs_s2vaudio_url was sent with still: true, or to a model that takes no audio.audio_longer_than_videolatentsync-1.6: the audio is longer than the video by more than 0.5 s.invalid_afterList: after is not the id of one of your videos.invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceNot enough credits for the estimated cost.model_not_allowedThis API key isn't authorised for the requested model.not_foundVideo generation is not enabled for this account yet, or the id is unknown.video_not_readyContent requested before the shot succeeded.video_expiredThe output is past its retention period.resolution_not_supported2K was requested; only 768P is available.reference_video_not_supportedreference_video_urls are not supported yet.no_video_workerNo GPU worker is online for this model / mode right now. Returned immediately; nothing is charged.# 1. Submit
curl https://api.openalchemy.io/v1/videos \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "minimax-h3", "mode": "t2v", "prompt": "waves on a pebble beach, gulls calling", "duration": 5}'
# 2. Poll until "status": "succeeded"
curl https://api.openalchemy.io/v1/videos/video_… \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY"
# 3. Download
curl -L https://api.openalchemy.io/v1/videos/video_…/content \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" -o shot.mp4OCR
#/v1/ocrExtract text from an image or PDF. PDFs are rasterised and recognised page by page (up to 50 pages). Billed per page.
OpenAlchemy extension (not an OpenAI endpoint). To ask questions about an image, use /v1/chat/completions with a vision model.
| Name | Type | Description |
|---|---|---|
model | stringdefault "paddleocr" | paddleocr (fast, returns per-line boxes) or a vision model id from /v1/models. |
image_b64required | string | Base64 of an image (PNG/JPG/WebP) or a PDF; a data: URL prefix is accepted. Alias: image. |
lang | string | PaddleOCR language code (ch, en, japan, korean, …). Ignored by vision models. |
{
"text": "…full recognised text…",
// PaddleOCR — per-line text, confidence and box:
"lines": [ { "text": "…", "score": 0.99, "box": [[x0,y0],[x1,y1],[x2,y2],[x3,y3]] } ],
"page_count": 1,
// PDFs — one entry per page:
"pages": [ { "page_num": 1, "text": "…" } ],
"x_openalchemy": { "request_id": "req_…", "worker_id": "…", "usage": { "pages": 1, "cost": "…" } }
}missing_imageimage_b64 (or image) is missing or empty.bad_base64The payload is not valid base64.bad_pdfThe PDF could not be opened.invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.too_many_pagesThe PDF has more than 50 pages.engine_unreachableThe inference engine could not be reached or timed out.curl https://api.openalchemy.io/v1/ocr \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"model\": \"paddleocr\", \"lang\": \"japan\", \"image_b64\": \"$(base64 -w0 receipt.png)\"}"Garment segmentation
#/v1/garment/segmentFind the garments in a photo and where each one is (SegFormer clothes parsing). One anchor per garment: the centroid of its mask. Billed per image.
OpenAlchemy extension (not an OpenAI endpoint).
| Name | Type | Description |
|---|---|---|
model | stringdefault "segformer-b2-clothes" | segformer-b2-clothes (faster) or segformer-b3-clothes. |
image_b64required | string | Base64 of a PNG/JPG/WebP photo (up to 25 MB); a data: URL prefix is accepted. Alias: image. |
min_area_ratio | number 0–1 | Garments whose mask covers less than this share of the image are not reported. |
{
"model": "segformer-b2-clothes",
"width": 1000, "height": 1500,
// label: Dress | Upper-clothes | Skirt | Pants | Bag | Shoes | Hat | Scarf | Belt
"garments": [
{ "label": "Upper-clothes", "x": 473.2, "y": 739.5, "x_norm": 0.4732, "y_norm": 0.493,
"area_ratio": 0.0417, "bbox": [376.3, 534.7, 578.3, 931.6] }
],
"x_openalchemy": { "request_id": "req_…", "worker_id": "…", "usage": { "images": 1, "cost": "…" } }
}missing_imageimage_b64 (or image) is missing or empty.bad_base64The payload is not valid base64.invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.image_too_largeThe image exceeds 25 MB.engine_unreachableThe inference engine could not be reached or timed out.curl https://api.openalchemy.io/v1/garment/segment \
-H "Authorization: Bearer $OPENALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"image_b64\": \"$(base64 -w0 outfit.jpg)\"}"Errors
#Errors follow the OpenAI shape: a JSON body with an error object carrying message, type, and code. Branch on code, not on the HTTP status — the same status can carry several codes.
invalid_api_keyBearer token missing, malformed, or revoked.insufficient_balanceEstimated cost exceeds spendable balance.model_not_allowedThis API key isn't authorised for the requested model.model_not_foundModel id is not registered.rate_limitedPer-key RPM or TPM limit exceeded.no_workers_for_modelNo grid worker is currently serving this model.model_not_pricedNo credit price has been published for this model's tier yet.engine_unreachableThe inference engine could not be reached or timed out.{
"error": {
"message": "Insufficient credit balance for this request.",
"type": "billing_error",
"code": "insufficient_balance"
}
}Rate limits
#Rate limits are set per API key in two dimensions: RPM (requests per minute) and TPM (tokens per minute, summed across input + output). Both default to a free-tier ceiling; tiers expand as you top up credits.
When you exceed a limit we return 429 rate_limited with a Retry-After header (seconds). The OpenAI SDKs honour this automatically; for custom clients, back off and retry.
Sustained 503 no_workers_for_model on a specific model is a capacity signal, not a rate-limit signal — pick a peer model from /models or open an issue so we can route additional providers to it.
x_openalchemy.request_id on the response is what we will ask for first.