PL Log inCreate account
Launching in October 2026. Accounts, model catalog, calculator and docs are live. Studio is available in preview; generation is awaiting activation.

One API for AI Models: Image, Video, Music and Chat

How one API key and one prepaid balance cover chat, image, video and music models. Endpoints, working code, cost checks and failure handling.

A unified AI API gives you one key, one balance and one request format for many models: chat models answer through an OpenAI-compatible endpoint, while image, video and music models run as async tasks that you submit and then collect. With Jantaris that means POST /v1/chat/completions for text and POST /v1/generate plus GET /v1/tasks/{id} for everything else. You switch models by changing one string, not by signing up for another vendor.

I built Jantaris because my own projects kept collecting API keys. A chat model from one company, an image model from another, a video model from a third, each with its own billing page, its own minimum top-up and its own idea of what an error looks like. This post shows how the single-API approach works in practice, with code you can run today.

Why use one API for multiple AI models

The honest reason is less plumbing. Each vendor SDK is fine on its own. The trouble starts when a project needs three of them: three auth schemes, three retry policies, three places where the card can expire.

With one API for AI models you get:

  • One key (sk-jt-...) that works for Claude Sonnet 5, GPT Image 2.5, Seedance 2.5 and Mureka V9 alike.
  • One prepaid balance. No subscription, no minimum commitment, and the balance does not expire.
  • One price format. Every model has a price per unit: per image, per second of video, per 1M tokens. You see it on the pricing page before you call anything.
  • One error format. Every error is { "error": { "type": "...", "message": "..." } } with a predictable HTTP code.

There is a practical bonus too. When a model is available from two infrastructure sources, Jantaris can fail over automatically for part of the catalog. Your code does not change.

How a unified AI API works: two request styles

AI models fall into two groups by how long they take.

Model typeTypical timeRequest styleEndpoint
Chat and codeseconds, streamedsynchronousPOST /v1/chat/completions or POST /v1/messages
Imagea few seconds to a minuteasync task, or sync with ?wait=POST /v1/generate
Videoone to several minutesasync task + polling or webhookPOST /v1/generate
Music and audiotens of seconds to minutesasync taskPOST /v1/generate

Chat stays synchronous because the OpenAI and Anthropic formats are what every tool already speaks. Media generation is async because a 10-second video clip can take minutes, and holding an HTTP connection open that long breaks on proxies, serverless functions and mobile networks.

The full endpoint list

MethodPathWhat it does
POST/v1/chat/completionsChat, OpenAI-compatible, stream: true supported
POST/v1/messagesChat, Anthropic Messages-compatible (Claude Code uses this)
POST/v1/generateSubmit an image, video or audio job, returns a task (202)
GET/v1/tasks/{id}Task status and output
GET/v1/modelsPublic model catalog with prices
GET/v1/models/{id}One model's details and parameter schema
GET/v1/balanceYour current balance

The base URL is https://jantaris.com/v1 and the key goes in Authorization: Bearer sk-jt-... (or x-api-key). Details are in authentication.

Chat models through the OpenAI format

If you have ever used the OpenAI SDK, you already know this part. Change the base URL, keep the rest.

python
from openai import OpenAI

client = OpenAI(
    base_url="https://jantaris.com/v1",
    api_key="sk-jt-...",
)

resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "Write a product name for a ceramic kettle."}],
)
print(resp.choices[0].message.content)

Swap claude-sonnet-5 for gpt-6-sol, gemini-3.8-flash or deepseek-v4.1-flash and the same code runs. I cover this pattern in depth, including streaming and model fallback, in Call Claude, Gemini, DeepSeek and GPT with the OpenAI SDK.

Image, video and music through one generate endpoint

Every media model uses the same body shape: a model id and an input object. The inputs share names across models where the idea is the same.

  • Image: prompt, negative_prompt, aspect_ratio (1:1, 16:9, 9:16, 4:3, 3:4), num_images (1 to 4), image_urls for edits and references, seed
  • Video: prompt, duration in seconds, resolution (480p, 720p or 1080p depending on the model), aspect_ratio, image_url for the first frame, generate_audio
  • Music and audio: prompt, duration, lyrics, instrumental

Model-specific options live in the schema at GET /v1/models/{id}. Check it before you rely on a parameter.

Submitting a video task

bash
curl -X POST https://jantaris.com/v1/generate \
  -H "Authorization: Bearer sk-jt-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5",
    "input": {
      "prompt": "A lighthouse at dawn, slow aerial push-in, waves breaking on rocks",
      "duration": 5,
      "resolution": "720p",
      "aspect_ratio": "16:9"
    }
  }'

The response comes back immediately with status 202:

json
{
  "id": "task_8f2k...",
  "object": "task",
  "status": "queued",
  "model": "seedance-2.5",
  "estimated_cost": { "amount": "X.XX", "currency": "USD" },
  "poll_url": "https://jantaris.com/v1/tasks/task_8f2k..."
}

Note estimated_cost. The price is calculated before the job is sent to the model, so you know what a request costs before it runs. That amount is held on your balance while the task works.

Collecting the result

python
import time
import requests

API = "https://jantaris.com/v1"
HEADERS = {"Authorization": "Bearer sk-jt-..."}

def wait_for(task_id, every=5, timeout=900):
    deadline = time.time() + timeout
    while time.time() < deadline:
        task = requests.get(f"{API}/tasks/{task_id}", headers=HEADERS, timeout=30).json()
        if task["status"] in ("succeeded", "failed"):
            return task
        time.sleep(every)
    raise TimeoutError(task_id)

task = wait_for("task_8f2k...")
if task["status"] == "succeeded":
    for item in task["output"]:
        print(item["type"], item["url"])
else:
    print("failed:", task["error"])

A finished task looks like this:

json
{
  "id": "task_...",
  "status": "succeeded",
  "model": "seedance-2.5",
  "output": [{ "type": "video", "url": "https://...", "duration": 5, "width": 1280, "height": 720 }],
  "cost": { "amount": "X.XX", "currency": "USD" },
  "error": null
}

For production pipelines, skip polling and pass a webhook_url. I walk through signature checks and retries in Building an AI video pipeline with async tasks and webhooks.

Images without polling: the wait parameter

Images usually finish in seconds, so polling is overkill. Add ?wait=60 (max 120) and the server holds the request until the task is done, then returns the finished task in one response.

python
r = requests.post(
    f"{API}/generate?wait=60",
    headers=HEADERS,
    json={
        "model": "nano-banana-pro",
        "input": {"prompt": "Minimal poster, the word NORTH in bold serif", "aspect_ratio": "3:4"},
    },
    timeout=130,
)
task = r.json()
print(task["status"], task.get("output"))

Always check status in the response. If it is not succeeded yet, fall back to polling the task by its id.

Music in the same format

javascript
const res = await fetch("https://jantaris.com/v1/generate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk-jt-...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "mureka-v9",
    input: {
      prompt: "Warm lo-fi hip hop, soft piano, vinyl crackle, 80 bpm",
      lyrics: "[Verse]
City lights are fading slow
[Chorus]
Stay a little longer",
    },
  }),
});
const task = await res.json();
console.log(task.id, task.estimated_cost);

Same key, same task object, same polling code as the video above.

What happens when a generation fails

This is the part that matters most once you run real volume. Video models fail sometimes: a content filter trips, a provider times out, a job gets stuck.

On Jantaris a failed generation costs nothing. When a task ends as failed, the hold on your balance is released in full and cost is 0. You pay for results, not attempts.

Errors on submission come back as JSON with a clear code:

CodeMeaningWhat I do in code
401Bad or missing keyStop, fix config
402Balance too lowAlert, top up, retry later
404Model id does not existCheck /v1/models
422Invalid parametersLog error.message, fix input
429Rate limitBack off and retry
502Upstream provider errorRetry with backoff

The full list is in errors.

Checking prices and balance from code

The model catalog is public, so you can read prices without a key:

bash
curl https://jantaris.com/v1/models
curl https://jantaris.com/v1/models/seedance-2.5

And your balance:

bash
curl https://jantaris.com/v1/balance -H "Authorization: Bearer sk-jt-..."

A few current rates for reference: Seedance 2.5 runs at from $0.261 per second, GPT Image 2.5 at $0.0432 per image, and Claude Sonnet 5 at $3.24 / $16.20 per 1M tokens. The live list is on the pricing page. Top-ups start at $10, with ready-made packs of $10, $25, $50, $100 and $250.

Things to know before you build on it

  • Download your outputs. Result links are valid for a limited time. Copy files to your own storage as soon as a task succeeds. The Studio keeps a generation history on your account, the API assumes you store files yourself.
  • Your data stays yours. Prompts and outputs are not used to train models.
  • Use the schema. Not every video model supports every resolution or duration. GET /v1/models/{id} tells you what the model accepts, and a wrong value returns 422 before anything is charged.
  • Not a coder? The same models are in the browser Studio, on the same balance.

If you want to start now, the quickstart takes about five minutes from sign-up to first video.

FAQ

What is a unified AI API?

It is a single API that routes requests to many AI models from different vendors. You use one key, one billing balance and one request format, and you choose the model with a model parameter instead of integrating each vendor separately.

Can I use the OpenAI SDK with Jantaris?

Yes. Set base_url="https://jantaris.com/v1" and your sk-jt-... key. The /v1/chat/completions endpoint is OpenAI-compatible, including streaming, and /v1/messages is Anthropic-compatible for tools like Claude Code.

Do I pay for failed image or video generations?

No. If a task ends with status failed, its cost is 0 and the amount held on your balance is released in full.

How do I know what a request will cost?

Every model has a public per-unit price in /v1/models and on the pricing page. When you submit a generation, the 202 response includes estimated_cost, calculated before the job runs.

Does my balance expire?

No. The balance is prepaid, has no expiry date, and there is no subscription or minimum commitment.