# AGENTS.md - Jantaris API

Jantaris is a prepaid API for image, video, audio and chat models. One key, one balance (USD or PLN).
Docs: https://jantaris.com/docs - model list: https://jantaris.com/models - Polish docs: https://jantaris.com/pl/dokumentacja

## Setup

- Base URL: `https://jantaris.com/v1`
- Auth: `Authorization: Bearer $JANTARIS_API_KEY` (or `x-api-key`). Keys start with `sk-jt-`.
- Keep the key in the environment variable `JANTARIS_API_KEY`. Never commit it.
- All bodies are JSON (`Content-Type: application/json`). Amounts are strings in the account currency.

## Cost rules

- Every generation is reserved up front (`estimated_cost`) and charged on success (`cost`).
- A failed generation costs 0 - the reserve is released in full.
- Check balance: `GET /v1/balance` -> `{ balance, available, currency, low_balance }`.
- No balance -> HTTP 402 `insufficient_balance`. Key over its monthly limit -> HTTP 403.
- Before any paid generation, show the user the estimated cost and wait for approval.

## Models

- `GET /v1/models` (optional `?category=video|image|audio|chat|tool`, `?currency=USD|PLN`) - no key needed.
- `GET /v1/models/{id}` - full `input_schema` and pricing. Read it before sending `input`; unknown fields are rejected (422).

## Media: image, video, audio

`POST /v1/generate` with `{ "model": "...", "input": { ... }, "webhook_url"?: "https://..." }` creates a task.

- Without `?wait` -> 202 and a task in `queued`/`running`.
- With `?wait=N` (seconds, max 120) the server holds the request until the task ends -> 200 when done, 202 if still running.
- Poll `GET /v1/tasks/{id}` every 3-10 s until `status` is `succeeded` or `failed`.
- Result: `output[].url` (files are kept for a limited time, see `expires_at` - download them), `cost`, `error`.

```bash
# image (usually ready in under a minute)
curl "https://jantaris.com/v1/generate?wait=60" \
  -H "Authorization: Bearer $JANTARIS_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"nano-banana-pro","input":{"prompt":"Isometric cozy coffee shop, soft light","aspect_ratio":"16:9","resolution":"2k"}}'

# video - start, then poll
curl "https://jantaris.com/v1/generate" \
  -H "Authorization: Bearer $JANTARIS_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"seedance-2.5","input":{"prompt":"Drone shot over a foggy pine forest at sunrise","duration":5,"resolution":"720p","aspect_ratio":"16:9"}}'
curl "https://jantaris.com/v1/tasks/task_..." -H "Authorization: Bearer $JANTARIS_API_KEY"

# song with lyrics
curl "https://jantaris.com/v1/generate?wait=120" \
  -H "Authorization: Bearer $JANTARIS_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"mureka-v9","input":{"prompt":"indie pop, warm female vocal","lyrics":"[Verse]\nCity lights are humming low..."}}'
```

Task object (shortened):

```json
{ "id": "task_...", "status": "succeeded", "model": "nano-banana-pro",
  "output": [{ "type": "image", "url": "https://jantaris.com/media/task_.../0.png" }],
  "estimated_cost": { "amount": "...", "currency": "USD" },
  "cost": { "amount": "...", "currency": "USD" }, "error": null,
  "poll_url": "https://jantaris.com/v1/tasks/task_..." }
```

Webhooks: pass `webhook_url`; Jantaris POSTs the task object with `x-jantaris-event` (`task.succeeded` / `task.failed`)
and `x-jantaris-signature: sha256=<HMAC-SHA256 of the raw body with your webhook secret>`.

## Chat

- OpenAI format: `POST /v1/chat/completions` (streaming with `"stream": true`). Works with the OpenAI SDK: `base_url="https://jantaris.com/v1"`.
- Anthropic format: `POST /v1/messages` for Claude models. Works with the Anthropic SDK: `base_url="https://jantaris.com"`.
- Set `max_tokens` - without it the reserve covers the model maximum output.

```bash
curl https://jantaris.com/v1/chat/completions \
  -H "Authorization: Bearer $JANTARIS_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-5","max_tokens":800,"messages":[{"role":"user","content":"Summarize this diff in 3 bullets: ..."}]}'
```

## MCP server (for coding agents)

Endpoint: `https://jantaris.com/mcp` (Streamable HTTP, JSON-RPC over POST), auth with the same key.

Tools: `search_models`, `list_models`, `get_model`, `estimate_cost`, `generate`, `wait_for_task`, `get_task`,
`list_tasks`, `cancel_task`, `get_balance`, `upload_file`, `chat`, `search_docs`.
Prompts (recipes): `reel_9x16`, `product_ad`, `ugc_testimonial`, `image_to_video_from_file`, `podcast_cover`, `music_jingle`, `compare_models`.

Approve-then-run (use it for every paid generation):

1. `search_models` (e.g. `{"task":"text-to-video","aspect_ratio":"9:16","needs_audio":true}`) and pick a model.
2. `estimate_cost` with the exact input -> show the cost to the user and wait for an explicit OK.
3. `generate` with the same input plus `confirm_token` (from step 2) and `max_cost` (the approved amount).
   Changed input or a higher cost = the call is refused and nothing is charged.
4. Still running? `wait_for_task` (up to 240 s per call). Then give the user the links and offer to download them.

Local files (start frames, reference audio): `upload_file` with `data_base64` (up to 8 MB), `source_url`, or
`request_upload_url: true` and then `curl -T file.png "<upload_url>"` (PowerShell: `Invoke-WebRequest -Method Put -InFile file.png -Uri "<upload_url>"`).

- Claude Code: `claude mcp add --transport http jantaris https://jantaris.com/mcp --header "Authorization: Bearer $JANTARIS_API_KEY"`
- Codex CLI (`~/.codex/config.toml`):
  ```toml
  [mcp_servers.jantaris]
  url = "https://jantaris.com/mcp"
  bearer_token_env_var = "JANTARIS_API_KEY"
  ```
- Cursor (`.cursor/mcp.json`): `{ "mcpServers": { "jantaris": { "url": "https://jantaris.com/mcp", "headers": { "Authorization": "Bearer ${env:JANTARIS_API_KEY}" } } } }`

## Errors

JSON `{ "error": { "type", "message", "param"? } }`. 400/422 bad input (fix `input`), 401 bad key, 402 balance,
403 key limit, 404 unknown model or task, 429 rate limit (respect `retry-after`), 5xx temporary - retry later.
POST /v1/generate is not idempotent: after a timeout, check your tasks before sending the same request again.
