# Vision inputs

> Send images to chat models over the OpenAI, Anthropic, and Gemini protocols using image_url with an HTTP(S) URL or base64 data URI, billed as input tokens.

Vision-capable chat models accept images inside the message `content` array. Images are billed as **tokens** — each image adds input tokens to the request, proportional to its resolution.

## Prerequisites

To send images you need:

- An **API key** — one key works across all three protocols (`Authorization: Bearer` for OpenAI, `x-api-key` or `Authorization: Bearer` for Anthropic, `x-goog-api-key` or `?key=` for Gemini).
- A model with the **vision** capability — check the capability flags in the catalogue. The examples below use `deepseek-chat` as a placeholder; swap in any vision-capable model.
- The right **base URL** per protocol:
  - OpenAI-compatible: `https://api.mirapi.ai/v1`
  - Anthropic: `https://api.mirapi.ai`
  - Gemini: `https://api.mirapi.ai` or `https://api.mirapi.ai/v1beta`

## OpenAI-compatible format

OpenAI-style requests place images in the message `content` array as `image_url` parts. The `url` can be an HTTP(S) URL or a base64 data URI.

### Using an image URL

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.mirapi.ai/v1", api_key=os.environ["MIRAPI_API_KEY"])

response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What is in this photo?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}},
        ],
    }],
)

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

Send the text part first, then the images — this ordering is parsed most reliably across models.

### Using a base64 data URI

For local or private images, encode the file as a data URI:

```python
import base64

def data_uri(path: str) -> str:
    with open(path, "rb") as f:
        encoded = base64.b64encode(f.read()).decode()
    return f"data:image/jpeg;base64,{encoded}"
```

Pass the result as `image_url.url`. Base64 is required for files that are not publicly reachable.

## Anthropic protocol

Anthropic-format requests go to `POST /v1/messages` and carry the image as an `image` part with a base64 source. Base URL: `https://api.mirapi.ai` (no `/v1`).

```json
{
  "model": "deepseek-chat",
  "max_tokens": 1024,
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "Describe this image"},
        {
          "type": "image",
          "source": {
            "type": "base64",
            "media_type": "image/jpeg",
            "data": "<base64>"
          }
        }
      ]
    }
  ]
}
```

## Gemini protocol

Gemini-format requests go to `POST /v1beta/models/{model}:generateContent` and place images in `contents[].parts` using `inlineData` (base64) or `fileData` (a file URI). Base URL: `https://api.mirapi.ai` or `https://api.mirapi.ai/v1beta`.

```json
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {"text": "Describe this image"},
        {
          "inlineData": {
            "mimeType": "image/jpeg",
            "data": "<base64>"
          }
        }
      ]
    }
  ]
}
```

## Parameters

| Parameter | Required | Description |
|---|---|---|
| `model` | yes | Model id; must support vision. |
| `messages` / `contents` | yes | The conversation; image parts go inside `content` / `parts`. |
| `type` | yes | `image_url` (OpenAI), `image` (Anthropic), `inlineData` / `fileData` (Gemini). |
| `url` (OpenAI) | yes | HTTP(S) URL or base64 data URI. |
| `source` (Anthropic) | yes | Base64 source with `media_type` and `data`. |
| `mimeType` (Gemini) | yes | MIME type for `inlineData`. |
| `max_tokens` (Anthropic) | yes | Required by the Anthropic protocol. |

Commonly supported formats are PNG, JPEG, WebP, and GIF; the exact set depends on the model, so check the catalogue.

## Errors

- **401** — the key is missing, invalid, or unknown. Fix your credentials.
- **403** — the key is valid but the request was rejected: insufficient balance, a model-whitelist miss, quota, or IP restrictions. Top up or adjust the key's constraints, then retry.
- **413** — the request is too large. Downscale or compress the image and retry.
- **429** — rate limited. Retry with jittered exponential backoff (no `Retry-After` header is returned).
- **500** — an upstream failure. Idempotent requests are safe to retry.

Every error message ends with a request ID — include it when you open a support ticket.

## Billing

- Images are billed as **input tokens**; a single high-resolution image can cost thousands of tokens. Check per-token pricing in the catalogue before batch runs.
- Non-streaming responses report token counts in `usage`, not dollar amounts — reconcile costs in the console billing log or via `GET /api/log/token`.
- MirAPI runs on a prepaid USD balance with no monthly fee. When the balance is exhausted, requests return **403**; topping up restores the same key immediately.

## Troubleshooting

- **Model rejects the image** — the model lacks the vision capability; pick a vision-capable model from the catalogue.
- **403 on a vision model** — this is a balance/whitelist/quota issue, not an image-format problem.
- **413 on a large image** — downscale or re-encode the image before retrying.
- **URL images fail** — the URL must be publicly reachable; use a base64 data URI for private files.
- **Multiple images in the wrong order** — put the text prompt first, then the images.

## Related links

- [Models & pricing](/docs/models)
- [Image generation](/docs/guides/images)
- [OpenAI compatibility](/docs/openai-compat)
- [Billing & top-ups](/docs/billing)
- [Authentication](/docs/api-reference/authentication)
- [Errors](/docs/api-reference/errors)