> Source page: https://tryaiapi.com/en/docs/ (Markdown version of this page)

Docs

# API Documentation

Integration guide for the Try AI API unified gateway: available models, quick start, endpoints, code examples, SDK setup, and FAQ.

**Text call · cURL**

```bash
curl https://tryaiapi.com/v1/chat/completions \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "messages": [
      {"role": "user", "content": "Hello"}
    ]
  }'
```

**Image generation · cURL**

```bash
curl https://tryaiapi.com/v1/images/generations \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2.5-sunburst",
    "prompt": "A minimalist studio product photo"
  }'
```

**Video model task · cURL**

```bash
curl https://tryaiapi.com/videos/v1/videos/generations \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0",
    "prompt": "A cinematic product shot",
    "duration": 5,
    "resolution": "480p",
    "aspect_ratio": "16:9"
  }'
```

## 01 One key for text, image, and video models

One place for Claude, ChatGPT, image generation, and video model endpoints, model ID lookup, and status; see the [models page](https://tryaiapi.com/en/models/) for the full catalog.

### Live model catalog

Model IDs are sourced from `GET /v1/models`; live pricing and groups are on the model market.

- [Open model market](https://tryaiapi.com/en/models/)
- [View endpoints](https://tryaiapi.com/en/docs/#surfaces)

- [**GET /v1/models** Query current callable model IDs](https://tryaiapi.com/en/docs/#surfaces)
- [**POST /v1/chat/completions** OpenAI-compatible text endpoint](https://tryaiapi.com/en/docs/#surfaces)
- [**POST /v1/messages** Anthropic Messages endpoint](https://tryaiapi.com/en/docs/#surfaces)
- [**POST /v1/images/generations** OpenAI-compatible image generation endpoint](https://tryaiapi.com/en/docs/#surfaces)
- [**POST /videos/v1/videos/generations** Video async task endpoint](https://tryaiapi.com/en/docs/#surfaces)
- [**GET /videos/v1/videos/jobs/{job_id}** Use the poll_url returned by submission to fetch the result](https://tryaiapi.com/en/docs/#surfaces)

API definition: [`/docs/openapi.json`](https://tryaiapi.com/docs/openapi.json). Model details follow live `/v1/models` and the [model market](https://tryaiapi.com/en/models/). To check public prices against actual billing yourself, see [Public price and API response consistency check](https://tryaiapi.com/en/docs/discovery-test/).

**curl · List models**

```bash
curl https://tryaiapi.com/v1/models \
  -H "Authorization: Bearer $TRYAIAPI_KEY"
```

## 02 Three steps to integrate

STEP 01

#### Request an API key

[Log in to the console](https://tryaiapi.com/sign-in?lng=en) to create an API key.

STEP 02

#### Choose an endpoint

Text models swap the SDK base URL; video models submit tasks through `/videos/v1/videos/generations`.

STEP 03

#### Review results and usage

Text requests return in real time; video tasks are polled until complete, then results can be downloaded and roll up into usage records.

## 03 Endpoints

GET

#### `/v1/models`

Query current callable model IDs before integrating. ChatGPT model IDs follow this list.

POST

#### `/v1/chat/completions`

OpenAI-compatible endpoint. After swapping the base URL, the OpenAI SDK, Cursor, Codex CLI, opencode, and LangChain can all call through here.

POST

#### `/v1/messages`

Anthropic Messages-native endpoint. Claude Code and the Anthropic SDK can switch directly to the Try AI API base URL.

POST

#### `/v1/images/generations`

OpenAI-compatible image generation endpoint. Supports per-image billed models (e.g. `gpt-image-2.5-sunburst`); see the model market for unit pricing. Nano Banana 2 (`gemini-3.1-flash-image`) is not served here: call it via Gemini-native `/v1beta/models/gemini-3.1-flash-image:generateContent` or `/v1/chat/completions` (the image is returned in the reply content).

POST

#### `/videos/v1/videos/generations`

Video model async task endpoint. Submit a prompt, duration, resolution, and aspect ratio to receive a job_id and poll_url.

GET

#### `/videos/v1/videos/jobs/{job_id}`

Prefer the `poll_url` returned by submission; it provides task status, completed output, failure details, and the video download URL.

POST

#### `/videos/api/v3/contents/generations/tasks`

Volcano Ark native-format entry for video models. `model` accepts the official IDs (`doubao-seedance-2-0-260128` / `doubao-seedance-2-0-fast-260128` / `doubao-seedance-2-5-260628`); unsupported top-level parameters return an explicit error and are never silently dropped.

GET

#### `/videos/api/v3/contents/generations/tasks/{id}`

Ark native-format task query entry; returns Ark status values (`queued` / `running` / `succeeded` / `failed` / `cancelled` / `expired`). On success `content.video_url` is the generation engine's official link (typically valid ~24 hours), identical to `video_url` on the standard endpoint. When a platform copy exists, `content.platform_video_url` and `content.platform_expires_at` are also included (kept for 7 days on our side; these two fields are returned additionally by this platform and are absent from the Ark native format).

Developers already using the Volcano Ark native request format can use the Ark-compatible endpoint above directly; see the specific differences from Ark native in the Video Model API Integration Guide's ["Ark-compatible endpoint: differences from Ark native"](https://tryaiapi.com/en/docs/video-model/#ark-diff).

## 04 Advanced scenario examples

Advanced text capabilities and video model async task examples — each scenario shown in cURL / Python / JavaScript.

- [Anthropic text protocol ↗](https://docs.anthropic.com/en/api/messages)
- [OpenAI text protocol ↗](https://platform.openai.com/docs/api-reference/chat)
- [Video workbench](https://tryaiapi.com/videos/create.html)

**cURL · streaming response (SSE)**

```bash
curl -N https://tryaiapi.com/v1/chat/completions \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "messages": [{"role": "user", "content": "Write a short poem about clouds"}],
    "stream": true
  }'
```

**Python · streaming response**

```python
from openai import OpenAI

client = OpenAI(base_url="https://tryaiapi.com/v1", api_key="$TRYAIAPI_KEY")

stream = client.chat.completions.create(
    model="claude-opus-5-5",
    messages=[{"role": "user", "content": "Write a short poem about clouds"}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")
```

**JavaScript · streaming response**

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://tryaiapi.com/v1",
  apiKey: process.env.TRYAIAPI_KEY,
});

const stream = await client.chat.completions.create({
  model: "claude-sonnet-5-5",
  messages: [{ role: "user", content: "Write a short poem about clouds" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0].delta.content ?? "");
}
```

**cURL · video model async task**

```bash
# 1. Submit job and capture the returned poll_url
POLL_URL=$(curl -sS https://tryaiapi.com/videos/v1/videos/generations \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0",
    "prompt": "A cinematic product shot on a clean studio desk",
    "duration": 5,
    "resolution": "480p",
    "aspect_ratio": "16:9"
  }' | jq -r '.poll_url')

# 2. Poll the exact URL returned by the API
curl "$POLL_URL" \
  -H "Authorization: Bearer $TRYAIAPI_KEY"
```

**cURL · Volcano Ark native format**

```bash
# 1. Submit the task (Ark native shape: content array + ratio field)
TASK_ID=$(curl -sS https://tryaiapi.com/videos/api/v3/contents/generations/tasks \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2-0-260128",
    "content": [
      { "type": "text", "text": "A cinematic product shot on a clean studio desk" }
    ],
    "duration": 5,
    "resolution": "480p",
    "ratio": "16:9"
  }' | jq -r '.id')

# 2. Query the task (content.video_url = official link, ~24h;
#    content.platform_video_url is our 7-day copy when present)
curl "https://tryaiapi.com/videos/api/v3/contents/generations/tasks/$TASK_ID" \
  -H "Authorization: Bearer $TRYAIAPI_KEY"
```

**Python · video model async task**

```python
import os
import time
import requests

headers = {
    "Authorization": f"Bearer {os.environ['TRYAIAPI_KEY']}",
    "Content-Type": "application/json",
}

job = requests.post(
    "https://tryaiapi.com/videos/v1/videos/generations",
    headers=headers,
    json={
        "model": "doubao-seedance-2.0",
        "prompt": "A cinematic product shot on a clean studio desk",
        "duration": 5,
        "resolution": "480p",
        "aspect_ratio": "16:9",
    },
)
job.raise_for_status()
poll_url = job.json()["poll_url"]

while True:
    result = requests.get(poll_url, headers=headers)
    result.raise_for_status()
    data = result.json()
    if data["status"] in {"completed", "failed", "cancelled", "timeout"}:
        print(data)
        break
    time.sleep(5)
```

**JavaScript · video model async task**

```javascript
const headers = {
  Authorization: `Bearer ${process.env.TRYAIAPI_KEY}`,
  "Content-Type": "application/json",
};

const submit = await fetch("https://tryaiapi.com/videos/v1/videos/generations", {
  method: "POST",
  headers,
  body: JSON.stringify({
    model: "doubao-seedance-2.0",
    prompt: "A cinematic product shot on a clean studio desk",
    duration: 5,
    resolution: "480p",
    aspect_ratio: "16:9",
  }),
});
const job = await submit.json();

while (true) {
  const res = await fetch(job.poll_url, { headers });
  const result = await res.json();
  if (["completed", "failed", "cancelled", "timeout"].includes(result.status)) {
    console.log(result);
    break;
  }
  await new Promise((resolve) => setTimeout(resolve, 5000));
}
```

Video task parameter reference

| Parameter | Type | Description |
| --- | --- | --- |
| `model` | string | `doubao-seedance-2.0` / `doubao-seedance-2.0-fast` / `doubao-seedance-2.5` / `MiniMax-H3` |
| `prompt` | string | Text prompt, required |
| `duration` | integer | Duration in seconds: `doubao-seedance-2.0` / `doubao-seedance-2.0-fast` and `MiniMax-H3` are 4–15, `doubao-seedance-2.5` is 4–30 |
| `resolution` | string | Per model: `doubao-seedance-2.0` takes `480p` / `720p` / `1080p` / `4k`; `doubao-seedance-2.0-fast` takes `480p` / `720p`; `doubao-seedance-2.5` takes `480p` / `720p` / `1080p`; `MiniMax-H3` takes `768p` / `2k` |
| `image_urls` | array ≤9 (≤30 for `doubao-seedance-2.5`) | Reference images. Each item is either a URL string (default role = `first_frame`, image-to-video) or an object `{"url":"…","role":"first_frame\|last_frame\|reference_image"}` |
| `video_urls` | array ≤3 (≤10 for `doubao-seedance-2.5`) | Reference videos |
| `audio_urls` | array ≤3 (≤10 for `doubao-seedance-2.5`) | Reference audio; `doubao-seedance-2.5` accepts audio on its own; for `doubao-seedance-2.0`, `doubao-seedance-2.0-fast` and `MiniMax-H3` it **cannot be used alone** — must pair with a `role:"reference_image"` image or `video_urls`; first/last-frame images cannot be combined with audio |
| `generate_audio` | boolean | Whether output includes audio, default `false` |

**cURL · with reference image and audio**

```bash
curl https://tryaiapi.com/videos/v1/videos/generations \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2.0",
    "prompt": "Product showcase video, soft lighting, slow push-in",
    "duration": 5,
    "resolution": "720p",
    "image_urls": [
      {"url": "https://example.com/product.jpg", "role": "reference_image"}
    ],
    "audio_urls": ["https://example.com/bgm.mp3"],
    "generate_audio": true
  }'
```

**Billing:** pre-held on submission, then settled on actual usage, with any difference refunded or charged; failed tasks are fully refunded.

Video link fields

| Field | Description |
| --- | --- |
| `video_url` | Original video link from the generation engine, valid for **about 24 hours** (`expires_at` is the authoritative expiry). |
| `platform_video_url` | Platform storage link, **kept for 7 days** from job completion; once expired the file is deleted and cannot be recovered. `null` when the job has no platform copy — use `video_url` instead. |
| `platform_expires_at` | Expiry time for `platform_video_url`. |

**cURL · tool calling (Function Calling)**

```bash
curl https://tryaiapi.com/v1/chat/completions \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "messages": [{"role": "user", "content": "What's the weather like in Hangzhou?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}}
        }
      }
    }]
  }'
```

**Python · tool calling**

```python
from openai import OpenAI

client = OpenAI(base_url="https://tryaiapi.com/v1", api_key="$TRYAIAPI_KEY")

resp = client.chat.completions.create(
    model="claude-opus-5-5",
    messages=[{"role": "user", "content": "What's the weather like in Hangzhou?"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
            },
        },
    }],
)
print(resp.choices[0].message.tool_calls)
```

**JavaScript · tool calling**

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://tryaiapi.com/v1",
  apiKey: process.env.TRYAIAPI_KEY,
});

const resp = await client.chat.completions.create({
  model: "claude-sonnet-5-5",
  messages: [{ role: "user", content: "What's the weather like in Hangzhou?" }],
  tools: [{
    type: "function",
    function: {
      name: "get_weather",
      parameters: {
        type: "object",
        properties: { city: { type: "string" } },
      },
    },
  }],
});
console.log(resp.choices[0].message.tool_calls);
```

The platform uses official standard model IDs. Reasoning depth is controlled via **API parameters** — Anthropic uses the `thinking` object, OpenAI uses `reasoning_effort`.

**Anthropic Claude · Enable Extended Thinking**

```
curl https://tryaiapi.com/v1/messages \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 16000,
    "thinking": {
      "type": "adaptive",
      "display": "summarized"
    },
    "output_config": { "effort": "high" },
    "messages": [{
      "role": "user",
      "content": "Explain the mechanism of quantum entanglement"
    }]
  }'

# Claude Opus 5.5 / Sonnet 5 / Fable 5.1: use thinking "adaptive"; set depth with output_config.effort (low / medium / high / xhigh / max)
```

**Python · Anthropic SDK**

```python
import anthropic

client = anthropic.Anthropic(
    base_url="https://tryaiapi.com",
    api_key="$TRYAIAPI_KEY",
)

resp = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    output_config={"effort": "high"},
    messages=[{"role": "user", "content": "Explain the mechanism of quantum entanglement"}],
)

for block in resp.content:
    if block.type == "thinking":
        print("[thinking]", block.thinking[:200], "...")
    elif block.type == "text":
        print("[answer]", block.text)
```

**OpenAI · reasoning_effort parameter**

```
curl https://tryaiapi.com/v1/chat/completions \
  -H "Authorization: Bearer $TRYAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-6-sol",
    "reasoning_effort": "high",
    "messages": [{
      "role": "user",
      "content": "Explain the mechanism of quantum entanglement"
    }]
  }'

# reasoning_effort: "low" | "medium" | "high" (default: medium)
# Applies to gpt-6-sol and other reasoning models; check /v1/models for current IDs
```

**Python · OpenAI SDK**

```python
from openai import OpenAI

client = OpenAI(base_url="https://tryaiapi.com/v1", api_key="$TRYAIAPI_KEY")

resp = client.chat.completions.create(
    model="gpt-6-sol",
    reasoning_effort="high",
    messages=[{"role": "user", "content": "Explain the mechanism of quantum entanglement"}],
)
print(resp.choices[0].message.content)
```

## 05 Client SDK configuration examples

#### Claude Code

All Claude models

⚠ `ANTHROPIC_BASE_URL` must stop at the hostname `https://tryaiapi.com` — do not add `/v1` (unlike OpenAI; adding it would form /v1/v1/messages → 404)

```
# Set environment variables, then launch directly
export ANTHROPIC_BASE_URL=https://tryaiapi.com
export ANTHROPIC_API_KEY=your-tryaiapi-key
claude
```

#### Cursor

Claude + ChatGPT

⚠ Base URL must include `/v1` (Cursor appends `/chat/completions` directly to this OpenAI-protocol endpoint — unlike Claude Code's `ANTHROPIC_BASE_URL`, which does not take /v1)

```
# Settings → Models → Override OpenAI Base URL
Base URL: https://tryaiapi.com/v1
API Key:  your-tryaiapi-key

# Claude: use model IDs directly (e.g. claude-sonnet-5-5); ChatGPT model IDs: check /v1/models first
```

#### Anthropic Python SDK

All Claude models

```
import anthropic

client = anthropic.Anthropic(
    base_url="https://tryaiapi.com",
    api_key="your-tryaiapi-key",
)

msg = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
```

#### OpenAI Python SDK

Claude + ChatGPT

```
# Swap providers by changing the model name
from openai import OpenAI

client = OpenAI(
    base_url="https://tryaiapi.com/v1",
    api_key="your-tryaiapi-key",
)

# Claude
resp = client.chat.completions.create(
    model="claude-opus-5-5",
    messages=[{"role": "user", "content": "Hello"}],
)

# ChatGPT — same client; check /v1/models for the current model ID
resp = client.chat.completions.create(
    model="gpt-6-sol",
    messages=[{"role": "user", "content": "Hello"}],
)
```

#### Video model API

Seedance / Video tasks

```
# Standard HTTP call: submit a task, then poll poll_url
curl https://tryaiapi.com/videos/v1/videos/generations \
  -H "Authorization: Bearer your-tryaiapi-key" \
  -H "Content-Type: application/json" \
  -d '{"model":"doubao-seedance-2.0","prompt":"A product video","duration":5,"resolution":"480p","aspect_ratio":"16:9"}'
```

#### Codex CLI

ChatGPT series (Responses API)

⚠ `base_url` must stop at `/v1` — do not add `/chat/completions`, or Codex will build a bad path and return 404

⚠ Use model `gpt-6-sol`. Codex uses the Responses API (not chat/completions) — declare `wire_api = "responses"` in config.toml

```
# ~/.codex/config.toml
model = "gpt-6-sol"
model_provider = "tryaiapi"

[model_providers.tryaiapi]
name = "Try AI API"
base_url = "https://tryaiapi.com/v1"
wire_api = "responses"
env_key = "TRYAIAPI_KEY"
```

```
export TRYAIAPI_KEY="sk-your-api-key"
codex
```

#### Codex automatic approval (compatibility mode)

We map `codex-auto-review` requests to GPT-5.6 Terra and bill them at our Terra rates. This affects automatic approval only; your selected chat and coding model stays unchanged.

Automatic approval defaults to Low reasoning effort. A backup is attempted only when the service request fails; an explicit denial is not retried with another model to seek approval.

You can configure a different reviewer model in your client. Luna has limited supply on this service and cannot be used once that supply runs out, so we do not recommend it for automatic approval that needs continuous availability.

To confirm actions yourself, select manual approval in Codex.

#### opencode

Claude + ChatGPT

⚠ `options.baseURL` must include `/v1` — `@ai-sdk/openai-compatible` appends `/chat/completions` directly and does not add /v1 for you

```
# ~/.config/opencode/opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "tryaiapi": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Try AI API",
      "options": {
        "baseURL": "https://tryaiapi.com/v1",
        "apiKey": "{env:TRYAIAPI_KEY}"
      },
      "models": {
        "claude-sonnet-5-5": { "name": "Claude Sonnet 5" },
        "gpt-6-sol": { "name": "GPT-6 Sol" }
      }
    }
  }
}
```

```
# Set the env var, then run /connect to store the credential under provider id "tryaiapi"
export TRYAIAPI_KEY="your-tryaiapi-key"
opencode
# In the TUI: /connect → Other → paste your key under provider id "tryaiapi"
# Then /models to pick one of the models configured above
```

#### LangChain

All Claude models

```
from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(
    base_url="https://tryaiapi.com",
    api_key="your-tryaiapi-key",
    model="claude-opus-5-5",
)
```

## 06 Video models

Video generation is asynchronous with asset library support. See the **dedicated guide**: [/en/docs/video-model/](https://tryaiapi.com/en/docs/video-model/) (same API key, same base URL).

1. [1 Get your first video running in 3 minutes](https://tryaiapi.com/en/docs/video-model/#intro)
2. [2 Reference images](https://tryaiapi.com/en/docs/video-model/#ref-images)
3. [3 Asset library: the real-person channel](https://tryaiapi.com/en/docs/video-model/#asset-library)
4. [4 Naming people & mention syntax](https://tryaiapi.com/en/docs/video-model/#mention-syntax)
5. [5 API reference](https://tryaiapi.com/en/docs/video-model/#api-reference)
   1. [5.1 Submit a generation job · `POST /videos/v1/videos/generations`](https://tryaiapi.com/en/docs/video-model/#api-submit)
   2. [5.2 Estimate before submitting · `POST /videos/v1/videos/generations/estimate`](https://tryaiapi.com/en/docs/video-model/#api-estimate)
   3. [5.3 Query a job · `GET /videos/v1/videos/jobs/{job_id}`](https://tryaiapi.com/en/docs/video-model/#api-get-job)
   4. [5.4 List jobs · `GET /videos/v1/videos/jobs`](https://tryaiapi.com/en/docs/video-model/#api-list-jobs)
   5. [5.5 Usage stats · `GET /videos/v1/videos/jobs/stats`](https://tryaiapi.com/en/docs/video-model/#api-stats)
   6. [5.6 Export · `GET /videos/v1/videos/jobs/export.csv`](https://tryaiapi.com/en/docs/video-model/#api-export)
   7. [5.7 Asset library](https://tryaiapi.com/en/docs/video-model/#api-assets)
   8. [5.8 Upload endpoint (video workbench only — a platform integration usually doesn't need it)](https://tryaiapi.com/en/docs/video-model/#api-uploads)
   9. [5.9 Ark-compatible endpoint: differences from Ark native](https://tryaiapi.com/en/docs/video-model/#ark-diff)
6. [6 Error codes](https://tryaiapi.com/en/docs/video-model/#error-codes)
   1. [6.1 Client & validation errors](https://tryaiapi.com/en/docs/video-model/#error-codes-request)
   2. [6.2 Auth & server errors](https://tryaiapi.com/en/docs/video-model/#error-codes-auth-server)
   3. [6.3 Retry guidance](https://tryaiapi.com/en/docs/video-model/#error-codes-retry)
7. [7 Tenancy & isolation](https://tryaiapi.com/en/docs/video-model/#tenancy)
8. [8 Quotas & limits](https://tryaiapi.com/en/docs/video-model/#quotas)
9. [9 Integration FAQ](https://tryaiapi.com/en/docs/video-model/#faq)

## 07 Account balance

Check it before you start making calls, or from your own scheduler, to warn early or pause submissions when the balance is low, so tasks or requests are not rejected midway for insufficient balance. Read-only; it never incurs a charge.

`GET /v1/user/balance`

Auth is the same as the other endpoints: `Authorization: Bearer <your key>`. It returns the balance of the **account that owns the key**. An account has a single balance, denominated in US dollars, matching the console.

**curl example · check balance**

```bash
curl https://tryaiapi.com/v1/user/balance \
  -H "Authorization: Bearer <your API Key>"
```

**Response example (illustrative values)**

```
{
  "code": "success",
  "data": {
    "user_id": 1001,
    "scope": "account",
    "balance_usd": 61.66,
    "balance_cny": 431.62,
    "quota_remaining": 30829650,
    "currency_default": "USD",
    "conversion_ratio": 7,
    "status": "normal",
    "timestamp": 1791108566,
    "cached": false,
    "age_seconds": 0
  }
}
```

| Field | Description |
| --- | --- |
| `balance_usd` | **Current available balance of the account, in US dollars**, two decimal places. Use this field to decide whether the balance is sufficient |
| `balance_cny` | The USD balance above expressed in Chinese yuan using `conversion_ratio`, for reference; **not a separate balance** |
| `status` | `normal` balance is fine / `low` balance is low (the threshold is set by this platform; also apply your own threshold, see the example below) / `exhausted` balance is used up |
| `scope` | Always `account`, meaning the account balance |
| `currency_default` | Always `USD`: the account balance is denominated in US dollars |
| `quota_remaining` / `conversion_ratio` | `conversion_ratio` is the factor used to compute `balance_cny` (yuan per US dollar); `quota_remaining` is the raw credit value of the same balance. For reconciliation only; use `balance_usd` in your code |
| `timestamp` | When the response was generated (Unix seconds) |
| `cached` / `age_seconds` | Whether the response came from cache, and how many seconds old the cached value is — see “Polling frequency and rate limiting” below |
| `user_id` | Account number, returned as-is; you do not need it for integration |

**Polling frequency and rate limiting**: balances change slowly, so querying once an hour is enough. Results are cached per account for about 1 minute. Repeated requests inside that window **do not return an error**; they return the previous result (`cached: true`, with `age_seconds` showing its age). If requests from one source IP are clearly too frequent, the endpoint returns `429` (`rate_limit_exceeded`) with a `Retry-After` header — this is rate limiting, **not a low balance**; simply query again after `Retry-After`.

| HTTP | `error.code` | Meaning |
| --- | --- | --- |
| 401 | `invalid_api_key` | Key missing, invalid, or account disabled |
| 429 | `rate_limit_exceeded` | Too many requests; query again later |
| 500 | `internal_error` | Internal error on this platform; retry later and contact us if it persists |

**Python example · alert below a threshold**

```python
import os
import requests

ALERT_BELOW_USD = 15.0  # set your own warning line (USD)

def check_balance():
    r = requests.get(
        "https://tryaiapi.com/v1/user/balance",
        headers={"Authorization": f"Bearer {os.environ['INFWAVE_API_KEY']}"},
        timeout=10,
    )
    if r.status_code == 429:  # rate limited, NOT a low balance: check again later
        return None
    r.raise_for_status()
    return r.json()["data"]

data = check_balance()
if data is not None and (data["balance_usd"] < ALERT_BELOW_USD or data["status"] != "normal"):
    # plug in your own notification channel (email / SMS / chat) and pause new submissions here
    print(f"Low balance warning: ${data['balance_usd']:.2f} left")
```

### Balance alerts

Get notified early when the balance runs low, so calls are not interrupted by insufficient balance. Pick any one of the three options below, or combine them.

#### Option 1: Email alert

Turn it on in the console, no code needed. In the console sidebar open “Personal” → “Profile”, and in the “Settings” card set “Notification Method” to “Email”, enter your alert line in “Quota Warning Threshold”, then click “Save Settings”. When the balance falls below that value, we email your account address; to use a different mailbox, enter it under “Notification Email”.

The threshold is entered in credit units: **US$1 = 500,000**. For example, to be alerted when the balance drops below US$100, enter `50000000`. Please add the sender address `no-reply@tryaiapi.com` to your mailbox allowlist so the alert is not filed as spam.

#### Option 2: Self-hosted alert (e.g. Feishu / DingTalk / WeCom / SMS / Telegram)

Run a scheduled job that calls the balance endpoint above once an hour and, when the balance falls below your own line, sends the alert to your Feishu group, DingTalk group, WeCom group, phone (SMS), Telegram or another channel. The example below uses only the Python standard library; the `NOTIFY_CHANNEL` environment variable switches between Feishu, DingTalk, WeCom and Telegram, and SMS goes through your own SMS provider (see the table).

| Platform | Prepare first | Environment variables |
| --- | --- | --- |
| Feishu (`feishu`) | Add a “Custom Bot” to the group and copy its webhook URL; if the bot has “Signature verification” on, copy its secret too | `FEISHU_WEBHOOK`; add `FEISHU_SECRET` if signature verification is on |
| DingTalk (`dingtalk`) | Add a “Custom Robot” to the group and copy its webhook URL; if you chose “Sign” in its security settings, copy the secret too | `DINGTALK_WEBHOOK`; add `DINGTALK_SECRET` if “Sign” is on |
| WeCom (`wecom`) | Add a “Group Bot” to the group and copy its webhook URL | `WECOM_WEBHOOK` |
| SMS | Sign up with an SMS provider such as Alibaba Cloud SMS or Tencent Cloud SMS and get a sender signature and message template approved first. The example does not include SMS; replace its send function with your provider's send API | As required by your provider (e.g. access key, signature, template ID, recipient phone number) |
| Telegram (`telegram`) | Create a bot with @BotFather to get its token, add the bot to the target group or chat, and get the chat_id. Servers in mainland China usually cannot reach Telegram directly; you need a network that can | `TG_BOT_TOKEN`, `TG_CHAT_ID` |

Also set `TRYAIAPI_KEY` (the API key you call models with); `LOW_USD` / `CRIT_USD` are the low / critical lines in US dollars, defaulting to 40 / 10. Treat the webhook URL and bot token like passwords and do not share them. Behaviour of the example: one alert each for Low and Critical, with no repeats inside a tier; it counts as recovered only after the balance climbs back above 1.2 times the low line; a failed query (network, rate limit) is not treated as a low balance, and three failures in a row raise a “monitor is down” alert; a message that fails to send is not recorded and is retried on the next run. Run it hourly with crontab: `0 * * * *`.

**Python example · push to a group when the balance is below your line (Feishu / DingTalk / WeCom / Telegram)**

```python
#!/usr/bin/env python3
"""TryAIAPI balance alert: check the balance hourly and push to Feishu / DingTalk / WeCom / Telegram when it falls below your line. Python standard library only.

Environment variables:
  TRYAIAPI_KEY        the API key you call models with
  NOTIFY_CHANNEL   feishu / dingtalk / wecom / telegram (pick one)
  Feishu: FEISHU_WEBHOOK (add FEISHU_SECRET if the bot has signature verification)
  WeCom: WECOM_WEBHOOK
  DingTalk: DINGTALK_WEBHOOK (add DINGTALK_SECRET if the bot uses "Sign" security)
  Telegram: TG_BOT_TOKEN, TG_CHAT_ID
  LOW_USD / CRIT_USD  low / critical line in USD, default 40 / 10

Schedule it (crontab, hourly):
  0 * * * * /usr/bin/python3 /path/to/balance_alert.py >> balance_alert.log 2>&1

Behaviour: one alert for Low and one for Critical; a failed query is not treated as low
balance, and 3 failures in a row raise a 'monitor is down' alert."""
import base64
import hashlib
import hmac
import json
import os
import time
import urllib.parse
import urllib.request
from pathlib import Path

API = "https://tryaiapi.com/v1/user/balance"
KEY = os.environ.get("TRYAIAPI_KEY", "")
CHANNEL = os.environ.get("NOTIFY_CHANNEL", "feishu")  # feishu / wecom / dingtalk / telegram
LOW = float(os.environ.get("LOW_USD", "40"))
CRIT = float(os.environ.get("CRIT_USD", "10"))
STATE = Path(os.environ.get("STATE_FILE", str(Path.home() / ".tryaiapi_balance_alert.json")))
TIERS = ["Normal", "Low", "Critical"]
UA = "balance-alert/1.0"

def http_json(url, payload=None, headers=None):
    data = json.dumps(payload).encode() if payload is not None else None
    h = {"User-Agent": UA, **(headers or {})}
    if data:
        h["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=data, headers=h)
    with urllib.request.urlopen(req, timeout=15) as r:
        return json.loads(r.read().decode())

def query_balance():
    data = http_json(API, headers={"Authorization": f"Bearer {KEY}"})["data"]
    usd = float(data["balance_usd"])
    return usd, f"${usd:.2f} (about ¥{float(data['balance_cny']):.2f})"

def send_once(text):
    if CHANNEL == "feishu":  # Feishu custom-bot webhook
        payload = {"msg_type": "text", "content": {"text": text}}
        secret = os.environ.get("FEISHU_SECRET")
        if secret:  # only needed if the bot has signature verification on
            ts = str(int(time.time()))
            key = f"{ts}\n{secret}".encode()
            payload["timestamp"] = ts
            payload["sign"] = base64.b64encode(hmac.new(key, b"", hashlib.sha256).digest()).decode()
        return http_json(os.environ["FEISHU_WEBHOOK"], payload).get("code") == 0
    if CHANNEL == "wecom":  # WeCom group-bot webhook
        res = http_json(os.environ["WECOM_WEBHOOK"], {"msgtype": "text", "text": {"content": text}})
        return res.get("errcode") == 0
    if CHANNEL == "dingtalk":  # DingTalk group custom robot webhook
        url = os.environ["DINGTALK_WEBHOOK"]
        secret = os.environ.get("DINGTALK_SECRET")
        if secret:  # only if the bot uses "Sign" security
            ts = str(int(time.time() * 1000))
            digest = hmac.new(secret.encode(), f"{ts}\n{secret}".encode(), hashlib.sha256).digest()
            url += f"&timestamp={ts}&sign={urllib.parse.quote_plus(base64.b64encode(digest))}"
        res = http_json(url, {"msgtype": "text", "text": {"content": text}})
        return res.get("errcode") == 0
    if CHANNEL == "telegram":  # Telegram bot (usually unreachable from mainland-China servers)
        url = f"https://api.telegram.org/bot{os.environ['TG_BOT_TOKEN']}/sendMessage"
        return http_json(url, {"chat_id": os.environ["TG_CHAT_ID"], "text": text}).get("ok") is True
    raise SystemExit("NOTIFY_CHANNEL must be feishu, wecom, dingtalk or telegram")

def notify(text):
    for i in range(3):
        try:
            if send_once(text):
                return True
            print("message rejected, will retry")
        except Exception as e:
            print(f"send error: {type(e).__name__}")  # log the exception type only, never the URL, so tokens stay out of logs
        time.sleep(2 ** i)
    return False

def load_state():
    try:
        return json.loads(STATE.read_text())
    except Exception:
        return {"tier": 0, "fails": 0, "down_alerted": False}

def main():
    if not KEY:
        raise SystemExit("Set TRYAIAPI_KEY first")
    st = load_state()
    try:
        usd, shown = query_balance()
    except Exception as e:
        st["fails"] += 1
        print(f"query failed (#{st['fails']}): {type(e).__name__}")
        if st["fails"] >= 3 and not st["down_alerted"]:
            if notify("[TryAIAPI balance monitor] 3 consecutive balance queries failed. The monitor may be down; check the network or API key."):
                st["down_alerted"] = True
        STATE.write_text(json.dumps(st))
        return
    st["fails"], st["down_alerted"] = 0, False

    tier = 2 if usd < CRIT else 1 if usd < LOW else 0
    if tier > st["tier"]:  # alert only when it drops into a worse tier; no repeats within a tier
        line = CRIT if tier == 2 else LOW
        msg = (f"[TryAIAPI balance {TIERS[tier].lower()}] Available balance is {shown}, "
               f"below the {TIERS[tier].lower()} line of ${line:.0f}. Please top up to avoid interrupted calls.")
        if notify(msg):
            st["tier"] = tier  # record the tier only after a successful send; a failed send retries next run
    elif tier < st["tier"] and (tier > 0 or usd >= LOW * 1.2):  # recovered only above 1.2x the low line, to avoid flapping around it
        st["tier"] = tier
        if tier == 0:
            notify(f"[TryAIAPI balance recovered] Available balance is {shown}.")
    print(f"balance ${usd:.2f}, tier {TIERS[st['tier']]}")
    STATE.write_text(json.dumps(st))

if __name__ == "__main__":
    main()
```

#### Option 3: We push alerts to your Feishu group

If you would like us to push balance alerts straight into a Feishu group, please contact support. We will set up an alert group and invite you; all you need is a Feishu account.

## 08 Frequently asked questions

**Do responses match official provider formats?**

Yes. Responses pass through the upstream native fields (Anthropic's `request_id`, OpenAI's `id` and `system_fingerprint`). Text requests store no prompts or completions; see the [transparency page](https://tryaiapi.com/en/transparency/) for the full boundary.

**How is billing handled?**

Text models are billed by token usage; video models are billed by model and duration. Log in to the console to see real-time balance and usage detail, filterable by model, date, or key. Bank transfer and VAT invoice are supported.

**Do you store my prompts and responses?**

Text prompts and responses are not stored. Video models are async tasks — task parameters, status, cost, result URL, and failure reason are kept for task tracking and billing. See the [transparency page](https://tryaiapi.com/en/transparency/) for the full boundary.

**Do you support streaming?**

Text models support Anthropic-native SSE streaming and OpenAI-compatible streaming — set `"stream": true` in the request. Video models are not streaming responses; after submission, prefer the response's `poll_url`. Its current path is `/videos/v1/videos/jobs/{job_id}`.

**How do I call video models?**

Use the same API key to call `POST /videos/v1/videos/generations`. The response returns `job_id` and `poll_url`. After the task completes, check the polling response or "Video Model Usage" for the result, download URL, and failure reason.

**How do I enable Extended Thinking or reasoning_effort?**

The platform provides only official standard model IDs — suffix variants like `-thinking`/`-high`/`-low` are not offered. Reasoning depth is controlled via API parameters:

- **Anthropic Claude**: for `claude-opus-5-5`, `claude-sonnet-5-5` and `claude-fable-5-1`, add `"thinking": {"type": "adaptive"}` and set depth with `"output_config": {"effort": "high"}` (options: `low`/`medium`/`high`/`xhigh`/`max`).
- **OpenAI (reasoning models)**: add `"reasoning_effort": "high"` to the request body (options: `low`/`medium`/`high`). Supported models: `gpt-6-sol` and others — check `GET /v1/models` to confirm.

See the full example under the [Code Examples → Extended Thinking](https://tryaiapi.com/en/docs/#examples) tab.

**How do I enable Prompt Caching?**

💡 **Prompt Caching**: Claude models support prompt caching, saving up to ~90% on input cost for repeated long context (system prompts, agent loops, codebase context, etc). **To enable**: use the Anthropic-native `/v1/messages` format and mark `cache_control` breakpoints in the messages or system prompt. **Note**: the OpenAI-compatible `/v1/chat/completions` format does not support Claude prompt caching — every request is billed at full input price.

**How is this different from calling Anthropic / OpenAI directly?**

The text endpoints are wire-compatible with Anthropic / OpenAI, including streaming and tool calling. Video models use the Try AI API async task endpoint. One key, one balance, and one usage record across both, plus CNY bank transfer and VAT invoices.
