OpenAlchemyOpenAlchemy
Docs

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_openalchemy block 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.

Header
Authorization: Bearer $OPENALCHEMY_API_KEY

Base URL

#

The only network endpoint you need is:

HTTPShttps://api.openalchemy.io

Append /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 SDK
# 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.

Response envelope
{
  "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.

List models

#
GET/v1/models

Returns 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.

Request
curl https://api.openalchemy.io/v1/models \
  -H "Authorization: Bearer $OPENALCHEMY_API_KEY"
Response
{
  "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

#
POST/v1/chat/completions

Generate 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.

Request body
NameTypeDescription
modelrequiredstringModel id from /v1/models.
messagesrequiredarray<{role, content}>Conversation turns. role is system / user / assistant.
temperaturenumber 0–2
default 1
Sampling temperature.
top_pnumber 0–1
default 1
Nucleus sampling cumulative probability.
max_tokensinteger
default 2048
Maximum tokens to generate.
presence_penaltynumber -2…2
default 0
Penalise tokens already present in the text.
frequency_penaltynumber -2…2
default 0
Penalise tokens in proportion to their frequency.
stopstring | string[]Sequences that halt generation.
response_format{ type: 'text' | 'json_object' }Force JSON output.
streamboolean
default false
Stream the response as server-sent events; the final chunk carries usage.
Response
{
  "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" }
  }
}
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_not_pricedNo credit price has been published for this model's tier yet.
502engine_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
}'
Try it in the playground→
Tweak the request live and copy the resulting code back here.

Vision

#
POST/v1/chat/completions

Same endpoint as chat completions, with image content parts. Use a vision-capable model.

OpenAI Vision-style — pick a vision-capable model from /v1/models.

Request body
NameTypeDescription
modelrequiredstringA vision-capable model id.
messagesrequiredarray<{role, content: (string | ContentPart[])}>Each content part is {type:'text', text} or {type:'image_url', image_url:{url, detail}}.
image_url.urlstringhttps://… 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).
temperaturenumber 0–2
default 0.2
Same as chat.
max_tokensinteger
default 1024
Same as chat.
Response
// Same shape as /v1/chat/completions.
// x_openalchemy.usage.image_tokens is the per-request image token total.
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_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
}'
Try it in the playground→
Tweak the request live and copy the resulting code back here.

Embeddings

#
POST/v1/embeddings

Turn one or many strings into dense vectors for search, clustering, or RAG.

OpenAI Embeddings v1 compatible.

Request body
NameTypeDescription
modelrequiredstringEmbedding model id.
inputrequiredstring | string[]One or many strings to embed.
dimensionsintegerFor Matryoshka-trained models, truncate the vector to this many dimensions. Ignored otherwise.
encoding_format'float' | 'base64'
default 'float'
Wire format for the returned vectors.
Response
{
  "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": "…" } }
}
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_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"
  ]
}'
Try it in the playground→
Tweak the request live and copy the resulting code back here.

Reranking

#
POST/v1/rerank

Rerank N candidate documents against a query. Cohere-compatible request shape.

Cohere /v2/rerank compatible (`model`, `query`, `documents`, `top_n`).

Request body
NameTypeDescription
modelrequiredstringReranker model id.
queryrequiredstringThe search query.
documentsrequiredstring[] | { text: string }[]Candidate documents to rerank.
top_nintegerReturn only the top-N results. Omit for all.
return_documentsboolean
default false
Include each document's text in the response.
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": "…"
    }
  }
}
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_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
}'
Try it in the playground→
Tweak the request live and copy the resulting code back here.

Audio transcriptions

#
POST/v1/audio/transcriptions

Speech-to-text. Multipart upload of an audio file, Whisper-compatible response.

OpenAI Whisper v1 compatible. multipart/form-data.

Form fields (multipart/form-data)
NameTypeDescription
filerequiredbinarymp3 / wav / m4a / webm / flac. Up to 25 MB per request.
modelrequiredstringSpeech-to-text model id.
languagestringISO-639-1 (e.g. 'en', 'ja'). Omit for auto-detect.
promptstringOptional bias text. Useful for technical vocabulary.
response_format'json' | 'text' | 'srt' | 'vtt' | 'verbose_json'
default 'json'
Output format.
temperaturenumber 0–1
default 0
Sampling temperature (rarely needed).
Response
// 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.
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_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=json
Try it in the playground→
Tweak the request live and copy the resulting code back here.

Audio speech

#
POST/v1/audio/speech

Text-to-speech. Returns binary audio bytes with usage metadata in response headers.

OpenAI TTS v1 compatible. WAV output today.

Request body
NameTypeDescription
modelrequiredstringText-to-speech model id.
inputrequiredstringText to synthesise.
voicestringVoice 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.
speednumber 0.5–2.0
default 1
Playback speed multiplier.
streamboolean
default false
Relay chunked WAV as it is generated (low-latency playback). Audio already generated is billed if you disconnect.
Response
// 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)
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_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.wav
Try it in the playground→
Tweak the request live and copy the resulting code back here.

Cancel speech

#
POST/v1/audio/speech/cancel

Stop 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).

Request body
NameTypeDescription
request_idrequiredstringThe X-Engine-Request-Id returned by /v1/audio/speech (or the X-Request-Id you set on that call).
Response
// 202 Accepted (empty body)
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
cURL
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

#
POST/v1/audio/voices

List 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.

Endpoints
GET/v1/audio/voicesBuilt-in and cloned voices: {data: [{id, name, kind, language}]}.
POST/v1/audio/voicesClone a voice (multipart form fields below).
GET/v1/audio/voices/{voice_id}/clipA cloned voice's reference clip (audio_b64) and transcript. Your own clones only.
DELETE/v1/audio/voices/{voice_id}Delete one of your cloned voices. Built-in voices cannot be deleted.
Form fields (multipart/form-data)
NameTypeDescription
filerequiredbinaryReference clip, WAV or MP3, up to 20 MB.
namerequiredstring1–64 characters: letters, digits, _ or -. Becomes the voice id.
languagerequiredstringOne of zh, en, ja, zh_en, ja_en, zh_ja.
reference_textrequiredstringExact transcript of the clip.
Response
// 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" }
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
400invalid_namename is not 1–64 letters, digits, _ or -.
400invalid_languagelanguage is not one of the supported values.
404not_foundNo cloned voice with that id in your account.
cURL
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

#
POST/v1/audio/watermark/embed

Embed an inaudible 16-bit watermark into audio, or detect one (AudioSeal). Billed per audio second.

OpenAlchemy extension (not an OpenAI endpoint).

Endpoints
POST/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.
POST/v1/audio/watermark/detectReturns whether the audio carries a watermark, the confidence and the decoded message.
Request body
NameTypeDescription
audio_b64requiredstringBase64 of WAV or MP3 audio (up to 50 MB); a data: URL prefix is accepted.
messageinteger 0–65535embed only (required there): the 16-bit value to embed. Mapping it to your content is up to you.
Response
// 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": "…" } } }
Errors
400missing_audioaudio_b64 is missing or empty.
400bad_messageembed: message must be an integer 0–65535.
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
413audio_too_largeAudio exceeds 50 MB.
cURL
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.mp3

Image generation

#
POST/v1/images/local/generations

Generate 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.

Request body
NameTypeDescription
modelstringAn image model from /v1/models (endpoint_type=image).
promptrequiredstringImage description, 1–32,000 characters.
ninteger
default 1
Must be 1 (one image per request).
sizestring
default 1024x1024
WIDTHxHEIGHT, multiples of 16, up to 1536 per side.
response_formatstring
default b64_json
Only b64_json. Decode data[0].b64_json to get the PNG.
seedinteger0–4294967295. Random when omitted.
stepsinteger
default 4
Sampling steps, 1–50.
guidance_scalenumber
default 4
Guidance scale, 0–30.
Response
{
  "created": 1713833628,
  "data": [{ "b64_json": "<base64 PNG>" }],
  "x_openalchemy": {
    "request_id": "…", "worker_id": "…", "provider": "local",
    "usage": { "images": 1, "cost": "…" }
  }
}
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
422validation_errorUnsupported or invalid parameter (quality, background, output_format, stream and masks are not supported).
502engine_unreachableThe inference engine could not be reached or timed out.
cURL
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

#
POST/v1/images/local/edits

Generate 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.

Form fields (multipart/form-data)
NameTypeDescription
imagerequiredfileOne PNG, JPEG or WebP, up to 25 MiB (form field image).
modelstringAn image model from /v1/models (endpoint_type=image).
promptrequiredstringImage description, 1–32,000 characters.
ninteger
default 1
Must be 1 (one image per request).
sizestring
default 1024x1024
WIDTHxHEIGHT, multiples of 16, up to 1536 per side.
response_formatstring
default b64_json
Only b64_json. Decode data[0].b64_json to get the PNG.
seedinteger0–4294967295. Random when omitted.
stepsinteger
default 4
Sampling steps, 1–50.
guidance_scalenumber
default 4
Guidance scale, 0–30.
Response
{
  "created": 1713833628,
  "data": [{ "b64_json": "<base64 PNG>" }],
  "x_openalchemy": {
    "request_id": "…", "worker_id": "…", "provider": "local",
    "usage": { "images": 1, "cost": "…" }
  }
}
Errors
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
422validation_errorUnsupported or invalid parameter (quality, background, output_format, stream and masks are not supported).
502engine_unreachableThe inference engine could not be reached or timed out.
400empty_imageThe reference image is empty.
413image_too_largeThe reference image exceeds 25 MiB.
cURL
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

#
POST/v1/videos

Render 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.

Endpoints
POST/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.
GET/v1/videosYour shots in this project, newest first. Query: limit (1–100, default 20), after (last_id of the previous page), status.
GET/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.
GET/v1/videos/{id}status, stage, progress (sampling percent 0–100), credits, and — once succeeded — output with a short-lived output.url.
GET/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).
POST/v1/videos/{id}/cancelCancel a queued or running shot; the held credits are released in full.
Request body
NameTypeDescription
modelstring
default "minimax-h3"
minimax-h3, wan2.2-s2v, wan2.2-i2v, wan2.2-animate or latentsync-1.6.
modestring
default "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.
promptrequiredstringWhat happens in the shot, including sound. Up to 8,000 characters. Not used by latentsync-1.6.
durationinteger 4–15
default 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).
resolutionstring
default "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_ratiostring
default "16:9"
For t2v and ref; i2v follows its frame.
seedintegerFix for repeatable output; random when omitted.
first_frame_urlstringi2v (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_urlstringi2v: the frame the shot ends on.
reference_image_urlsstring[] (1–9)ref: reference images (https, base64 or data: URLs).
stillboolean
default 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_urlstringwan2.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_urlstringwan2.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_urlstringlatentsync-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.
stepsinteger 1–50Sampling 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.
turboboolean
default 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.
Response
// 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 }
Errors
400invalid_durationduration is outside 4–15 seconds.
400invalid_modemode is not t2v, i2v or ref.
400missing_promptprompt is empty.
400invalid_stepssteps is outside 1–50.
400turbo_not_supportedturbo: false was sent for a model other than wan2.2-s2v.
400invalid_resolutionresolution is not one the model supports (see resolution).
422driving_aspect_mismatchwan2.2-animate: the driving video's aspect ratio differs from aspect_ratio.
400audio_needs_s2vaudio_url was sent with still: true, or to a model that takes no audio.
400audio_longer_than_videolatentsync-1.6: the audio is longer than the video by more than 0.5 s.
400invalid_afterList: after is not the id of one of your videos.
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceNot enough credits for the estimated cost.
403model_not_allowedThis API key isn't authorised for the requested model.
404not_foundVideo generation is not enabled for this account yet, or the id is unknown.
409video_not_readyContent requested before the shot succeeded.
410video_expiredThe output is past its retention period.
422resolution_not_supported2K was requested; only 768P is available.
422reference_video_not_supportedreference_video_urls are not supported yet.
503no_video_workerNo GPU worker is online for this model / mode right now. Returned immediately; nothing is charged.
cURL
# 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.mp4

OCR

#
POST/v1/ocr

Extract 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.

Request body
NameTypeDescription
modelstring
default "paddleocr"
paddleocr (fast, returns per-line boxes) or a vision model id from /v1/models.
image_b64requiredstringBase64 of an image (PNG/JPG/WebP) or a PDF; a data: URL prefix is accepted. Alias: image.
langstringPaddleOCR language code (ch, en, japan, korean, …). Ignored by vision models.
Response
{
  "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": "…" } }
}
Errors
400missing_imageimage_b64 (or image) is missing or empty.
400bad_base64The payload is not valid base64.
400bad_pdfThe PDF could not be opened.
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
413too_many_pagesThe PDF has more than 50 pages.
502engine_unreachableThe inference engine could not be reached or timed out.
cURL
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

#
POST/v1/garment/segment

Find 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).

Request body
NameTypeDescription
modelstring
default "segformer-b2-clothes"
segformer-b2-clothes (faster) or segformer-b3-clothes.
image_b64requiredstringBase64 of a PNG/JPG/WebP photo (up to 25 MB); a data: URL prefix is accepted. Alias: image.
min_area_rationumber 0–1Garments whose mask covers less than this share of the image are not reported.
Response
{
  "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": "…" } }
}
Errors
400missing_imageimage_b64 (or image) is missing or empty.
400bad_base64The payload is not valid base64.
401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
413image_too_largeThe image exceeds 25 MB.
502engine_unreachableThe inference engine could not be reached or timed out.
cURL
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.

401invalid_api_keyBearer token missing, malformed, or revoked.
402insufficient_balanceEstimated cost exceeds spendable balance.
403model_not_allowedThis API key isn't authorised for the requested model.
404model_not_foundModel id is not registered.
429rate_limitedPer-key RPM or TPM limit exceeded.
503no_workers_for_modelNo grid worker is currently serving this model.
503model_not_pricedNo credit price has been published for this model's tier yet.
502engine_unreachableThe inference engine could not be reached or timed out.
Error body
{
  "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.

Something missing? Drop notes in /logs when a request misbehaves — the x_openalchemy.request_id on the response is what we will ask for first.