# Bineric API documentation

> Reference for the Bineric API: an OpenAI-compatible API for Lynx and leading models from OpenAI, Anthropic, Google and more. Chat completions, streaming, tool calling, images, speech, deep research, files and usage.

HTML version: https://bineric.com/platform/docs

- Getting started: Quickstart, Authentication, Models
- Chat: Chat completions, Streaming, Tool calling, Errors, Rate limits
- Images, speech and research: Image generation, Text to speech, Speech to text, Deep research
- Files: Convert a file to base64, Decode base64
- Account: Usage, Daily usage, Balance

## Quickstart

The Bineric API is OpenAI-compatible. Point an OpenAI SDK at the Bineric base URL, use a Bineric API key, and call any model in the list below.

### Base URL

```bash
https://api.bineric.com/v1
```

Chat completions and the model list live under `/v1`, where OpenAI SDKs expect them. Account endpoints (file conversion, usage and balance) live under `/api/v1`. The older `/api/v1/ai/chat/completions` still works for chat completions.

### First request

Python:

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.bineric.com/v1",
    api_key="YOUR_API_KEY",
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}],
)

print(response.choices[0].message.content)
```

JavaScript:

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.bineric.com/v1",
  apiKey: "YOUR_API_KEY",
});

const response = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello!" }],
});

console.log(response.choices[0].message.content);
```

cURL:

```bash
curl https://api.bineric.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'
```

To switch models, change `model` to any id from [Models](https://bineric.com/platform/docs#models). Everything else stays the same.

## Authentication

Every request except `GET /v1/models` needs an API key.

Send the key as a Bearer token in the `Authorization` header (what the OpenAI SDKs do), or in an `api-key` header. If both are sent, the Bearer token is used.

```bash
-H "Authorization: Bearer YOUR_API_KEY"
# or
-H "api-key: YOUR_API_KEY"
```

### Get a key

1. Create an account at [platform.bineric.com](https://platform.bineric.com).
2. Create an API key in the dashboard and copy it.
3. Buy credits under Billing. A call needs a completed purchase and a balance of at least $0.10; a saved card on its own is not enough.

> Keep your key on the server. Never put it in client-side code or a public repository.

## Models

Pass the id in the first column as `model`. Prices are in USD, at the provider's list price.

**Language models**

| model | Name | Provider | Context | Hosting | Price (input / output per 1M tokens) | Tools | Vision |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `lynx-30b` | Lynx | Bineric AI | 262K | EU | $3 / $15 |  |  |
| `gpt-4o` | GPT-4o | OpenAI | 128K | EU | $2.50 / $10 | yes | yes |
| `gpt-5` | GPT-5-Reasoning | OpenAI | 128K | EU | $1.38 / $11 | yes | yes |
| `gpt-5-mini` | GPT-5-Mini | OpenAI | 128K | EU, US | $0.25 / $2 | yes | yes |
| `gpt-5.1` | GPT 5.1 | OpenAI | 400K | EU, US | $1.25 / $10 | yes | yes |
| `gpt-5.2` | GPT 5.2 | OpenAI | 400K | US | $1.75 / $14 | yes | yes |
| `gpt-5.3` | GPT 5.3 | OpenAI | 128K | US | $1.75 / $14 | yes | yes |
| `gpt-5.4` | GPT 5.4 | OpenAI | 1M | EU | $2.75 / $16.50 | yes | yes |
| `gpt-5.5` | GPT-5.5 | OpenAI | 1M | EU | $5.50 / $33 | yes | yes |
| `gpt-5.6-luna` | GPT-5.6-Luna | OpenAI | 1M | EU | $0.22 / $1.32 |  | yes |
| `gpt-5.6-sol` | GPT 5.6 Sol | OpenAI | 1M | EU | $4.40 / $22 |  | yes |
| `gpt-5.6-terra` | GPT 5.6 Terra | OpenAI | 1M | EU | $2.20 / $13.20 |  | yes |
| `gpt-oss-20b` | gpt-oss-20b | OpenAI | 128K | US | $0.07 / $0.30 |  |  |
| `gpt-oss-120b` | gpt-oss-120b | OpenAI | 128K | US | $0.15 / $0.60 |  |  |
| `o1` | O1 | OpenAI | 128K | EU | $15 / $60 | yes | yes |
| `o3-mini` | O3-Mini | OpenAI | 128K | EU | $1.10 / $4.40 | yes | yes |
| `claude-haiku-4-5` | Claude Haiku 4.5 | Anthropic | 200K | EU | $1.10 / $5.50 | yes | yes |
| `claude-opus-4-5` | Claude Opus 4.5 | Anthropic | 200K | EU | $5.50 / $27.50 | yes | yes |
| `claude-opus-4-6` | Claude Opus 4.6 | Anthropic | 1M | EU | $5.50 / $27.50 | yes | yes |
| `claude-opus-4-7` | Claude Opus 4.7 | Anthropic | 1M | EU | $5.50 / $27.50 | yes | yes |
| `claude-opus-4.8` | Claude Opus 4.8 | Anthropic | 1M | EU | $5.50 / $27.50 | yes | yes |
| `claude-opus-5` | Claude Opus 5 | Anthropic | 1M | EU | $5.50 / $27.50 |  | yes |
| `claude-opus-5.5` | Claude Opus 5.5 | Anthropic | 1M | EU | $4.40 / $22 |  | yes |
| `claude-sonnet-4-6` | Claude Sonnet 4.6 | Anthropic | 1M | EU | $3.30 / $16.50 | yes | yes |
| `claude-sonnet-5` | Claude Sonnet 5 | Anthropic | 1M | EU | $2.20 / $11 |  | yes |
| `gemini-2.5-flash` | Gemini-2.5-Flash | Google | 1M | US | $0.30 / $2.50 | yes | yes |
| `gemini-2.5-pro` | Gemini-2.5-Pro | Google | 1M | US | $1.25 / $10 | yes | yes |
| `gemini-3-pro-preview` | Gemini-3-Pro | Google | 1M | US | $1.25 / $10 | yes | yes |
| `borealis-2` | Borealis 2 | Nasjonalbiblioteket | 16K | EU | $2 / $10 |  |  |
| `meta-llama-3.3-70b` | Llama 3.3-70B | Meta | 128K | US | $0.72 / $0.72 |  |  |
| `mistral_large_24_02` | Mistral Large 24.02 | Mistral AI | 32K | EU | $4.30 / $13 |  |  |
| `qwen-3-235b` | Qwen 3 235B | Qwen | 128K | EU | $0.22 / $0.88 |  |  |
| `qwen-3-coder-480b` | Qwen 3 Coder 480B | Qwen | 128K | EU | $0.45 / $1.80 |  |  |

**Image models**

| model | Name | Provider | Context | Hosting | Price (input / output per 1M tokens) | Tools | Vision |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `gpt-image-1` | gpt-image-1 | OpenAI | 128K | EU | $10 / $40 |  | yes |
| `nano-banana-2` | Nano Banana 2 | Google | 128K | US | $0.50 / $61 |  |  |
| `stability-ai` | Stable Diffusion 3.5 Large | Stability AI | – | US | $0.08 per image |  |  |

**Speech, search and research models**

| model | Name | Provider | Context | Hosting | Price (input / output per 1M tokens) | Tools | Vision |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `deep-research` | Deep Research | Bineric AI | 128K | US | $0.25 per call |  |  |
| `speech-to-text` | Speech to Text | Microsoft | 4K | EU | $0.18 per hour |  |  |
| `sonar-web-search` | sonar-web-search | Perplexity | 128K | US | $1 / $1 |  |  |

Retired ids still work and are answered by their replacement: `claude-sonnet-4` → `claude-sonnet-4-6`.

Decision models (Zuse) answer fixed-choice questions and have their own API; see the [Zuse docs](https://bineric.com/zuse/docs). The [model library](https://bineric.com/models) compares every model side by side.

### GET /v1/models

`GET /v1/models`

Every model the API serves, chat and decision models together, with live prices. No key is needed, and `client.models.list()` works. Per-token prices are per million tokens; `per_request` is a fixed price per call (0 when there is none), and decision models show `per_decision` instead. `models` repeats the decision models with their name and description.

```bash
curl https://api.bineric.com/v1/models
```

```json
{
  "object": "list",
  "data": [
    {
      "id": "claude-sonnet-4-6",
      "object": "model",
      "created": 1735689600,
      "owned_by": "bedrock-anthropic",
      "name": "Claude Sonnet 4.6",
      "description": "Claude Sonnet 4.6 delivers frontier intelligence at scale, built for coding, agents, and enterprise workflows.",
      "category": "General",
      "hosting": "EU",
      "tier": "premium",
      "context_length": 1000000,
      "pricing": {
        "input_per_million_tokens": 3,
        "output_per_million_tokens": 15,
        "per_request": 0,
        "currency": "USD"
      }
    }
  ],
  "models": []
}
```

`owned_by` is the provider the model runs on. `hosting` is `EU`, `US` or `Both`. `context_length` is the model's context window in tokens.

## Chat completions

Generate a response from a list of messages, in the OpenAI format.

`POST /v1/chat/completions`

### Request body

| Field | Type | Description |
| --- | --- | --- |
| `model` | string | Required. A model id from [Models](https://bineric.com/platform/docs#models). |
| `messages` | array | Required. The conversation so far; see Messages below. |
| `stream` | boolean | Stream the response as server-sent events. Default `false`. See [Streaming](https://bineric.com/platform/docs#streaming). |
| `stream_options` | object | `{"include_usage": true}` adds a final chunk with token usage. |
| `max_completion_tokens` | integer | Maximum tokens in the response, never above the model's own output limit. `max_tokens` also works. |
| `temperature` | number | 0 to 2. Out of range returns 400. |
| `top_p` | number | 0 to 1. Out of range returns 400. |
| `reasoning_effort` | string | `low`, `medium` or `high`; `minimal`, `none`, `xhigh` and `max` are also accepted. Used by models that reason; other values are ignored. |
| `tools` | array | Functions the model may call. See [Tool calling](https://bineric.com/platform/docs#tool-calling). |
| `tool_choice` | string or object | `auto`, `none`, `required`, or `{"type":"function","function":{"name":"..."}}`. |
| `parallel_tool_calls` | boolean | Sent to OpenAI and Qwen models; others ignore it. |

GPT-5 and the o-series ignore `temperature` and `top_p`, and so do Claude Opus 4.7 and later, Claude Sonnet 5, and any Claude request with reasoning on. Other Claude models use one of the two: `temperature` if both are sent. `reasoning_effort: "none"` picks the lowest level the model has, which on older Claude models and Gemini 2.5 Flash turns reasoning off.

Older integrations can still send `stream`, `temperature`, `top_p` and `reasoning_effort` inside an `options` object, and `max_response_length` at the top level. Top-level fields win when both are sent.

### Messages

| Field | Type | Description |
| --- | --- | --- |
| `role` | string | `system`, `user`, `assistant` or `tool`. |
| `content` | string or array | The text, or an array of content parts (below). Can be `null` on an assistant message that only has `tool_calls`. |
| `tool_calls` | array | Assistant messages only: the tool calls the model made, sent back unchanged. |
| `tool_call_id` | string | Tool messages only: the `id` of the call this result answers. |
| `attachments` | array | Bineric's own format: base64 data URLs. Content parts do the same thing. |

### Content parts

| Type | Description |
| --- | --- |
| `text` | `{"type": "text", "text": "..."}` |
| `image_url` | `image_url.url` is a base64 data URL or an http(s) URL. Images fetched from a URL can be up to 20 MB. |
| `file` | `file.file_data` is a base64 data URL; `file.filename` is optional. `file_id` is not supported. |
| `input_audio` | `input_audio.data` (base64) and `input_audio.format` (`wav` or `mp3`). |

Images, files and audio are read from the **last** user message only; attach them to the message you are asking about. A malformed part returns 400 with `param` pointing at it, for example `messages[0].content[1]`.

```json
{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "What is in this image, and what does the report say about it?" },
        { "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } },
        { "type": "file", "file": { "filename": "report.pdf", "file_data": "data:application/pdf;base64,JVBERi0..." } }
      ]
    }
  ]
}
```

### Files

| Kind | Formats | Notes |
| --- | --- | --- |
| Images | JPEG, PNG, WebP, GIF | Models that support vision. Each model has its own size limit (Claude: 3.75 MB); a larger image returns 400. Images are scaled to fit 1024×1024. |
| Documents | PDF, DOCX, DOC, XLSX, XLS, TXT, CSV, MD, JSON, HTML | The text is extracted and sent to the model. |
| Audio | WAV, MP3 | Gemini models and `speech-to-text` only. Other models return 400. |

### Response

```json
{
  "id": "chatcmpl-1768817914002-yq8xw50tv2g",
  "object": "chat.completion",
  "created": 1768817914,
  "model": "GPT-4o",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Hello! How can I help?" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 9, "completion_tokens": 7, "total_tokens": 16 }
}
```

`model` in the response is the model's display name, such as `GPT-4o`, not the id you sent. `finish_reason` is `stop`, `length` (cut off by `max_completion_tokens` or the model's limit) or `tool_calls`; image models can also return `content_filter`.

When a model is retired, requests for its id are answered by its replacement, and `model` shows the replacement's name.

## Streaming

Set `stream: true` to receive the response as server-sent events while it is written.

Python:

```python
stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Write a haiku about the sea."}],
    stream=True,
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
```

JavaScript:

```javascript
const stream = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Write a haiku about the sea." }],
  stream: true,
});

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

cURL:

```bash
curl -N https://api.bineric.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "Write a haiku about the sea."}],
    "stream": true
  }'
```

### Events

```text
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[{"index":0,"delta":{"content":"Waves"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":17,"total_tokens":31}}

data: [DONE]
```

- The first chunk carries the role, then come content chunks, then an empty `delta` with the `finish_reason`.
- The usage chunk (empty `choices`) is only sent when the request sets `"stream_options": {"include_usage": true}`.
- Reasoning models also send `delta.reasoning_content` chunks with their reasoning.
- Search models add `_bineric.citations` to the last chunk.
- If an error happens after the stream has started, it arrives as `data: {"error": {...}}` and the stream closes without `[DONE]`.

### Tool calls

Tool calls stream as `delta.tool_calls`. The first piece of each call carries its `id` and function `name`; join the `arguments` strings by `index`.

```text
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":\"Oslo\"}"}}]},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1768818339,"model":"GPT-4o","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
```

Image, speech and deep research models do not send OpenAI chunks; see their sections.

## Tool calling

Give the model functions it can call. The model returns the call; your code runs it and sends the result back.

The Supports column in [Models](https://bineric.com/platform/docs#models) marks the models that support tool calling (Tools). Image, audio, search and research models return 400 with `code: "tools_not_supported"` and `param: "tools"`, both when `tools` is sent and when the messages carry earlier tool calls or results.

### 1. Send tools

Describe each function with a name, a description and a JSON Schema for its parameters. `tool_choice: "auto"` lets the model decide; `"required"` makes it call one.

```json
{
  "model": "gpt-4o",
  "messages": [
    { "role": "user", "content": "What's the weather in Oslo?" }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }
  ],
  "tool_choice": "auto"
}
```

### 2. The model calls a tool

`finish_reason` is `tool_calls` and `message.tool_calls` holds the calls. `arguments` is a JSON string: parse it, then run your function.

```json
{
  "id": "chatcmpl-1768817914002-yq8xw50tv2g",
  "object": "chat.completion",
  "created": 1768817914,
  "model": "GPT-4o",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_abc123",
            "type": "function",
            "function": { "name": "get_weather", "arguments": "{\"city\":\"Oslo\"}" }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": { "prompt_tokens": 62, "completion_tokens": 18, "total_tokens": 80 }
}
```

### 3. Send the result back

Add the assistant message unchanged, then a `tool` message with the matching `tool_call_id`. Keep sending `tools`. The model then answers in text with `finish_reason: "stop"`.

```json
{
  "model": "gpt-4o",
  "messages": [
    { "role": "user", "content": "What's the weather in Oslo?" },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": { "name": "get_weather", "arguments": "{\"city\":\"Oslo\"}" }
        }
      ]
    },
    { "role": "tool", "tool_call_id": "call_abc123", "content": "{\"temp_c\": 12, \"sky\": \"cloudy\"}" }
  ],
  "tools": [ ... ]
}
```

## Errors

Errors use the OpenAI shape, so SDKs raise their usual exceptions.

```json
{
  "error": {
    "message": "temperature must be between 0 and 2",
    "type": "invalid_request_error",
    "param": "temperature",
    "code": "invalid_request"
  }
}
```

| Status | code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | A field is wrong; `param` names it (for example `temperature`, `tools[0].function.name` or `messages[0].content[1]`). |
| 400 | `model_not_found` | Unknown model id. Use an id from `GET /v1/models`. |
| 400 | `model_not_supported` | A decision model was sent to chat completions. Use the [Zuse API](https://bineric.com/zuse/docs). |
| 400 | `tools_not_supported` | The model does not support tools. |
| 400 | `null` | An attachment is too large, of an unsupported type, or could not be processed. |
| 401 | `missing_api_key`, `invalid_api_key` | No key, or the key is wrong or disabled. |
| 402 | `insufficient_quota` | Balance below $0.10. Buy credits. |
| 403 | `payment_required` | No completed purchase on the account yet. Buy credits. |
| 404 | `unknown_endpoint` | No such endpoint. |
| 429 | `rate_limit_exceeded` | Too many requests from your IP address. Wait for `Retry-After` seconds. |
| 429 | `concurrent_request_limit` | Too many requests running at once on your account. |
| 500 | `internal_error` | Something failed on our side. Retry. |
| 502 | `image_generation_failed` | The image model failed. |
| 503 | `model_not_configured` | The model is listed but not available right now. Try another model. |

Errors from the model provider, such as its own rate limits or content policy, are passed through with the provider's status and code. Retry 429 and 5xx responses with exponential backoff.

## Rate limits

- 500 requests a minute per IP address across `/v1` and `/api/v1`. Over the limit you get 429 with a `Retry-After` header and the standard `RateLimit-*` headers.
- Up to 50 requests running at once per account; more return 429 `concurrent_request_limit`.
- Each model's context window and output limit apply; see [Models](https://bineric.com/platform/docs#models).

## Image generation

Use an image model with chat completions. The last user message is the prompt.

`POST /v1/chat/completions`

```json
{
  "model": "gpt-image-1",
  "messages": [
    { "role": "user", "content": "A lighthouse on a rocky coast at dawn, watercolour" }
  ],
  "size": "1024x1024",
  "quality": "high"
}
```

- `gpt-image-1` also reads `size`, `quality`, `output_format` and `n` at the top level of the body.
- Attach images to the last user message to edit them instead; `_bineric.operation` is then `editing`.
- Image models do not stream. With `stream: true` you still get one JSON response, not events, so call them without streaming.

### Response

`message.content` holds each image as a Markdown image with a data URL. The raw data URLs are also in `_bineric.images`.

```json
{
  "id": "chatcmpl-1768817914002-yq8xw50tv2g",
  "object": "chat.completion",
  "created": 1768817914,
  "model": "gpt-image-1",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "![image 1](data:image/png;base64,iVBORw0KGgo...)" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 },
  "_bineric": {
    "images": ["data:image/png;base64,iVBORw0KGgo..."],
    "image_count": 1,
    "operation": "generation",
    "total_cost": 0.04
  }
}
```

When Stable Diffusion or a Gemini image model blocks a prompt, you get 200 with a short text reply, `finish_reason: "content_filter"` and an empty `_bineric.images`. `gpt-image-1` returns the provider's content policy error instead.

## Text to speech

Turn text into speech with the `text-to-speech` model. The last user message is the text.

`POST /v1/chat/completions`

| Field | Default | Description |
| --- | --- | --- |
| `voice` | `nb-NO-FinnNeural` | An Azure neural voice name, such as `nb-NO-PernilleNeural`, `en-US-JennyNeural` or `sv-SE-SofieNeural`. The voice decides the language you hear. |
| `language` | `nb-NO` | The locale of the text, such as `en-US`. Also accepted as `locale`. It does not choose a voice, so set `voice` too. |
| `rate` | `medium` | Speaking rate, as SSML prosody: `slow`, `medium`, `fast` or a percentage like `+10%`. |
| `pitch` | `medium` | Pitch, such as `low`, `high` or `+5%`. |
| `volume` | `loud` | Volume, such as `soft`, `medium` or `+20%`. |

Send these at the top level of the body. Norwegian voices: Finn (male), Iselin and Pernille (female). Voices for English, Swedish, Danish and other languages follow the same pattern, `<locale>-<Name>Neural`.

```json
{
  "model": "text-to-speech",
  "messages": [{ "role": "user", "content": "Hei, velkommen til Bineric!" }],
  "voice": "nb-NO-PernilleNeural",
  "language": "nb-NO",
  "rate": "medium"
}
```

### Response

The speech is MP3 (16 kHz, 32 kbps, mono), base64-encoded in `message.audio.data`. It is priced at $16 per million characters.

```json
{
  "id": "chatcmpl-1768817914002-yq8xw50tv2g",
  "object": "chat.completion",
  "created": 1768817914,
  "model": "Text to Speech",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "audio": { "data": "<base64 mp3>", "format": "mp3" }
      },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 },
  "_bineric": {
    "metadata": {
      "language": "nb-NO",
      "voice": "nb-NO-PernilleNeural",
      "textLength": 27,
      "format": "mp3",
      "quality": "16khz/32kbps",
      "processedChunks": 1,
      "totalChunks": 1,
      "settings": { "rate": "medium", "pitch": "medium", "volume": "loud" }
    },
    "characters": 27,
    "total_cost": 0.00043
  }
}
```

Long text is split into chunks of up to 500 characters at sentence ends. If some chunks fail, you still get 200 with the audio that worked; compare `processedChunks` with `totalChunks`.

```python
import base64

data = response.json()
audio = base64.b64decode(data["choices"][0]["message"]["audio"]["data"])
with open("speech.mp3", "wb") as f:
    f.write(audio)
```

## Speech to text

Transcribe audio with speaker labels using the `speech-to-text` model.

`POST /v1/chat/completions`

Send one audio file in the last user message, as an `input_audio` content part (WAV or MP3), a `file` part, or a data URL in `attachments`. Only the first file is transcribed.

```json
{
  "model": "speech-to-text",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "Transcribe this" },
        { "type": "input_audio", "input_audio": { "data": "<base64 audio>", "format": "mp3" } }
      ]
    }
  ],
  "language": "nb-NO"
}
```

`language` (or `locale`, which wins if both are sent) is the main locale, default `en-US`. The transcriber also detects en-US, es-ES, de-DE, fr-FR, it-IT, pt-BR, nl-NL, nb-NO, fi-FI and sv-SE.

### Response

```json
{
  "id": "chatcmpl-1768817914002-yq8xw50tv2g",
  "object": "chat.completion",
  "created": 1768817914,
  "model": "Speech to Text",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Speaker 1: Hello and welcome.\nSpeaker 2: Thanks for having me."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 },
  "_bineric": {
    "phrases": [
      { "speaker": 1, "text": "Hello and welcome.", "startTime": "PT0.5S", "duration": "PT1.8S" },
      { "speaker": 2, "text": "Thanks for having me.", "startTime": "PT2.4S", "duration": "PT1.6S" }
    ],
    "total_cost": 0.0002
  }
}
```

- `_bineric.phrases` has one entry per phrase, with ISO 8601 `startTime` and `duration`.
- Priced at $0.18 per hour of recognised speech.
- Transcription runs as a batch job and does not stream. After 5 minutes it stops with 500 `Transcription timed out after 5 minutes`; split long recordings.

## Deep research

Research a question on the web and get an answer with sources, using the `deep-research` model. $0.25 per call.

`POST /v1/chat/completions`

```json
{
  "model": "deep-research",
  "messages": [
    { "role": "user", "content": "What changed in EU AI regulation this year?" }
  ],
  "options": { "maxDepth": 5, "timeLimit": 180, "maxUrls": 5 }
}
```

| Option | Default | Description |
| --- | --- | --- |
| `options.maxDepth` | 5 | How many rounds of searching and reading. |
| `options.timeLimit` | 180 | Time limit in seconds. |
| `options.maxUrls` | 5 | Maximum number of pages to read. |
| `stream` | `false` | Send progress events while it works (top level or in `options`). |

The research options are only read inside `options`. Each account can make 50 deep research calls a calendar month; unused calls do not carry over.

### Response

```json
{
  "id": "chatcmpl-1768817914002-yq8xw50tv2g",
  "object": "chat.completion",
  "created": 1768817914,
  "model": "Deep Research",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Based on my research..." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 320, "completion_tokens": 540, "total_tokens": 860 },
  "_bineric": {
    "request_id": "req_abc123",
    "citations": [
      { "url": "https://example.com/article", "title": "Article title", "description": "Relevant excerpt..." }
    ],
    "activities": [
      { "type": "search", "status": "complete", "message": "Searching...", "timestamp": "2026-10-26T10:00:00Z", "depth": 1 }
    ],
    "deep_research_info": { "usage_count": 5, "usage_limit": 50, "usage_left": 45 },
    "total_cost": 0.25,
    "response_time_ms": 12000
  }
}
```

Token counts in `usage` are estimates; you pay the fixed price per call.

### Streaming

With `stream: true` you get named events, not OpenAI chunks: `info` when research starts, `activity` for each step, and `complete` with the answer in `response`. There is no `[DONE]`.

```text
event: info
data: {"request_id":"req_abc123","model":"Deep Research","message":"Deep research started"}

event: activity
data: {"type":"search","message":"Searching...","request_id":"req_abc123"}

event: complete
data: {"success":true,"request_id":"req_abc123","model":"Deep Research","response":"Based on my research...","citations":[...],"activities":[...],"deep_research_info":{"usage_count":5,"usage_limit":50,"usage_left":45},"usage":{"prompt_tokens":320,"completion_tokens":540,"total_tokens":860,"total_cost":0.25,"response_time_ms":12000}}

event: error
data: {"success":false,"request_id":"req_abc123","error":{"type":"ProcessingError","message":"...","code":500}}
```

### Errors

| Status | Meaning |
| --- | --- |
| 403 `not_eligible` | This month's 50 calls are used up, or the balance is too low. |
| 429 | Too many requests at once, or the research service's own limit. |
| 500, 502 | The research failed. |
| 503 | Deep research is temporarily unavailable. |

When streaming, everything except the 403 arrives as an `error` event.

## Convert a file to base64

Upload a file and get it back as a data URL you can attach to a chat message.

`POST /api/v1/media/base64`

Multipart form data with one file in a field named `file`, up to 200 MB. Free; no credit is used.

cURL:

```bash
curl -X POST https://api.bineric.com/api/v1/media/base64 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@report.pdf"
```

Python:

```python
import requests

with open("report.pdf", "rb") as f:
    response = requests.post(
        "https://api.bineric.com/api/v1/media/base64",
        headers={"Authorization": "Bearer YOUR_API_KEY"},
        files={"file": f},
    )

data_url = response.json()["data"]["base64String"]
```

JavaScript:

```javascript
const form = new FormData();
form.append("file", fileInput.files[0]);

const response = await fetch("https://api.bineric.com/api/v1/media/base64", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_KEY" },
  body: form,
});

const { data } = await response.json();
console.log(data.base64String);
```

### Response

```json
{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "base64String": "data:application/pdf;base64,JVBERi0xLjcK...",
    "originalName": "report.pdf",
    "fileInfo": { "mimeType": "application/pdf" },
    "size": { "original": 102400, "base64": 136563 }
  }
}
```

The MIME type is read from the file's bytes, so binary formats (images, audio, PDF, Office files) are recognised. Plain-text formats such as TXT, CSV, MD, JSON and HTML come back as `application/octet-stream`.

| Status | code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | No file, or larger than 200 MB. |
| 401 | `missing_api_key`, `invalid_api_key` | No key, or the key is wrong. |
| 500 | `internal_error` | The file could not be converted. A form field not named `file` also ends here. |

## Decode base64

Send a base64 data URL and get the original file back.

`POST /api/v1/decode-base64`

cURL:

```bash
curl -X POST https://api.bineric.com/api/v1/decode-base64 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}' \
  --output pixel.png
```

Python:

```python
import requests

response = requests.post(
    "https://api.bineric.com/api/v1/decode-base64",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="},
)

with open("pixel.png", "wb") as f:
    f.write(response.content)
```

The response is the file itself. `Content-Type` is detected from the decoded bytes, and `Content-Disposition` is `inline; filename="file.<ext>"`. Errors: 400 `invalid_request` with `param: "data"` when `data` is missing, not a string or not valid base64, and 500 `internal_error` when decoding fails.

## Usage

Your API usage and cost, per model.

`GET /api/v1/monitoring/usage`

Filter with `startDate` and `endDate` (YYYY-MM-DD). Send both: with only one, the result covers all time. Without dates, `period` is `{"start": "all time", "end": "current"}`.

```bash
curl "https://api.bineric.com/api/v1/monitoring/usage?startDate=2026-10-01&endDate=2026-10-31" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "success": true,
  "data": {
    "by_model": [
      {
        "model": "GPT-4o",
        "usage": { "input_tokens": 50000, "output_tokens": 75000, "total_cost": 0.88, "request_count": 150 }
      }
    ],
    "total_usage": { "total_cost": 10.25, "total_tokens": 500000, "total_requests": 500 },
    "recent_requests": [
      { "model": "GPT-4o", "cost": 0.0213, "tokens": 1840, "timestamp": "2026-10-31T14:30:00.000Z" }
    ],
    "period": { "start": "2026-10-01", "end": "2026-10-31" }
  }
}
```

`model` is the model's display name. `recent_requests` holds the last 5 paid requests. All numbers are JSON numbers.

## Daily usage

Today's usage and whether your credit is active.

`GET /api/v1/monitoring/daily-usage`

```bash
curl https://api.bineric.com/api/v1/monitoring/daily-usage \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "success": true,
  "data": {
    "payment_status": { "active": true, "expires_at": "2026-11-20T09:12:00.000Z" },
    "today_usage": { "requests": 42, "input_tokens": 15000, "output_tokens": 22000, "total_cost": 0.85 },
    "last_request": { "time": "2026-10-26T14:30:00.000Z", "model": "Claude Sonnet 4.6", "cost": 0.015 }
  }
}
```

- "Today" is the server's calendar day.
- `requests` counts paid requests; the token and cost totals include every API request today.
- `last_request` is `null` when there are no paid requests yet.
- `expires_at` is only present while `active` is true.

## Balance

Your credit balance and lifetime token usage.

`GET /api/v1/monitoring/balance`

```bash
curl https://api.bineric.com/api/v1/monitoring/balance \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "success": true,
  "data": {
    "balance": 25.5,
    "inputTokens": 500000,
    "outputTokens": 750000,
    "lastUpdated": "2026-10-26T14:30:00.000Z",
    "payment_status": { "active": true, "expires": "2026-11-20T09:12:00.000Z" },
    "usage": {
      "daily": { "requests": 42, "tokens": { "input": 15000, "output": 22000 }, "cost": 0.85 },
      "total": { "input_tokens": 500000, "output_tokens": 750000 }
    }
  }
}
```

`balance` is in USD. `payment_status.active` means a completed purchase in the last 30 days, and `expires` (present only while active) is 30 days after it. A new account has `balance: 0` and `lastUpdated: null`.
