# Gemini-compatible API

https://modelportal.ai/docs/api-gemini

REST prefix `https://modelportal.ai/v1beta`, authenticated with `x-goog-api-key: YOUR_API_KEY`, the query parameter `key=YOUR_API_KEY`, or `Authorization: Bearer YOUR_API_KEY`; if you send more than one, they must match. Requests and responses follow the Gemini API. `{model}` in the path is the model ID, with its slash written as is or as `%2F`.

## POST /v1beta/models/{model}:generateContent

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `contents` | array | Yes | Conversation contents; `parts` support `text`, `inlineData`, `fileData`, `functionCall` and `functionResponse` |
| `systemInstruction` | object | No | System prompt |
| `generationConfig` | object | No | `temperature`, `topP`, `topK`, `maxOutputTokens`, `candidateCount`, `responseMimeType`, `responseSchema`, `thinkingConfig` and so on; without `maxOutputTokens` the platform fills one from the model limit and your available balance |
| `tools`, `toolConfig` | — | No | Function calling |

```json
{
  "candidates": [{"content": {"role": "model", "parts": [{"text": "Hello!"}]}, "finishReason": "STOP"}],
  "usageMetadata": {"promptTokenCount": 9, "candidatesTokenCount": 4, "totalTokenCount": 13, "cost": 0.000013},
  "provider": "PROVIDER_ID",
  "attempts": [{"provider": "PROVIDER_ID", "endpoint": "ENDPOINT_ID", "status": 200, "cost": 0.000013}],
  "modelVersion": "MODEL_ID"
}
```

`usageMetadata` is the billed usage: `promptTokenCount` includes cache reads and writes, `candidatesTokenCount` excludes reasoning, and `thoughtsTokenCount` and `cachedContentTokenCount` are listed separately. `usageMetadata.cost` is the charge for this call (USD); `provider` and `attempts` name the provider that served it and each attempt. When streaming, these three are in the last chunk with `usageMetadata`. Response headers are in the [API overview](/docs/api-overview).

## POST /v1beta/models/{model}:streamGenerateContent

Same body as `generateContent`. With `?alt=sse` it streams SSE; without it you get a JSON array.

## POST /v1beta/models/{model}:countTokens

Counts input tokens for free. The body may be `contents` or a `generateContentRequest`.

## POST /v1beta/models/{model}:embedContent and :batchEmbedContents

Creates embeddings; the model needs the embedding capability. In `batchEmbedContents`, each item's `model` must be empty or match the model in the path.

## GET /v1beta/models

Lists the models this key may call, with each model's supported methods and token limits.

## Errors

Error bodies are `{"error": {"code", "message", "status"}}`, where `code` is the HTTP status and `status` depends only on it:

| HTTP | `status` |
| --- | --- |
| 400, 422 | `INVALID_ARGUMENT` |
| 401 | `UNAUTHENTICATED` |
| 402, 429 | `RESOURCE_EXHAUSTED` |
| 403 | `PERMISSION_DENIED` |
| 404, 410 | `NOT_FOUND` |
| 502, 503 | `UNAVAILABLE` |

On 422, `error.excluded` lists the exclusion reasons. The platform codes behind each status are in [Common errors](/docs/errors).
