<!-- toc: Premiers pas -->
# Hyperouter API {#introduction}

*Version v1 · Mis à jour le 25 octobre 2026*

> Cette documentation est disponible en Markdown pour les assistants IA. Commencez par [/llms.txt](https://hyperouter.app/llms.txt), ou récupérez [/llms-full.txt](https://hyperouter.app/llms-full.txt) pour avoir toutes les pages dans un seul fichier. Cette page est [/docs.md](https://hyperouter.app/docs.md).

Hyperouter est une API HTTP unique devant de nombreux éditeurs de modèles. Vous envoyez une requête qui nomme un modèle. Hyperouter choisit un fournisseur capable de la servir, lui transmet la requête et débite votre solde au tarif public de ce fournisseur.

`BASE https://api.hyperouter.app/v1`

Chaque endpoint suit les formats de requête et de réponse d’OpenAI. Les OpenAI SDK fonctionnent dès que vous changez l’URL de base et la clé API. Hyperouter ajoute quelques champs aux corps d’OpenAI. Ils sont signalés comme ajouts dans cette référence.

| Endpoint | Rôle | Auth |
| --- | --- | --- |
| `GET /v1/models` | Lister les modèles que votre compte peut appeler. | Clé API |
| `POST /v1/chat/completions` | Chat completion, avec streaming optionnel. | Clé API |
| `POST /v1/images/generations` | Texte vers image. | Clé API |
| `POST /v1/images/edits` | Modifier des images. | Clé API |
| `POST /v1/videos` | Créer une vidéo à partir d’un prompt, avec une image facultative. | Clé API |
| `POST /v1/videos/edits` | Modifier ou agrandir une vidéo. | Clé API |
| `POST /v1/videos/extensions` | Prolonger une vidéo. | Clé API |
| `POST /v1/videos/{id}/remix` | Produire une nouvelle version d’une vidéo avec un nouveau prompt. | Clé API |
| `GET /v1/videos`, `GET /v1/videos/{id}`, `GET /v1/videos/{id}/content`, `DELETE /v1/videos/{id}` | Lister, lire, télécharger et supprimer des vidéos. | Clé API |
| `GET /v1/generations/{id}` | Lire l’enregistrement de n’importe quelle requête : statut, consommation et coût. | Clé API |
| `GET /v1/credits` | Votre solde, ce que bloquent les requêtes ouvertes, et ce qu’il reste à dépenser (`balance_usd`, `held_usd`, `available_usd`, chaînes décimales en USD). | Clé API |

> [Connectez-vous sur dash.hyperouter.app](https://dash.hyperouter.app) avec Google ou GitHub pour créer une clé API. `GET /v1/models` renvoie les modèles que vous pouvez appeler au moment de la requête.

## Démarrage rapide {#quickstart}

1. [Connectez-vous](https://dash.hyperouter.app) et créez une clé API. Les clés commencent par `sk-hr-v1-` et ne sont affichées qu’une seule fois.
2. Stockez-la dans une variable d’environnement, par exemple `HYPEROUTER_API_KEY`.
3. Ajoutez des crédits : ouvrez une demande de recharge dans le tableau de bord. Notre équipe crédite votre solde une fois le paiement confirmé.
4. Pointez n’importe quel OpenAI SDK vers l’URL de base ci-dessus et choisissez un modèle.

```bash
curl https://api.hyperouter.app/v1/chat/completions \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "x-ai/grok-4.7",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.hyperouter.app/v1",
  apiKey: process.env.HYPEROUTER_API_KEY,
});

const res = await client.chat.completions.create({
  model: "x-ai/grok-4.7",
  messages: [{ role: "user", content: "Hello" }],
});
console.log(res.choices[0].message.content);
```

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.hyperouter.app/v1",
    api_key=os.environ["HYPEROUTER_API_KEY"],
)

res = client.chat.completions.create(
    model="x-ai/grok-4.7",
    messages=[{"role": "user", "content": "Hello"}],
)
print(res.choices[0].message.content)
```

## Authentification {#authentication}

Envoyez votre clé API comme bearer token dans chaque requête.

```
Authorization: Bearer sk-hr-v1-...
```

Les clés donnent accès à votre solde. Gardez-les côté serveur. Ne les mettez jamais dans du code navigateur ou mobile. Hyperouter ne stocke que le hash de chaque clé : une clé perdue ne peut donc pas être affichée de nouveau, il faut en créer une nouvelle.

Dans le [tableau de bord](https://dash.hyperouter.app/keys), vous pouvez nommer chaque clé, lui fixer une limite de crédit et la désactiver. Une clé désactivée cesse de fonctionner immédiatement. Une requête qui ferait dépasser sa limite à une clé est refusée avec `402 insufficient_credits`.

### Erreurs d’authentification

| Statut | type | code | Quand | message |
| --- | --- | --- | --- | --- |
| `401` | `authentication_error` | `invalid_api_key` | L’en-tête est absent, n’est pas de type `Bearer`, ou la clé ne commence pas par `sk-hr-v1-`. | `The API key is missing or malformed. Send Authorization: Bearer sk-hr-v1-…` |
| `401` | `authentication_error` | `invalid_api_key` | La clé est inconnue ou désactivée. | `The API key is not valid.` |
| `403` | `permission_error` | `account_suspended` | Le compte est suspendu. La vérification est faite à chaque requête. | `This account is suspended. Open a support ticket.` |
| `429` | `rate_limit_error` | `too_many_auth_failures` | Plus de 30 requêtes provenant de votre adresse en une minute ont utilisé une clé absente ou invalide. Toutes les requêtes `/v1` de cette adresse sont refusées pendant 15 minutes, même avec une clé valide. `Retry-After` indique les secondes restantes. | `Too many failed API key checks from this address. Try again after the time in Retry-After.` |
| `500` | `server_error` | `internal` | Hyperouter n’a pas pu vérifier la clé. | `An internal error occurred. Try again.` |

```bash
curl https://api.hyperouter.app/v1/generations/0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21 \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY"
```

```json
{ "error": { "message": "The API key is not valid.", "type": "authentication_error", "code": "invalid_api_key", "param": null } }
```

## Erreurs {#errors}

Chaque erreur utilise l’objet d’erreur d’OpenAI et un statut HTTP correspondant.

```json
{
  "error": {
    "message": "n must be an integer from 1 to 128",
    "type": "invalid_request_error",
    "code": "invalid_body",
    "param": "n"
  }
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `message` | string | Une phrase qui dit ce qui ne va pas. Ne l’analysez pas. |
| `type` | string | La classe d’erreur. Voir la liste ci-dessous. |
| `code` | string | Un identifiant stable de la cause. Fiez-vous à ce champ. |
| `param` | string \| null | Le champ de la requête en cause, ou `null`. |

### Types et statuts

| Statut | type | Signification |
| --- | --- | --- |
| `400` | `invalid_request_error` | La requête est invalide. `code` désigne la cause et `param` le champ. |
| `401` | `authentication_error` | La clé API est absente ou invalide. |
| `402` | `insufficient_credits` | Votre solde, ou la limite de la clé, ne peut pas couvrir le coût maximal de la requête. |
| `403` | `permission_error` | Le compte est suspendu. |
| `404` | `not_found` | Cet id n’existe pas, l’id appartient à un autre compte, ou une vidéo a dépassé son `expires_at`. |
| `409` | `conflict` | L’objet n’est pas dans un état qui autorise l’appel (`video_not_finished`), ou conflit d’`Idempotency-Key` (voir [Nouvelles tentatives et idempotence](#idempotency)). |
| `422` | `invalid_request_error` | La requête était valide, mais sa réponse a été refusée (`expansion_refused`). Non facturé. |
| `429` | `rate_limit_error` | Tous les fournisseurs du modèle sont saturés, votre compte dépasse ses [limites de débit](#rate-limits), ou trop de vérifications de clé API depuis votre adresse ont échoué. Réessayez après le délai indiqué dans l’en-tête `Retry-After`. |
| `500`, `501` | `server_error` | Hyperouter a échoué, ou l’endpoint n’est pas implémenté. Non facturé. |
| `503` | `upstream_error` | Un fournisseur a échoué ou n’a pas répondu à temps : `code` indique lequel (`all_venues_failed`, `upstream_error` ou `timeout`). L’en-tête `X-Upstream-Status` contient `502` (échec) ou `504` (délai dépassé). |

Hyperouter ne répond jamais `502` ni `504` : ils vous parviendraient sous forme de page brute de notre réseau en périphérie, sans le corps d’erreur. Là où une passerelle répondrait ces statuts, vous recevez `503` avec le corps ci-dessous et le statut de la passerelle dans `X-Upstream-Status`.

### Codes possibles

| code | Statut | Quand |
| --- | --- | --- |
| `invalid_body` | 400 | Un champ du corps ou un paramètre de requête ne peut pas être analysé, est du mauvais type, ou sort de ses valeurs autorisées. `param` le désigne. |
| `unsupported_content_type` | 400 | `/v1/images/generations` a reçu un corps `multipart/form-data`. Envoyez du JSON. `param` vaut `null`. |
| `request_too_large` | 413 | Un envoi `multipart/form-data` dépasse 50 MiB. Envoyez les médias volumineux sous forme d’URL https. |
| `unknown_model` | 400 | Le modèle n’existe pas pour cet endpoint, ou aucun fournisseur ne peut le servir avec vos préférences `provider`. |
| `unsupported_parameter` | 400 | Un paramètre qui existe pour d’autres modèles n’est pas disponible sur ce modèle. Voir [Paramètres propres à chaque modèle](#parameters). |
| `invalid_combination` | 400 | L’éditeur n’autorise pas ces paramètres ensemble, par exemple des médias de référence avec des images clés. |
| `invalid_size` | 400 | `size` n’est pas de la forme `WIDTHxHEIGHT`, ou n’est pas autorisé pour le modèle. |
| `too_many_images` | 400 | `n` ou le nombre d’images en entrée dépasse la limite. |
| `invalid_image`, `invalid_video` | 400 | L’URL d’une image ou d’une vidéo n’est pas en https vers un hôte public : une URL `data:`, une URL `http://`, une adresse IP ou un nom interne. `param` désigne le champ. |
| `image_required` | 400 | Le modèle exige une image en entrée et vous n’en avez envoyé aucune (ou il ne fait que modifier, et vous avez appelé generations). |
| `invalid_seconds` | 400 | `seconds` n’est pas autorisé pour le modèle, ou est absent pour une vidéo donnée par URL. |
| `unsupported_operation` | 400 | Le modèle ne prend pas en charge l’opération (modifier, prolonger ou remixer, ou modifier une image). |
| `video_not_ready` | 400 | La vidéo source n’est pas terminée. |
| `context_length_exceeded` | 400 | Le prompt de chat et `max_tokens` ne tiennent pas dans le contexte du modèle. |
| `content_rejected` | 400 | Le filtre de sécurité de l’éditeur a refusé l’entrée. |
| `vendor_rejected` | 400 | Le modèle a refusé la requête comme invalide. Vérifiez ses paramètres et ses entrées. |
| `clip_too_long` | 400 | Un clip de référence et les `seconds` demandées dépassent la limite du modèle (30 s). Non facturé. |
| `prompt_rejected` | 400 | Le prompt demande au modèle d’expansion du prompt ses instructions. Non facturé. Voir [Expansion du prompt](#prompt-expansion-model). |
| `expansion_refused` | 422 | La réécriture du modèle d’expansion du prompt a été refusée ou est revenue vide. Non facturé. Voir [Expansion du prompt](#prompt-expansion-model). |
| `invalid_api_key` | 401 | Voir [Authentification](#authentication). |
| `insufficient_credits` | 402 | Voir [Facturation et limites](#billing). |
| `account_suspended` | 403 | Le compte est suspendu. |
| `moderation_not_enabled` | 403 | `moderation: "low"` n’est pas activé pour le compte. Voir [Modération](#moderation). |
| `feature_not_enabled` | 403 | `prompt_expand` n’est pas activé pour le compte, sur un modèle qui le propose. Ouvrez un ticket de support pour en demander l’accès. Rien n’est réservé ni envoyé au fournisseur. Voir [Expansion du prompt](#prompt-expansion). |
| `model_not_enabled` | 403 | Le modèle exige un accès accordé à votre compte. Ouvrez un ticket de support pour le demander. Rien n’est bloqué ni envoyé au fournisseur. |
| `not_found` | 404 | Cet objet n’existe pas. |
| `video_not_finished` | 409 | Vous avez supprimé une vidéo encore en cours de création. |
| `venue_busy` | 429 | Tous les fournisseurs du modèle de chat ont dépassé leur budget de débit. |
| `rate_limited` | 429 | Votre compte dépasse ses requêtes par minute ou ses requêtes simultanées. Voir [Limites de débit](#rate-limits). |
| `too_many_auth_failures` | 429 | Plus de 30 requêtes provenant de votre adresse en une minute ont utilisé une clé API invalide. Voir [Limites de débit](#rate-limits). |
| `all_venues_failed` | 503 | Aucun fournisseur n’est en bonne santé, ou tous ont échoué. |
| `upstream_error` | 503 | Une tâche d’image ou de vidéo a échoué au niveau du modèle. Vous n’êtes pas facturé. |
| `internal`, `server_error`, `no_price` | 500 | Hyperouter a échoué (`no_price` : le modèle ne peut pas être tarifé pour le moment). Non facturé. |
| `not_implemented` | 501 | `This endpoint is not implemented yet.` |
| `invalid_idempotency_key` | 400 | L’en-tête `Idempotency-Key` dépasse 255 caractères ou n’est pas en ASCII imprimable. Voir [Nouvelles tentatives et idempotence](#idempotency). |
| `idempotency_key_reused` | 409 | L’`Idempotency-Key` a déjà été utilisée avec une requête différente. |
| `idempotency_key_in_use` | 409 | Une requête avec cette `Idempotency-Key` est encore en cours de démarrage. Réessayez avec la même clé. |
| `idempotency_key_replayed` | 409 | Une chat completion avec cette `Idempotency-Key` a déjà été exécutée. `error.generation_id` en est l’id. |
| `timeout` | 503 | Une image synchrone n’était pas prête au bout de 90 secondes. Elle est toujours en cours de création : `error.generation_id` est l’id à interroger. |

## Nouvelles tentatives et idempotence {#idempotency}

Une erreur réseau ou un délai dépassé ne vous dit pas si Hyperouter a reçu la requête. Si vous la renvoyez, vous pouvez démarrer une seconde tâche et payer deux fois. Une `Idempotency-Key` rend la répétition sûre : Hyperouter reconnaît la répétition, ne démarre pas de seconde tâche, ne bloque pas de second montant, et répond à partir de la première requête.

**Envoyez-en une sur chaque requête qui coûte de l’argent.** Générez une nouvelle valeur aléatoire, par exemple un UUID v4, pour chaque nouvelle requête. Conservez-la avec la requête et envoyez la même valeur chaque fois que vous renvoyez cette requête.

### Envoyer une clé

L’en-tête est accepté sur ces endpoints :

| Endpoint | Une répétition renvoie |
| --- | --- |
| `POST /v1/images/generations`, `POST /v1/images/edits` | l’image de la première requête, ou son `202` avec le même `id` sous `Prefer: respond-async` |
| `POST /v1/videos`, `/v1/videos/edits`, `/v1/videos/extensions`, `/v1/videos/{id}/remix` | l’objet Video de la première requête, dans son état actuel |
| `POST /v1/chat/completions` | `409` `idempotency_key_replayed` avec l’id de la première requête (voir [Chat completions](#idempotency-chat)) |

Les OpenAI SDK n’envoient pas cet en-tête d’eux-mêmes. Passez-le comme en-tête supplémentaire ; les SDK le renvoient alors inchangé lors de leurs nouvelles tentatives automatiques.

```bash
curl https://api.hyperouter.app/v1/images/generations \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -d '{"model": "x-ai/grok-imagine-image", "prompt": "A lighthouse at dusk", "n": 1}'
```

```python
import uuid

key = str(uuid.uuid4())  # one per request; reuse it when you retry this request
img = client.images.generate(
    model="x-ai/grok-imagine-image",
    prompt="A lighthouse at dusk",
    extra_headers={"Idempotency-Key": key},
)
```

```typescript
import { randomUUID } from "node:crypto";

const key = randomUUID(); // one per request; reuse it when you retry this request
const img = await client.images.generate(
  { model: "x-ai/grok-imagine-image", prompt: "A lighthouse at dusk" },
  { headers: { "Idempotency-Key": key } },
);
```

### Ce que renvoie une répétition

| Vous envoyez | Hyperouter répond |
| --- | --- |
| Une nouvelle clé | Exécute la requête normalement. |
| La même clé et la même requête, après acceptation de la première | Le résultat de la première requête, comme dans le tableau ci-dessus. Pas de seconde tâche, pas de second blocage, pas de second débit. |
| La même clé et la même requête, alors que la première est encore en cours d’acceptation | Attend la première, puis répond comme ci-dessus. Si la première n’a pas démarré sa tâche au bout de 60 secondes : `409` `idempotency_key_in_use`. Réessayez avec la même clé. |
| La même clé et une requête différente | `409` `idempotency_key_reused`. Rien ne s’exécute. Utilisez une nouvelle clé. |
| La même clé, après que la première a été refusée avant le démarrage de toute tâche (un `400`, un `402`, ou un `5xx` envoyé sans `x-should-retry: false`) | Exécute la requête comme une nouvelle, ce qui vous permet de la corriger et de la renvoyer avec la même clé. |
| La même clé, plus de 30 minutes après la première requête | Exécute la requête comme une nouvelle. |
| Une clé de plus de 255 caractères, ou qui n’est pas en ASCII imprimable | `400` `invalid_idempotency_key`. Rien ne s’exécute. |

### Règles

- **Portée.** Une clé appartient à la clé API qui l’a envoyée. Deux clés API peuvent utiliser la même valeur sans s’influencer.
- **Fenêtre.** Une clé est honorée pendant 30 minutes après la première requête. Réessayez dans ce délai.
- **Même requête.** Deux requêtes sont identiques lorsque la méthode, le chemin et le corps correspondent. Le JSON est comparé après analyse : l’ordre des champs et les espaces n’ont pas d’importance. Une image ou une vidéo est une URL dans le corps : c’est l’URL qui est comparée, jamais les octets qu’elle sert.
- **Facturation.** Une requête effectuée sous une clé est facturée au plus une fois, quel que soit le nombre de répétitions. Une répétition servie à partir de la première requête ne coûte rien.
- **Garantie.** Une clé ne démarre jamais plus d’une tâche, y compris lorsque deux répétitions arrivent en même temps.

### Chat completions {#idempotency-chat}

Les réponses de chat ne sont pas rejouées. Une répétition avec la même clé répond `409` avec le code `idempotency_key_replayed`, et `error.generation_id` est l’id de la première requête. La requête n’est ni réexécutée ni refacturée. Retrouvez la première requête avec [GET /v1/generations/{id}](#generations) pour voir son statut, sa consommation et son coût. Si la première chat completion a échoué et n’a pas été facturée, sa clé est libérée et une répétition s’exécute comme une nouvelle requête.

```json
{
  "error": {
    "message": "A request with this Idempotency-Key already ran as generation 019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17. Chat replies are not replayed: GET /v1/generations/019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17 for its record.",
    "type": "conflict",
    "code": "idempotency_key_replayed",
    "param": null,
    "generation_id": "019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17"
  }
}
```

### Nouvelles tentatives automatiques dans les OpenAI SDK

Les OpenAI SDK réessaient deux fois par défaut une réponse `408`, `409`, `429` ou `5xx`, sauf si la réponse porte l’en-tête `x-should-retry: false`. Hyperouter envoie `x-should-retry: false` sur toute réponse pour laquelle une nouvelle tentative démarrerait une seconde tâche ou un second débit :

- le `503` `timeout` d’une image synchrone, dont la tâche continue et est facturée si elle réussit ; interrogez [GET /v1/generations/{id}](#generations) avec son `generation_id` ;
- un `500` après la création de la tâche, par exemple lorsqu’une image terminée ne peut pas être relue ;
- `409` `idempotency_key_reused` et `idempotency_key_replayed`, qu’une nouvelle tentative ne peut pas changer.

Il ne l’envoie jamais sur un `429`, sur un `409` `idempotency_key_in_use`, ni sur un `5xx` survenu avant l’existence de toute tâche. Dans ces cas, réessayer est sans risque. Avec une `Idempotency-Key`, chaque nouvelle tentative est sûre.

<!-- toc: Référence de l’API -->
## Modèles {#models}

`GET /v1/models`

Liste tous les modèles que votre compte peut appeler à l’instant, avec leurs prix et ce qu’ils prennent en charge. Un modèle sans fournisseur activé n’est pas listé. Certains modèles exigent un accès accordé à votre compte : ils sont listés dès qu’il l’est. Cet endpoint exige une clé API et liste les modèles que le compte de cette clé peut appeler. La réponse peut être mise en cache pendant 60 secondes, par votre client uniquement.

### Paramètres

Aucun.

### Réponse

```json
{
  "data": [{
    "id": "x-ai/grok-4.7",
    "name": "Grok 4.7",
    "kind": "chat",
    "context_length": 500000,
    "architecture": { "input_modalities": ["text", "image"], "output_modalities": ["text"] },
    "pricing": {
      "prompt": "0.000002", "completion": "0.000006", "input_cache_read": "0.0000005",
      "request": "0", "image": "0", "video_second": "0"
    },
    "top_provider": { "context_length": 500000, "max_completion_tokens": 32768 },
    "response_modes": ["sync", "stream"],
    "supported_parameters": ["max_tokens", "temperature", "top_p", "stop", "stream", "tools", "tool_choice", "response_format", "seed", "frequency_penalty", "presence_penalty"],
    "extra_parameters": {}
  }]
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `id` | string | L’id du modèle, `<provider>/<name>`, par exemple `x-ai/grok-4.7` ou `qwen/qwen-image-2.0`. Envoyez-le comme `model`. Un modèle renommé continue de répondre à son ancien id. |
| `response_modes` | string[] | La manière dont le modèle répond : `sync` (une réponse retenue), `async` (envoyez `Prefer: respond-async`, recevez `202`, puis interrogez) et `stream` (`"stream": true`). Un modèle qui ne liste que `async` exige l’en-tête `Prefer`, et `stream` ne fonctionne que là où il est listé. La vidéo est toujours asynchrone via la Videos API, avec ou sans l’en-tête. |
| `name` | string | Nom d’affichage. |
| `kind` | string | `chat`, `image` ou `video`. |
| `context_length` | integer | Fenêtre de contexte en tokens. `0` pour les modèles d’image et de vidéo. |
| `architecture` | object | `input_modalities` et `output_modalities`. La sortie est `["text"]`, `["image"]` ou `["video"]`. |
| `pricing` | object of strings | USD par unité, en chaînes décimales, au palier de base : `prompt` et `completion` (par token), `input_cache_read` (par token en cache), `request`, `image` (par image), `video_second` (par seconde de vidéo). Une vidéo tarifée selon la résolution affiche le prix le plus bas. Une unité qui ne s’applique pas vaut `"0"`. |
| `top_provider` | object | `context_length` et `max_completion_tokens`. |
| `supported_parameters` | string[] | Pour le chat, les paramètres OpenAI que le modèle accepte. Pour l’image et la vidéo, les fonctionnalités communes qu’il accepte (`resolution`, `ratio`, `audio`, `seed`, `negative_prompt`, `first_frame`, `last_frame`, `reference_images`, `reference_videos`, `prompt_extend`). |
| `operations` | string[] | Modèles vidéo uniquement : `edit`, `extend` et `remix`, pour chacune des opérations que le modèle prend en charge. Absent lorsque le modèle n’en prend aucune. |
| `extra_parameters` | object | Les champs de premier niveau propres au modèle, par nom. Chacun a `type`, `values` (valeurs autorisées ; vide signifie n’importe laquelle), `price_condition` (le champ modifie le prix) et `description`. Voir [Paramètres propres à chaque modèle](#parameters). |

```bash
curl https://api.hyperouter.app/v1/models \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY"
```

### Erreurs

Aucune propre à cet endpoint.

### Modèles de chat

Prix par million de tokens : entrée / entrée en cache / sortie.

<!-- BEGIN GENERATED models-chat: sitemd writes the rows from models.json; edit the header row only. Run cd tools && go run ./sitemd -->
| Modèle | Contexte | Prix |
| --- | --- | --- |
| `x-ai/grok-4.7` | 500K | $2.00 / $0.50 / $6.00 |
| `x-ai/grok-4.6` | 500K | $2.00 / $0.50 / $6.00 |
| `x-ai/grok-4.5` | 500K | $2.00 / $0.30 / $6.00 |
| `x-ai/grok-4.3` | 1M | $1.25 / $0.20 / $2.50 |
| `x-ai/grok-build-0.1` | 256K | $1.00 / $0.20 / $2.00 |
| `deepseek/deepseek-v4.1-flash` | 1M | $0.15 / $0.003 / $0.60 |
| `deepseek/deepseek-v4.1-flash-1017` | 1M | $0.15 / $0.003 / $0.60 |
| `openai/gpt-6-astra` | 272K | $10.00 / $1.00 / $50.00 |
| `openai/gpt-6.1-sol` | 272K | $2.00 / $0.10 / $10.00 |
| `z-ai/glm-5.3` | 1M | $1.40 / $0.26 / $4.40 |
| `lumina/face-detect` | 4K | $0.0013 par requête |
<!-- END GENERATED models-chat -->

Sur les modèles `x-ai/`, une requête de 200 000 tokens de prompt ou plus est facturée intégralement aux tarifs de long contexte, qui sont le double de ceux-ci. Les modèles `deepseek/` ont un seul tarif quelle que soit la longueur du prompt, raisonnent avant de répondre par défaut (`reasoning_effort: "none"` le désactive), et comptent leur raisonnement dans `max_tokens`, au plus 384 000. `z-ai/glm-5.3` raisonne toujours : `reasoning_effort` vaut `low`, `high` ou `max`, et `none` est refusé. Les modèles `openai/` raisonnent aussi et acceptent des prompts de 272 000 tokens au plus.

## Chat completions {#chat-completions}

`POST /v1/chat/completions`

Crée une réponse de modèle pour une conversation. Le corps est la requête Chat Completions d’OpenAI. Hyperouter ajoute `models`, `provider` et `moderation`.

### Paramètres

| Nom | Type | Requis | Valeurs autorisées | Défaut | Description |
| --- | --- | --- | --- | --- | --- |
| `model` | string | Oui | Un id de modèle de chat issu de `GET /v1/models` |  | Le modèle à utiliser, par exemple `x-ai/grok-4.7`. |
| `messages` | object[] | Oui | Non vide. Rôles `system`, `user`, `assistant`, `tool` |  | La conversation jusqu’ici. Les images (parties `image_url`) sont acceptées lorsque le modèle prend en charge les images. |
| `models` | string[] | Non | Au plus 5 ids de modèle |  | Ajout. Modèles de repli, essayés dans l’ordre lorsque tous les fournisseurs de `model` ont échoué. |
| `provider` | object | Non | Voir ci-dessous |  | Ajout. Préférences de routage. |
| `moderation` | string | Non | `auto`, `low` | `auto` | Ajout. Voir [Modération](#moderation). |
| `stream` | boolean | Non |  | `false` | Renvoyer des server-sent events. |
| `stream_options` | object | Non | `{"include_usage": true}` |  | Avec `include_usage`, le flux se termine par un chunk qui porte `usage`. |
| `max_tokens`, `max_completion_tokens` | integer | Non | 1 ou plus | Le maximum de sortie du modèle | Borne supérieure des tokens générés. Elle fixe aussi le coût maximal d’une requête, qu’Hyperouter bloque avant l’appel (voir [Facturation](#billing)). |
| `n` | integer | Non | 1 à 128 | `1` | Nombre de choix. |
| `temperature`, `top_p`, `seed`, `frequency_penalty`, `presence_penalty`, `logit_bias` | number / integer / object | Non | Comme OpenAI |  | Contrôles d’échantillonnage, transmis au fournisseur. |
| `stop` | string \| string[] | Non |  |  | Séquences qui mettent fin à la génération. |
| `tools`, `tool_choice`, `parallel_tool_calls` | object[], string \| object, boolean | Non | Format d’appel de fonctions d’OpenAI |  | Appel de fonctions. Uniquement des outils de type `function` : tout autre type est refusé avec `400 unsupported_parameter`. |
| `response_format` | object | Non | `{"type":"json_object"}` ou un schéma JSON |  | Sortie structurée. |
| `reasoning_effort`, `logprobs`, `top_logprobs`, `user` | comme OpenAI | Non |  |  | Transmis au fournisseur lorsque le modèle les accepte. |

Les autres champs sont ignorés. `supported_parameters` de chaque modèle dans [GET /v1/models](#models) liste exactement ce qu’il accepte. Grok 4.20 et les versions plus récentes ignorent `logprobs` et `top_logprobs`, qui ne sont donc pas listés.

### L’objet provider {#routing}

Par défaut, Hyperouter envoie chaque requête de chat au fournisseur en bonne santé dont le prix est le plus bas pour ce modèle, et départage les égalités par le plus court temps récent jusqu’au premier token. Un fournisseur qui renvoie des erreurs à répétition est ignoré jusqu’à son rétablissement. L’objet `provider` suit le format de requête d’OpenRouter.

| Nom | Type | Requis | Valeurs autorisées | Défaut | Description |
| --- | --- | --- | --- | --- | --- |
| `sort` | string | Non | `price`, `latency`, `throughput` | `price` | La manière de classer les fournisseurs. |
| `order` | string[] | Non | Noms de fournisseurs |  | Fournisseurs à essayer en premier, dans l’ordre, par exemple `["xai"]`. Un nom de fournisseur est son nom public, quelle que soit la casse (`xAI`, `Qwen`), ou la première partie de ses ids de modèle (`x-ai`, `qwen`). |
| `only` | string[] | Non | Noms de fournisseurs |  | Router uniquement vers ces fournisseurs. |
| `ignore` | string[] | Non | Noms de fournisseurs |  | Ne jamais router vers ces fournisseurs. |

### Réponse

La chat completion d’OpenAI. L’`id` est l’id de la requête : retrouvez-la avec [GET /v1/generations/{id}](#generations). Hyperouter ajoute `provider` (le nom public du fournisseur qui a exécuté la requête, par exemple `xAI`) et `usage.cost` (le coût, en USD, sous forme de nombre JSON).

```json
{
  "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21",
  "object": "chat.completion",
  "model": "x-ai/grok-4.7",
  "provider": "xAI",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "Hello! How can I help?" },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 8,
    "total_tokens": 17,
    "prompt_tokens_details": { "cached_tokens": 0 },
    "cost": 0.000066
  }
}
```

Un modèle de réécriture de prompt répond avec un seul choix, un `usage` de zéro token et un `cost` fixe par requête.

### Streaming {#streaming}

Avec `"stream": true`, la réponse est un flux de server-sent events au format de chunk d’OpenAI. Il se termine par `data: [DONE]`. Lorsque vous définissez `stream_options.include_usage`, le dernier chunk avant `[DONE]` a un tableau `choices` vide, un objet `usage` qui inclut `cost`, et un champ `provider`. La consommation est toujours collectée pour la facturation, que vous demandiez ou non ce chunk.

Tant que le premier token n’est pas arrivé, le flux envoie la ligne de commentaire `: processing` toutes les 15 secondes pour garder la connexion ouverte. Les clients SSE ignorent les commentaires.

Le repli vers un autre fournisseur n’a lieu qu’avant l’envoi du premier token. Si un fournisseur échoue en cours de flux, le flux se termine par `data: {"error": {...}}` suivi de `data: [DONE]`, et vous êtes facturé pour les tokens déjà produits.

Vous êtes facturé pour les tokens que le fournisseur produit, que vous les lisiez ou non. Un flux que vous interrompez, ou une réponse que vous cessez d’attendre, n’est pas arrêté chez le fournisseur, qui continue et facture sa réponse entière : Hyperouter la lit jusqu’au bout (pendant 5 minutes au plus) et la facture.

Un fournisseur qui a accepté une requête peut être en train de l’exécuter. S’il cesse ensuite de répondre (5 minutes sans token sur un flux, ou plus longtemps que ne prend une réponse de `max_tokens`) ou si sa réponse s’interrompt avant le premier token, la requête n’est pas envoyée à un autre fournisseur : elle répond `503` avec le code `timeout` ou `upstream_error` et `error.generation_id`, et est facturée sur son montant bloqué, avec `usage_estimated: true` sur son enregistrement.

### Exemple

```bash
curl https://api.hyperouter.app/v1/chat/completions \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "x-ai/grok-4.7",
    "models": ["x-ai/grok-4.6"],
    "provider": { "sort": "latency" },
    "messages": [{ "role": "user", "content": "Hello" }]
  }'
```

### Erreurs

| Statut | type | code | param | Quand |
| --- | --- | --- | --- | --- |
| `400` | `invalid_request_error` | `invalid_body` | le champ | Le corps n’est pas du JSON valide ou dépasse 32 MiB ; `model` est absent ; `models` a plus de 5 entrées ; `n` est hors de 1 à 128 ; `max_tokens` n’est pas positif ; `provider.sort` n’est pas autorisé ; `moderation` n’est ni `auto` ni `low` (`moderation must be auto or low.`) ; `messages` est vide. Aussi lorsque tous les fournisseurs ont refusé la requête comme invalide. |
| `400` | `invalid_request_error` | `unknown_model` | `model` ou `models` | Le modèle n’est pas un modèle de chat, ou n’a aucun fournisseur activé. |
| `400` | `invalid_request_error` | `unsupported_parameter` | `tools`, `response_format`, `messages` ou `max_tokens` | Le modèle n’accepte pas les outils, un format de réponse ou les images ; un outil n’est pas de type `function` ; ou aucun fournisseur du modèle ne peut borner sa sortie. |
| `400` | `invalid_request_error` | `context_length_exceeded` | `messages` | Le prompt et `max_tokens` dépassent le contexte du modèle. |
| `402` | `insufficient_credits` | `insufficient_credits` | `max_tokens` | Le solde ou la limite de la clé ne peut pas couvrir le coût maximal de la requête. Le message indique combien de tokens vous pouvez vous permettre. |
| `429` | `rate_limit_error` | `venue_busy` |  | Tous les fournisseurs du modèle ont dépassé leur budget de débit. |
| `429` | `rate_limit_error` | `rate_limited` |  | Votre compte dépasse ses requêtes par minute ou ses requêtes simultanées (voir [Limites de débit](#rate-limits)). Rien n’est bloqué ni transmis. |
| `409` | `conflict` | `idempotency_key_replayed`, `idempotency_key_reused`, `idempotency_key_in_use` |  | Voir [Nouvelles tentatives et idempotence](#idempotency). |
| `503` | `upstream_error` | `all_venues_failed` |  | Aucun fournisseur n’est en bonne santé, ou tous ont échoué. Non facturé. |
| `503` | `upstream_error` | `upstream_error` |  | Le fournisseur a accepté la requête et sa réponse s’est interrompue avant le premier token. Facturé sur le montant bloqué (voir [Streaming](#streaming)). |
| `503` | `upstream_error` | `timeout` |  | Le fournisseur a accepté la requête et ne l’a pas terminée à temps. Facturé sur le montant bloqué (voir [Streaming](#streaming)). |
| `401`, `403`, `500` |  |  |  | Voir [Authentification](#authentication). |

## Images {#images}

`POST /v1/images/generations`

`POST /v1/images/edits`

Les deux suivent l’Images API d’OpenAI et prennent un corps JSON. `/v1/images/edits` prend la forme JSON d’OpenAI : les images sont `images`, de 1 à 5 URL https. Le `images.edit` des OpenAI SDK envoie les fichiers d’image en `multipart/form-data`, et cela fonctionne aussi : l’edge d’Hyperouter conserve chaque fichier (PNG, JPEG ou WebP, 20 MiB chacun, 50 MiB par requête) pendant 3 jours à une URL `https://api.hyperouter.app/u/...` impossible à deviner, que le fournisseur récupère, puis exécute la requête comme du JSON. Un fichier vidéo est refusé (`400 invalid_video`) : envoyez une vidéo sous forme d’URL https. Les deux acceptent les fonctionnalités communes ci-dessous et tous les [paramètres propres au modèle](#parameters) comme champs de premier niveau.

### Paramètres

| Nom | Type | Requis | Valeurs autorisées | Défaut | Description |
| --- | --- | --- | --- | --- | --- |
| `model` | string | Oui | Un id de modèle d’image |  | Le modèle à utiliser. |
| `prompt` | string | Generations : oui. Edits : selon le modèle | Texte non vide |  | Ce qu’il faut produire. |
| `images` | object[] | Edits : oui | 1 à 5 objets `{"image_url": "https://..."}`, autant que le modèle en prend. Chaque URL est en https vers un hôte public, jamais une URL `data:`, une adresse IP ni un nom interne. `file_id` n’est pas accepté. |  | Les images à modifier. Chaque URL est transmise telle quelle au fournisseur, qui doit donc pouvoir la récupérer ; une URL qu’il ne peut pas récupérer ou lire fait échouer la tâche avec `vendor_rejected`, sans aucun débit. Un modèle qui les prend dans un ordre précis l’indique dans ses notes de la référence des modèles. |
| `n` | integer | Non | 1 à 10, et au plus la limite du modèle | `1` | Nombre d’images. |
| `size` | string | Non | `WIDTHxHEIGHT`, par exemple `1024x1024` ; le modèle peut autoriser moins de tailles | `1024x1024` | Taille de sortie. |
| `quality` | string | Non | Selon le modèle |  | Transmis au modèle. |
| `response_format` | string | Non | `url` | `url` | La manière dont l’image est renvoyée : toujours une URL. `b64_json` est refusé avec `400 unsupported_parameter`. |
| `seed` | integer | Non | Si le modèle liste `seed` |  | Seed pour une sortie reproductible. |
| `negative_prompt` | string | Non | Si le modèle le liste |  | Ce qu’il faut éviter. |
| `prompt_extend` | boolean | Non | Si le modèle le liste | Désactivé sauf demande | Laisser le modèle enrichir le prompt. |
| `prompt_expand` | boolean | Non | Si le modèle le liste | `false` | Ajout. Hyperouter réécrit le prompt pour le modèle avant le rendu. Voir [Expansion du prompt](#prompt-expansion). |
| `prompt_expand_model` | string | Non | Le slug d’un modèle de chat que votre compte peut appeler | `x-ai/grok-4.6` | Ajout. Avec `prompt_expand` : le modèle qui écrit la réécriture. Sans `prompt_expand: true`, c’est `400 invalid_body`. Voir [Expansion du prompt](#prompt-expansion). |
| `locale` | string | Non | Une balise BCP 47, par exemple `zh-CN` | Anglais | Ajout. Avec `prompt_expand` : la langue des répliques, si le modèle la parle. |
| `ratio` | string | Non | Si le modèle le liste |  | Format d’image. |
| `resolution` | string | Non | `480p`, `720p`, `1080p`, `2K`, `4K`, selon le modèle |  | Résolution de sortie. |
| `models` | string[] | Non | Ids de modèles d’image |  | Ajout. Modèles de repli. |
| `provider` | object | Non | `only`, `ignore`, `order` (tableaux de noms de fournisseurs) |  | Ajout. Préférences de routage. |
| `moderation` | string | Non | `auto`, `low` | `auto` | Ajout. Voir [Modération](#moderation). |
| champs propres au modèle | comme déclaré | Comme déclaré | Comme déclaré |  | Voir [Paramètres propres à chaque modèle](#parameters). |

Une fonctionnalité commune que le modèle ne liste pas, comme `seed` sur un modèle qui ne l’a pas, est refusée avec `400 unsupported_parameter`. Elle n’est jamais ignorée en silence, car cela changerait la sortie et le prix.

### Réponse

```json
{
  "created": 1730000000,
  "data": [{ "url": "https://..." }],
  "usage": { "cost": 0.02 },
  "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21"
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `created` | integer | Heure Unix de la requête. |
| `data[].url` | string | Avec `response_format: "url"` : un lien signé, valable 24 heures. `GET /v1/generations/{id}` en signe un nouveau. |
| `usage.cost` | number | Le coût en USD, sous forme de nombre JSON. |
| `id` | string | L’id de la requête. Utilisez-le avec [GET /v1/generations/{id}](#generations). |
| `expanded_prompt` | string \| null | Ajout. Avec `prompt_expand` : le prompt qui a servi au rendu. `null` si rien n’a été réécrit. |

Hyperouter stocke chaque résultat pendant 30 jours, de sorte qu’un lien ne pointe jamais vers un éditeur. Au-delà de 30 jours, l’enregistrement subsiste mais son `outputs` est vide.

### Attendre le résultat

- Sur un modèle dont les `response_modes` incluent `sync`, la requête attend l’image jusqu’à 90 secondes et répond avec son véritable statut : `200` avec les images, ou l’erreur. Les erreurs de validation, de solde et de capacité passent en premier, avec leur propre statut.
- Une image qui n’est pas prête au bout de 90 secondes répond `503` avec le code `timeout` et l’en-tête de réponse `x-should-retry: false`, de sorte que les OpenAI SDK ne la réessaient pas et ne démarrent pas une seconde image. La tâche continue, et l’erreur porte son id dans `generation_id` : interrogez [GET /v1/generations/{id}](#generations) pour obtenir le résultat. Vous n’êtes facturé que si elle réussit. Pour réessayer vous-même en toute sécurité, envoyez une [Idempotency-Key](#idempotency) : une nouvelle tentative avec la même clé attend la même tâche.
- Un modèle dont les `response_modes` ne contiennent que `async` exige `Prefer: respond-async`. Sans cet en-tête, la réponse est `400` avec le code `async_required`.
- Pour éviter une connexion maintenue sur tout modèle qui liste `async`, envoyez `Prefer: respond-async` (RFC 7240). L’en-tête peut être répété et porter d’autres préférences, par exemple `Prefer: wait=10, respond-async`. Vous recevez aussitôt `202` avec l’en-tête de réponse `Preference-Applied: respond-async` :

  ```json
  { "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21", "status": "queued" }
  ```

  Interrogez [GET /v1/generations/{id}](#generations), ou recevez un [webhook](#webhooks) `image.completed` ou `image.failed`. Les webhooks ne se déclenchent pour les images que pour les tâches envoyées avec cet en-tête.

### Exemples

```bash
curl https://api.hyperouter.app/v1/images/generations \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "x-ai/grok-imagine-image", "prompt": "A lighthouse at dusk", "n": 1}'
```

```bash
curl https://api.hyperouter.app/v1/images/edits \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "x-ai/grok-imagine-image", "prompt": "make it night",
       "images": [{"image_url": "https://example.com/photo.png"}]}'
```

### Erreurs

| Statut | code | param | Quand |
| --- | --- | --- | --- |
| `400` | `unsupported_content_type` |  | Le corps est du `multipart/form-data` : `Send a JSON body; pictures and videos are https URLs.` |
| `400` | `invalid_body` | le champ | Le corps n’est pas du JSON valide ; `images` n’est pas un tableau d’objets avec `image_url` ; `prompt` est absent sur generations ; `n` est inférieur à 1 ; `response_format` n’est pas `url` (`response_format must be url.` ; `b64_json` est `unsupported_parameter`) ; `resolution` n’est pas autorisée ; un champ typé a le mauvais type ; `moderation` n’est ni `auto` ni `low`. |
| `400` | `unknown_model` | `model` | Le modèle n’existe pas pour les images, ou aucun fournisseur ne correspond à vos préférences `provider`. |
| `400` | `invalid_size` | `size` | `size` ne ressemble pas à `1024x1024`, ou le modèle ne l’autorise pas. |
| `400` | `too_many_images` | `n` ou `images` | `n` dépasse 10 ou la limite du modèle ; plus de 5 images ; plus d’images que le modèle n’en accepte. |
| `400` | `image_required` | `images` | Un edit a moins d’images que le modèle n’en exige, ou vous avez appelé generations sur un modèle qui ne fait que modifier (utilisez `/v1/images/edits`). |
| `400` | `unsupported_operation` | `images` | Le modèle ne modifie pas les images. |
| `400` | `unsupported_parameter` | le champ | Le modèle ne prend pas en charge ce champ ; `images[i].file_id`. |
| `400` | `invalid_combination` | le champ | L’éditeur n’autorise pas ces champs ensemble. |
| `400` | `async_required` |  | Le modèle ne répond que de façon asynchrone : envoyez `Prefer: respond-async`. |
| `400` | `unsupported_parameter` | `stream` | `"stream": true` sur un modèle dont les `response_modes` n’incluent pas `stream`. |
| `400` | `invalid_image` | `images[i].image_url` | L’URL n’est pas en https vers un hôte public : `images[i].image_url must be an https URL.` pour une URL `data:` ou `http://`. |
| `400` | `content_rejected` |  | Le filtre de sécurité de l’éditeur a refusé l’entrée : `The request was rejected by a content safety filter.` |
| `400` | `vendor_rejected` |  | `The request was refused by the model. Check its parameters and inputs.` |
| `400` | `invalid_idempotency_key` |  | Voir [Nouvelles tentatives et idempotence](#idempotency). |
| `402` | `insufficient_credits` |  | Le solde ou la limite de la clé ne peut pas couvrir le coût maximal de la requête. |
| `409` | `idempotency_key_reused`, `idempotency_key_in_use` |  | Voir [Nouvelles tentatives et idempotence](#idempotency). |
| `429` | `rate_limit_error` |  | Tous les fournisseurs sont saturés (pas de `code`), ou votre compte dépasse ses requêtes par minute ou ses requêtes simultanées (`code: rate_limited`, voir [Limites de débit](#rate-limits)). |
| `503` | `upstream_error` |  | `The model failed to produce a result. You were not charged.` |
| `503` | `provider_busy` |  | Le fournisseur est resté à pleine capacité pendant environ deux minutes et n’a pas accepté la tâche. Réessayez plus tard. Rien ne vous a été facturé. |
| `500` | `server_error`, `no_price` |  | Hyperouter a échoué, ou le modèle ne peut pas être tarifé pour le moment. Un `500` survenu après la création de l’image porte `x-should-retry: false`. |
| `503` | `timeout` |  | L’image n’était pas prête au bout de 90 secondes. Elle est toujours en cours de création : `error.generation_id` est l’id à interroger. Porte `x-should-retry: false`. |

Le message d’une tâche échouée se termine par la raison donnée par le fournisseur, s’il en a donné une, après `Provider:`, afin de distinguer un prompt refusé, un fournisseur saturé et une panne. Les URL qu’elle contient sont remplacées par `<url>`.

### Modèles d’image

<!-- BEGIN GENERATED models-image: sitemd writes the ids and prices from models.json; write each model's Does cell. Run cd tools && go run ./sitemd -->
| Modèle | Fonction | Prix |
| --- | --- | --- |
| `x-ai/grok-imagine-image` | Générer et modifier. Jusqu’à 10 images, jusqu’à 5 images en entrée. | $0.02 par image |
| `x-ai/grok-imagine-image-2.0` | Générer et modifier, comme ci-dessus. | À partir de $0.04 par image |
| `qwen/qwen-image-2.0` | Générer et modifier (1 à 3 images en entrée). `n` de 1 à 6. | $0.035 par image |
| `bytedance/seedream-5.0-flash` | Générer et modifier (1 à 10 images en entrée). Une image 2K par requête, sans filigrane. | $0.018 par image |
| `bytedance/seedream-5.0-lite` | Générer et modifier, comme ci-dessus. | $0.035 par image |
<!-- END GENERATED models-image -->

Certains modèles exigent un accès accordé à votre compte. La [référence des modèles](https://dash.hyperouter.app/docs/models) du tableau de bord liste tous les modèles que votre compte peut appeler, avec leurs paramètres, leurs prix et leurs notes.

## Vidéos {#videos}

La vidéo suit la Videos API d’OpenAI. Une vidéo est une tâche. Un appel de création, de modification, de prolongation ou de remix répond aussitôt `200` avec l’objet Video au statut `queued`. Vous interrogez `GET /v1/videos/{id}` jusqu’à ce que le statut soit `completed`, ou vous recevez un [webhook](#webhooks). `Prefer` ne change rien ici : les vidéos sont toujours asynchrones.

| Endpoint | Fonction |
| --- | --- |
| `POST /v1/videos` | Créer une vidéo à partir d’un prompt, avec un `input_reference` facultatif. |
| `POST /v1/videos/edits` | Modifier ou agrandir une vidéo terminée. Uniquement les modèles qui listent `edit` dans `operations`. |
| `POST /v1/videos/extensions` | Prolonger une vidéo terminée. Uniquement les modèles qui listent `extend`. |
| `POST /v1/videos/{id}/remix` | Nouveau prompt sur une vidéo terminée, même modèle. Uniquement les modèles qui listent `remix`. |
| `GET /v1/videos` | Lister vos vidéos. |
| `GET /v1/videos/{id}` | L’objet Video. |
| `GET /v1/videos/{id}/content` | Le MP4 ou la miniature. |
| `DELETE /v1/videos/{id}` | Supprimer une vidéo. |

### Créer une vidéo

`POST /v1/videos`

Le corps est du JSON, sur tous les endpoints vidéo ; le `videos.create` des OpenAI SDK avec un fichier d’image `input_reference` est aussi accepté, le fichier étant traité comme pour les [modifications d’images](#images). Chaque image et chaque vidéo est une URL https vers un hôte public, transmise telle quelle au fournisseur : il doit pouvoir la récupérer, et une URL qu’il ne peut pas récupérer ou lire fait échouer la vidéo avec `vendor_rejected`, sans aucun débit. Une URL `data:`, une URL `http://`, une adresse IP ou un nom interne est refusé avec `400 invalid_image` ou `400 invalid_video`. Un corps `multipart/form-data` est refusé avec `400 unsupported_content_type`.

| Nom | Type | Requis | Valeurs autorisées | Défaut | Description |
| --- | --- | --- | --- | --- | --- |
| `model` | string | Oui | Un id de modèle vidéo |  | Le modèle à utiliser. |
| `prompt` | string | Oui | Texte non vide |  | Ce qu’il faut produire. |
| `seconds` | string \| integer | Non | Selon le modèle | La première valeur autorisée du modèle, sinon 4 s (ou le maximum du modèle s’il est inférieur) | Durée de la vidéo. `"8"` et `8` sont équivalents. |
| `size` | string | Non | `WIDTHxHEIGHT` |  | La hauteur détermine la résolution, sauf si vous envoyez `resolution` : jusqu’à 480 donne `480p`, jusqu’à 720 donne `720p`, jusqu’à 1080 donne `1080p`, jusqu’à 1440 donne `2K`, au-delà `4K`. Le modèle doit autoriser cette résolution. |
| `input_reference` | object | Si le modèle exige une image | `{"image_url": "https://..."}`, une URL https |  | La première image de la vidéo. `file_id` n’est pas accepté. |
| `resolution` | string | Non | `480p`, `720p`, `1080p`, `2K`, `4K`, selon le modèle | La première résolution du modèle | Résolution de sortie. Elle modifie le prix. |
| `ratio` | string | Non | Si le modèle le liste |  | Format d’image. |
| `audio` | boolean | Non | Si le modèle produit du son | `false` | Produire du son. |
| `seed` | integer | Non | Si le modèle le liste |  | Seed pour une sortie reproductible. |
| `negative_prompt` | string | Non | Si le modèle le liste |  | Ce qu’il faut éviter. |
| `prompt_extend` | boolean | Non | Si le modèle le liste | Désactivé sauf demande | Laisser le modèle enrichir le prompt. |
| `prompt_expand`, `locale` | boolean, string | Non | Comme pour les [images](#images) | `false` | Ajout. Voir [Expansion du prompt](#prompt-expansion). |
| `prompt_expand_model` | string | Non | Comme pour les [images](#images) | `x-ai/grok-4.6` | Ajout. Avec `prompt_expand` : le modèle qui écrit la réécriture. Voir [Expansion du prompt](#prompt-expansion). |
| `first_frame`, `last_frame` | string | Non | Une URL https d’une image |  | Images clés. |
| `reference_images`, `reference_videos` | string[] | Non | URL https |  | Médias de référence. Ils ne peuvent pas être combinés avec des images clés, selon la règle de l’éditeur. |
| `models`, `provider` | string[], object | Non | Comme pour les [images](#images) |  | Ajout. Modèles de repli et préférences de routage. |
| `moderation` | string | Non | `auto`, `low` | `auto` | Ajout. Voir [Modération](#moderation). |
| champs propres au modèle | comme déclaré | Comme déclaré | Comme déclaré |  | Voir [Paramètres propres à chaque modèle](#parameters). |

### Modifier, prolonger et remixer

| Endpoint | Corps (JSON) |
| --- | --- |
| `POST /v1/videos/edits` | `model`, `video` (requis), `prompt`, `resolution` et les fonctionnalités communes. `video` est `{"id": "<your video id>"}` (une vidéo terminée qui vous appartient) ou `{"url": "https://..."}`. `model` prend par défaut le modèle de la vidéo source lorsque vous envoyez un id. |
| `POST /v1/videos/extensions` | `model`, `video` (requis), `prompt`, `seconds`. Les nouvelles secondes suivent les mêmes règles qu’une création. |
| `POST /v1/videos/{id}/remix` | `prompt` (requis). Le modèle est celui de la vidéo source. |

La vidéo source doit être terminée. Une vidéo donnée par URL est une URL https, transmise telle quelle au fournisseur, comme une image. Si vous donnez une vidéo par URL, `seconds` est requis sur les modèles qui n’ont pas de maximum fixe.

### Lister, lire, télécharger, supprimer

| Endpoint | Paramètres | Réponse |
| --- | --- | --- |
| `GET /v1/videos` | Query : `after` (un id de vidéo), `limit` (1 à 100, 20 par défaut), `order` (`desc` par défaut, ou `asc`) | `{"object":"list","data":[Video],"first_id","last_id","has_more"}`. Les vidéos ayant dépassé leur `expires_at` sont exclues. |
| `GET /v1/videos/{id}` |  | L’objet Video. |
| `GET /v1/videos/{id}/content` | Query : `variant` (`video` par défaut, ou `thumbnail`) | `302 Found` vers un lien signé donnant les octets (`video/mp4`, ou le type de la miniature), valable 15 minutes. Les clients HTTP et les OpenAI SDK le suivent. `404 not_found` avant la fin ou lorsqu’il n’y a pas de miniature. |
| `DELETE /v1/videos/{id}` |  | `{"id","object":"video.deleted","deleted":true}`. Répond `409 video_not_finished` tant que la tâche s’exécute. Une vidéo supprimée répond `404`. |

### L’objet Video

```json
{
  "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21",
  "object": "video",
  "model": "x-ai/grok-imagine-video",
  "status": "completed",
  "progress": 100,
  "prompt": null,
  "seconds": "8",
  "size": "",
  "created_at": 1730000000,
  "completed_at": 1730000137,
  "expires_at": 1732592000,
  "error": null,
  "remixed_from_video_id": null,
  "usage": { "cost": 0.4, "seconds": 8 }
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `status` | string | `queued`, `in_progress`, `completed` ou `failed`. |
| `progress` | integer | 0 à 100. |
| `prompt` | string \| null | Le prompt n’est renvoyé que pendant l’exécution de la tâche. L’objet terminé renvoie `null`. |
| `seconds` | string | Durée de la vidéo, sous forme de chaîne, comme chez OpenAI. |
| `size` | string | Le `size` que vous avez envoyé, le cas échéant. |
| `expires_at` | integer | Heure Unix : `created_at` plus 30 jours. Passé ce délai, `GET /v1/videos/{id}` et `/content` répondent `404 not_found` et la vidéo est exclue de la liste. Téléchargez ce que vous voulez conserver avant. |
| `error` | object \| null | Pour une tâche échouée : `{"code", "message"}`, par exemple `content_rejected`. |
| `remixed_from_video_id` | string \| null | La vidéo source d’une modification, d’une prolongation ou d’un remix. |
| `usage` | object \| null | Renseigné une fois la tâche réglée : `cost` (USD, un nombre JSON) et `seconds` (entier). |
| `expanded_prompt` | string \| null | Ajout. Avec `prompt_expand` : le prompt qui a servi au rendu, une fois la tâche soumise. Conservé 30 jours, puis `null`. |

Une vidéo est facturée sur les secondes que vous demandez. Le montant est bloqué à l’avance, puis libéré intégralement si la tâche échoue.

### Exemple : x-ai/grok-imagine-video

Le modèle `x-ai/grok-imagine-video` produit une vidéo à partir d’un prompt, avec une image de départ facultative. `seconds` va de 1 à 15. Prix : $0.050 par seconde.

```bash
curl https://api.hyperouter.app/v1/videos \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "x-ai/grok-imagine-video",
    "prompt": "She turns to the camera and smiles",
    "input_reference": { "image_url": "https://example.com/portrait.png" },
    "seconds": 8
  }'
```

```json
{
  "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21",
  "object": "video",
  "model": "x-ai/grok-imagine-video",
  "status": "queued",
  "progress": 0,
  "prompt": "She turns to the camera and smiles",
  "seconds": "8",
  "size": "",
  "created_at": 1730000000,
  "completed_at": null,
  "expires_at": 1732592000,
  "error": null,
  "remixed_from_video_id": null,
  "usage": null
}
```

Cette tâche est facturée 8 × $0.050 = $0.40, de sorte que l’objet réglé affiche `"usage": {"cost": 0.4, "seconds": 8}`.

### Erreurs

| Statut | code | param | Quand |
| --- | --- | --- | --- |
| `400` | `unsupported_content_type` |  | Le corps est du `multipart/form-data` : `Send a JSON body; pictures and videos are https URLs.` |
| `400` | `invalid_body` | le champ | Le corps n’est pas du JSON valide ; `prompt` est vide ; `video` n’est pas un objet avec `id` ou `url` ; `input_reference` n’est pas un objet avec `image_url` ; `limit`, `after` ou `variant` n’est pas valide ; un champ typé a le mauvais type ; `resolution` n’est pas autorisée ; `moderation` n’est ni `auto` ni `low`. |
| `400` | `unknown_model` | `model` | Le modèle n’existe pas pour les vidéos. |
| `400` | `invalid_seconds` | `seconds` | `seconds` n’est pas autorisé pour le modèle (le message liste les valeurs autorisées), ou est requis pour une vidéo donnée par URL. |
| `400` | `invalid_size` | `size` | `size` ne ressemble pas à `1280x720`. |
| `400` | `unsupported_parameter` | le champ | Le modèle ne prend pas en charge ce champ ; la résolution n’est pas disponible (`resolution` ou `size`) ; `audio` sur un modèle sans son ; `input_reference.file_id`. |
| `400` | `unsupported_operation` | `model` | Le modèle ne liste pas l’opération (modifier, prolonger ou remixer). |
| `400` | `invalid_combination` | le champ | Des médias de référence avec des images clés, selon la règle de l’éditeur. |
| `400` | `image_required` | `input_reference` | `This model needs an input_reference picture.` |
| `400` | `video_not_ready` | `video` | La vidéo source n’est pas terminée. |
| `400` | `invalid_image`, `invalid_video` | le champ | L’URL d’une image (`invalid_image`) ou d’une vidéo (`invalid_video` : `reference_videos[i]`, `video.url`) n’est pas en https vers un hôte public, par exemple une URL `data:` ou `http://` : `<field> must be an https URL.` `param` est le champ, par exemple `input_reference.image_url`, `first_frame` ou `reference_images[1]`. Une URL est transmise telle quelle au fournisseur : une URL que le fournisseur ne peut pas récupérer ou lire fait échouer la vidéo avec `vendor_rejected`, sans aucun débit. |
| `400` | `clip_too_long` |  | Le clip de référence et les `seconds` demandées dépassent la limite du modèle : `The reference clip plus the requested seconds run past the model's limit of 30 s. Send a shorter clip or fewer seconds. You were not charged.` Il figure dans `error.code` de l’objet Video. |
| `400` | `content_rejected`, `vendor_rejected` |  | La tâche a échoué chez l’éditeur. Cela apparaît dans `error.code` de l’objet Video. |
| `402` | `insufficient_credits` |  | Le solde ou la limite de la clé ne peut pas couvrir le montant bloqué. |
| `404` | `not_found` |  | Cette vidéo n’existe pas, ne vous appartient pas, a été supprimée ou a dépassé `expires_at` ; ou le contenu n’est pas prêt. |
| `409` | `video_not_finished` |  | Vous avez supprimé une vidéo encore en cours de création. |
| `409` | `idempotency_key_reused`, `idempotency_key_in_use` |  | Voir [Nouvelles tentatives et idempotence](#idempotency). |
| `429`, `503`, `500` |  |  | Comme pour les images. Un `500` survenu après la création de la vidéo porte `x-should-retry: false`. |

### Modèles vidéo

<!-- BEGIN GENERATED models-video: sitemd writes the ids and prices from models.json; write each model's Notes cell. Run cd tools && go run ./sitemd -->
| Modèle | Prix par seconde de vidéo | Notes |
| --- | --- | --- |
| `x-ai/grok-imagine-video` | À partir de $0.050 | Entrée texte, image ou vidéo. 15 s au plus. Prend en charge `edit`. |
| `x-ai/grok-imagine-video-1.5` | À partir de $0.080 | Entrée texte, image ou référence. 15 s au plus. |
| `x-ai/grok-imagine-video-1.5-lite` | À partir de $0.020 | Comme ci-dessus, plus petit et moins cher. |
| `bytedance/seedance-2.0` | À partir de $0.0431892 | Entrée texte, première et dernière image, ou images de référence, audio facultatif. 4 à 15 s. 480p, 720p, 1080p ou 4K ; le tarif indiqué est celui du 480p. |
| `bytedance/seedance-2.0-fast` | À partir de $0.0331452 | Entrée texte, première et dernière image, ou images de référence, audio facultatif. 4 à 15 s. 480p ou 720p ; le tarif indiqué est celui du 480p. |
| `bytedance/seedance-2.0-mini` | À partir de $0.0210924 | Entrée texte, première et dernière image, ou images de référence, audio facultatif. 4 à 15 s. 480p ou 720p ; le tarif indiqué est celui du 480p. |
| `bytedance/seedance-2.5` | À partir de $0.0642816 | Entrée texte, première et dernière image, ou images de référence, audio facultatif. 4 à 15 s. 480p, 720p ou 1080p ; le tarif indiqué est celui du 480p. |
| `lumina/berry-1.0` | À partir de $0.050 | Entrée texte, image ou référence, audio facultatif. 30 s au plus. 480p, 720p ou 1080p ; le tarif indiqué est celui du 480p. |
| `minimax/minimax-h3` | À partir de $0.080 | Entrée texte, première et dernière image, ou images de référence, sans audio. 4 à 15 s, en 768p ou 2K. |
| `alibaba/wan3.0` | À partir de $0.050 | Entrée texte, image ou référence, audio facultatif. 30 s au plus. 480p, 720p ou 1080p ; le tarif indiqué est celui du 480p. |
<!-- END GENERATED models-video -->

La [référence des modèles](https://dash.hyperouter.app/docs/models) du tableau de bord liste tous les modèles vidéo que votre compte peut appeler.

## Generations {#generations}

`GET /v1/generations/{id}`

Renvoie l’enregistrement de l’une de vos requêtes, de n’importe quel type : son statut, sa consommation, son coût, ses durées et, pour les images et les vidéos, des liens signés vers les sorties. Utilisez-le pour interroger une requête d’image envoyée avec `Prefer: respond-async` et pour rapprocher vos dépenses. Le texte des prompts et des completions ne figure jamais dans l’enregistrement. Hyperouter les conserve chiffrés pendant 180 jours uniquement pour respecter ses obligations légales, et aucune API ni page du tableau de bord ne les renvoie ([Politique de confidentialité](privacy.md#p3)).

### Paramètres

| Nom | Dans | Type | Requis | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string (UUID) | Oui | L’`id` d’une chat completion, d’une réponse d’image ou d’un objet Video. |

### Réponse

```json
{
  "data": {
    "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21",
    "kind": "chat",
    "model": "x-ai/grok-4.7",
    "provider_name": "xAI",
    "status": "done",
    "created_at": "2026-10-24T08:12:00Z",
    "finished_at": "2026-10-24T08:12:02Z",
    "usage": {
      "input_tokens": 120, "output_tokens": 300, "cache_read_tokens": 0,
      "image": 0, "video_seconds": 0, "cost": 0.00204
    },
    "usage_estimated": false,
    "ttft_ms": 320,
    "latency_ms": 2100,
    "error": null,
    "outputs": []
  }
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `kind` | string | `chat`, `image` ou `video`. |
| `provider_name` | string \| null | Le fournisseur qui a servi une requête de chat terminée. `null` pour les images et les vidéos. |
| `status` | string | `queued`, `submitting`, `running`, `done` ou `failed`. |
| `created_at`, `finished_at` | string \| null | RFC 3339 UTC. `finished_at` vaut `null` tant que la requête n’est pas terminée. |
| `usage` | object | `input_tokens`, `output_tokens`, `cache_read_tokens`, `image`, `video_seconds` et `cost`. `cost` est le coût en USD, un nombre JSON. Les compteurs valent `0` tant que la requête n’est pas terminée. |
| `usage_estimated` | boolean | `true` lorsque le fournisseur n’a envoyé aucun décompte de tokens et qu’Hyperouter les a comptés. |
| `ttft_ms`, `latency_ms` | integer \| null | Temps jusqu’au premier token et temps total, en millisecondes. |
| `error` | object \| null | `{"code", "message"}` lorsque la requête a échoué. |
| `outputs` | object[] | Pour les images et les vidéos : `[{"url", "expires_at"}]`, des liens signés valables 24 heures. Disponibles pendant 30 jours après la requête ; ensuite `outputs` est vide et l’enregistrement subsiste. Toujours vide pour le chat. |
| `expanded_prompt` | string \| null | Pour les images et les vidéos envoyées avec `prompt_expand` : le prompt qui a servi au rendu. Conservé 30 jours après la requête, puis `null`. |

```bash
curl https://api.hyperouter.app/v1/generations/0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21 \
  -H "Authorization: Bearer $HYPEROUTER_API_KEY"
```

### Erreurs

| Statut | type | code | param | Quand |
| --- | --- | --- | --- | --- |
| `404` | `not_found` | `not_found` | `id` | `No generation with that id.` L’id n’est pas un UUID, n’existe pas, appartient à un autre compte ou a été supprimé. |
| `500` | `server_error` | `server_error` |  | Hyperouter n’a pas pu lire ou signer l’enregistrement. |
| `401`, `403` |  |  | Voir [Authentification](#authentication). |

<!-- toc: Concepts -->
## Paramètres propres à chaque modèle {#parameters}

Certains modèles ont des champs que d’autres n’ont pas. Ce sont les *paramètres supplémentaires*. Vous les envoyez comme champs de premier niveau du corps de la requête, comme n’importe quel autre champ. Tous les OpenAI SDK peuvent les envoyer (`extra_body` en Python, propriétés supplémentaires en JavaScript).

`GET /v1/models` liste les paramètres supplémentaires de chaque modèle dans `extra_parameters`. Chaque entrée comporte :

| Champ | Type | Description |
| --- | --- | --- |
| `type` | string | L’un de `string`, `integer`, `number`, `boolean`, `array`, `object`. |
| `values` | array | Les valeurs autorisées. Vide signifie n’importe quelle valeur du type. |
| `price_condition` | boolean | `true` lorsque le champ modifie le prix. |
| `description` | string | Ce que fait le champ. |

La [référence des modèles](https://dash.hyperouter.app/docs/models) du tableau de bord décrit les paramètres supplémentaires de chaque modèle que votre compte peut appeler.

### Règles

- Un paramètre connu que le modèle n’a pas est refusé : `400 invalid_request_error` avec le code `unsupported_parameter`, `param` défini sur le nom du champ (par exemple `loras`) et le message `This model does not support "loras".` Un paramètre est connu lorsqu’un modèle du catalogue le déclare. La même règle s’applique aux fonctionnalités communes (`resolution`, `ratio`, `audio`, `seed`, `negative_prompt`, `first_frame`, `last_frame`, `reference_images`, `reference_videos`, `prompt_extend`). Rien n’est ignoré en silence, car cela changerait la sortie et le prix.
- Une valeur du mauvais type est refusée avec `400 invalid_body`, `param` défini sur le nom et un message tel que `loras must be of type array.`
- Une valeur hors de `values` est refusée avec `400 invalid_body` et le message `<name> must be one of [...]`.
- Un champ qu’aucun modèle ne déclare est ignoré, comme l’API d’OpenAI ignore les champs inconnus.
- Chaque modèle vérifie ses paramètres selon son propre schéma : valeurs autorisées, minimum et maximum, champs requis et longueurs de listes. Une valeur hors schéma répond `400 invalid_body` avec `param` désignant le champ, par exemple `seconds must be one of 5, 8.`
- Une combinaison qu’un éditeur n’autorise pas, comme des médias de référence avec des images clés, répond `400 invalid_combination` avec la raison. Les vérifications s’exécutent avant tout débit.
- Les champs qui modifient le prix, comme `resolution` et `audio`, sont inclus dans le montant bloqué à l’avance, qui est donc exact.

### Fonctionnalités communes

Ces champs portent le même nom sur tous les modèles qui les ont. Un modèle liste ceux qu’il accepte dans `supported_parameters`.

| Champ | Signification |
| --- | --- |
| `resolution` | `480p`, `720p`, `1080p`, `2K` ou `4K`, selon le modèle. |
| `ratio`, `seed`, `negative_prompt`, `prompt_extend`, `audio` | Comme leur nom l’indique. `audio` et `prompt_extend` sont désactivés sauf demande. |
| `first_frame`, `last_frame` | Images clés pour la vidéo. |
| `reference_images`, `reference_videos` | Médias de référence pour la vidéo et la modification d’images. |

### Expansion du prompt {#prompt-expansion}

Avec `"prompt_expand": true`, Hyperouter réécrit votre prompt pour le modèle avant le rendu : une description plus complète pour une image, un script seconde par seconde pour une vidéo. Elle est désactivée par défaut : le prompt part tel quel. Seul un modèle dont `supported_parameters` liste `prompt_expand` l’accepte, et uniquement pour créer une image ou une vidéo ; sur tout autre modèle, et pour une modification, une extension ou un remix de vidéo, elle est refusée avec `400 unsupported_parameter`. Elle ne peut pas non plus être combinée avec `prompt_extend: true` (`400 invalid_body`).

L’expansion du prompt est disponible pour un compte une fois que Hyperouter l’a activée ; ouvrez un ticket de support pour en demander l’accès. D’ici là, `GET /v1/models` ne liste `prompt_expand` pour aucun modèle, et une requête avec `prompt_expand: true` sur un modèle qui le propose répond `403 permission_error`, code `feature_not_enabled`, `param` `prompt_expand`, sans rien réserver ni envoyer au fournisseur.

- **Ce qui a servi au rendu.** La réponse image, l’objet vidéo et `GET /v1/generations/{id}` contiennent `expanded_prompt`, le prompt reçu par le modèle. Il est conservé 30 jours après la requête, puis renvoyé à `null`. `prompt` reste votre propre texte.
- **Modèle rédacteur.** `prompt_expand_model` désigne le modèle de chat qui écrit la réécriture ; par défaut, `x-ai/grok-4.6`. Il n’est valide qu’avec `prompt_expand: true` (sinon `400 invalid_body`). Un modèle inexistant ou qui n’est pas un modèle de chat donne `400 unknown_model`, et un modèle que votre compte ne peut pas appeler donne `403 model_not_enabled`, tous deux avant tout blocage. Un rédacteur qui lit les images reçoit celles de la requête ; sinon il écrit à partir du texte seul.
- **Prix.** La réécriture est facturée sur la même requête, aux prix par token du modèle qui l’a écrite (`prompt_expand_model`, sinon `x-ai/grok-4.6`) pour les tokens utilisés, et son montant maximal est bloqué d’avance avec le rendu. La référence des modèles détaille chaque modèle.
- **Quand elle ne s’exécute pas.** Si la réécriture échoue, expire ou ne peut pas être utilisée, le rendu se fait avec votre prompt tel quel, `expanded_prompt` vaut `null` et rien n’est facturé.
- **Langue.** Les répliques d’une vidéo suivent `locale` (sinon l’anglais) lorsque le modèle parle cette langue.

#### La réécriture comme modèle de chat {#prompt-expansion-model}

Un compte doté de l’expansion du prompt dispose aussi d’un modèle de chat qui renvoie la réécriture au lieu de la rendre ; votre [référence des modèles](#models) le nomme. Appelez-le sur `POST /v1/chat/completions` avec exactement un message `user` : son texte est le prompt, et ses parties `image_url` (URL https, 5 au plus) sont les images ; une URL `data:` ou `http://` renvoie `400 invalid_image` avec `param` valant `messages`. `target_model` est obligatoire : un modèle qui liste `prompt_expand`. `seconds`, `locale` et `negative_prompt` sont facultatifs. Une cible vidéo accompagnée d’une image est écrite à partir de cette première image. Il ne diffuse pas en streaming (`stream: true` renvoie `400 unsupported_parameter`), et tout autre message, message system compris, renvoie `400 invalid_body`.

Ce modèle de chat utilise toujours `x-ai/grok-4.6` ; il n’accepte pas `prompt_expand_model`. `choices[0].message.content` est une chaîne JSON, `{"prompt": "...", "negative_prompt": "..."}`, plus `"beats"` (une ligne par seconde) pour une cible vidéo. Il est facturé aux prix par token de `x-ai/grok-4.6`, indiqués dans `usage`. Un prompt qui demande les instructions du modèle renvoie `400 prompt_rejected` ; une réécriture refusée ou vide renvoie `422 expansion_refused`, et une réécriture en échec `503 upstream_error`. Aucun de ces cas n’est facturé.

## Modération {#moderation}

Le champ `moderation` est celui d’OpenAI. Il fonctionne sur `/v1/chat/completions`, `/v1/images/generations`, `/v1/images/edits` et `/v1/videos` avec ses appels de modification, de prolongation et de remix.

| Valeur | Signification |
| --- | --- |
| `auto` (par défaut) | Le filtrage propre à l’éditeur s’applique. Hyperouter n’envoie rien de plus. |
| `low` | Hyperouter envoie la désactivation du filtrage prévue par le fournisseur, sur les modèles où le fournisseur en propose une. Sur les autres modèles, `low` est ignoré sans erreur, comme le fait OpenAI. L’entrée de chaque modèle dans votre [référence des modèles](https://dash.hyperouter.app/docs/models) indique s’il en propose une. Envoyez `moderation` dans le corps : un en-tête ou un champ de filtrage propre au fournisseur n’est pas transmis sur ces endpoints. |

Toute autre valeur est refusée : `400 invalid_request_error`, code `invalid_body`, `param` `moderation`, message `moderation must be auto or low.`

`low` est disponible pour un compte une fois qu’Hyperouter l’a activé ; ouvrez un ticket de support pour le demander. D’ici là, une requête avec `low` répond `403 permission_error`, code `moderation_not_enabled`, `param` `moderation`, et n’est pas envoyée au fournisseur.

```json
{
  "model": "qwen/qwen-image-2.0",
  "prompt": "...",
  "moderation": "low"
}
```

La réponse n’indique pas si la désactivation a été utilisée. Les politiques des [Conditions d’utilisation](terms.md) continuent de s’appliquer. Une requête que le filtre de l’éditeur refuse échoue avec `400 content_rejected` et n’est pas facturée.

## Webhooks {#webhooks}

Au lieu d’interroger, un de vos endpoints peut recevoir une requête lorsqu’une tâche se termine. Ajoutez des endpoints dans le [tableau de bord](https://dash.hyperouter.app/webhooks). Le format est celui des webhooks d’OpenAI, signé selon le schéma Standard Webhooks, de sorte que les OpenAI SDK les vérifient.

| Événement | Envoyé quand |
| --- | --- |
| `video.completed`, `video.failed` | Une tâche vidéo se termine. |
| `image.completed`, `image.failed` | Une tâche d’image que vous avez envoyée avec `Prefer: respond-async` se termine. |
| `raw.completed`, `raw.failed` | La tâche d’un appel brut se termine (comptes dotés des appels bruts) : facturée, ou son blocage libéré. Un appel brut qui répond directement n’en envoie aucun. |

Le payload porte un id, jamais un résultat, un prompt ou un coût. Récupérez l’objet avec votre clé API : `data.id` est l’id que prennent [GET /v1/videos/{id}](#videos) et [GET /v1/generations/{id}](#generations). Pour une tâche brute, c’est le `X-Generation-Id` renvoyé par la création.

```json
{
  "id": "evt_0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21",
  "object": "event",
  "type": "video.completed",
  "created_at": 1761300000,
  "data": { "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21" }
}
```

### En-têtes et vérification

Chaque livraison est un `POST` avec `content-type: application/json` et ces en-têtes :

- `webhook-id` : l’id de l’événement. Il est identique à chaque nouvelle tentative : utilisez-le pour dédupliquer.
- `webhook-timestamp` : secondes Unix.
- `webhook-signature` : `v1,` suivi du HMAC-SHA256 en base64 de `id.timestamp.body`, avec le secret de l’endpoint comme clé.

Le secret de signature commence par `whsec_`. Il n’est affiché qu’une fois, à la création de l’endpoint et lors de sa rotation. Vérifiez le corps brut de la requête, avant de l’analyser :

```js
// Node, with the OpenAI SDK
const event = client.webhooks.unwrap(rawBody, req.headers, process.env.HYPEROUTER_WEBHOOK_SECRET);
if (event.type === "video.completed") {
  const video = await client.videos.retrieve(event.data.id);
}
```

`unwrap` lève une exception lorsque la signature ne se vérifie pas ; répondez à une telle requête par un statut d’erreur. L’appel suivant utilise la même URL de base que le reste de cette API.

### Endpoints, nouvelles tentatives et limites

- Un endpoint doit être en `https` sur le port 443 (ou sans port) et un nom d’hôte, pas une adresse IP. L’hôte doit se résoudre en une adresse publique ; Hyperouter vérifie à nouveau lors de la connexion. Les redirections ne sont pas suivies.
- Un compte a au plus 10 endpoints. Chaque endpoint choisit les types d’événements qu’il souhaite.
- Un `2xx` en moins de 10 secondes est un succès. Tout le reste est retenté après environ 5 secondes, 1 minute, 5 minutes, 30 minutes, 2 heures, 5 heures, 10 heures et 10 heures, puis la livraison est marquée comme échouée.
- Un endpoint dont toutes les livraisons échouent pendant 3 jours est désactivé, et le tableau de bord en indique la raison. Réactivez-le après avoir corrigé le récepteur.
- Le tableau de bord liste les livraisons de chaque endpoint avec leur statut, le nombre de tentatives, le dernier statut HTTP et la dernière erreur. Il propose Renvoyer (même id d’événement) et Envoyer un événement de test, qui livre un événement `webhook.test` dont `data.id` est l’id de l’endpoint.

## Serveur MCP {#mcp}

`POST https://api.hyperouter.app/mcp`

Un assistant IA peut travailler avec votre compte Hyperouter via le Model Context Protocol (MCP). Le serveur gère le tableau de bord : clés API, activité, crédits, tickets, webhooks et liste des modèles. Il n’exécute pas de modèles. Les appels aux modèles continuent d’aller vers `/v1` avec une clé API.

Le serveur parle Streamable HTTP et JSON-RPC 2.0. Il prend en charge les révisions MCP `2025-11-25` et `2025-06-18`. Il est sans état : il n’y a pas de sessions, et `GET /mcp` répond `405`.

### Jetons d’accès

Un client MCP s’authentifie avec un jeton d’accès du tableau de bord, pas avec une clé API.

- Créez un jeton dans le [tableau de bord](https://dash.hyperouter.app/tokens). Donnez-lui un nom, une portée et, facultativement, une expiration.
- Un jeton commence par `hr_dt_`. Il n’est affiché qu’une fois. Hyperouter n’en stocke que le hash.
- Un utilisateur du compte peut détenir au plus 25 jetons non expirés et non révoqués. Révoquez un jeton dans le tableau de bord pour l’arrêter immédiatement.
- Un jeton ne fonctionne pas sur `/v1`, et une clé API ne fonctionne pas sur `/mcp`.
- Un jeton agit comme l’utilisateur qui l’a créé, avec les permissions de cet utilisateur. Les permissions sont vérifiées à chaque message : un changement de rôle s’applique donc dès l’appel suivant.

| Portée | Outils que le jeton peut appeler |
| --- | --- |
| `read` | Les outils qui ne font que lire des données. |
| `write` | Tous les outils que l’utilisateur a la permission d’utiliser. |

Envoyez le jeton comme bearer token :

```
Authorization: Bearer hr_dt_...
```

### Protocole

| Méthode | Réponse |
| --- | --- |
| `initialize` | La version du protocole (celle du client, si elle est prise en charge ; sinon `2025-11-25`), la capacité `tools` et les informations du serveur. |
| `ping` | Un résultat vide. |
| `tools/list` | Les outils que l’utilisateur et la portée du jeton autorisent. |
| `tools/call` | Le résultat d’un outil. |

Une notification, ou une réponse adressée au serveur, répond `202` sans corps. Un lot JSON-RPC est refusé avec l’erreur `-32600`. Un message ne doit pas dépasser 1 MiB. Un en-tête `MCP-Protocol-Version` inconnu répond `400`. Une requête de navigateur doit provenir de `https://dash.hyperouter.app` ; un client qui n’envoie pas d’en-tête `Origin` n’est pas concerné.

```bash
curl https://api.hyperouter.app/mcp \
  -H "Authorization: Bearer $HYPEROUTER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
```

### Outils

`tools/list` renvoie l’ensemble actuel des outils. Chaque outil correspond à une route du tableau de bord et porte un JSON Schema pour ses arguments. Les outils couvrent :

- Compte et clés : `get_me`, `list_api_keys`, `create_api_key`, `update_api_key`.
- Activité et crédits : `list_activity`, `get_activity`, `get_activity_summary`, `get_credits`, `get_topup_fee`.
- Tickets : `list_tickets`, `create_ticket`, `get_ticket`, `reply_to_ticket`, `close_ticket`.
- Webhooks : `list_webhooks`, `create_webhook`, `update_webhook`, `rotate_webhook_secret`, `delete_webhook`, `list_webhook_deliveries`, `resend_webhook_delivery`, `send_test_webhook`.
- Modèles : `list_models`.
- Les outils du personnel, avec le préfixe `admin_`, n’apparaissent que pour les utilisateurs qui détiennent la permission du personnel.

Un outil répond avec un seul élément de contenu texte qui contient le corps JSON de la route. Lorsque la route échoue, le résultat porte `isError: true` et le texte `<message> (<code>, HTTP <status>)`. Un outil inconnu, ou que votre jeton ne peut pas voir, donne l’erreur JSON-RPC `-32602`.

### Approbations

Une écriture sensible, comme créditer un compte, n’est pas exécutée immédiatement. L’outil ne l’exécute pas. Il répond avec `status` défini sur `approval_required` et une `approval_url` sur le tableau de bord. L’utilisateur ouvre le lien, confirme avec un second facteur et approuve l’action. Une approbation expire au bout de 24 heures. L’utilisateur peut aussi la rejeter.

<!-- toc: Compte -->
## Facturation et limites {#billing}

- Les requêtes sont payées sur un solde prépayé en USD, facturées au tarif public du fournisseur qui les a servies, sans aucune majoration.
- **Bloquer, puis régler.** Avant l’exécution d’une requête, Hyperouter bloque le maximum qu’elle peut coûter : votre entrée plus `max_tokens` pour le chat, le nombre demandé pour les images et les secondes pour la vidéo. À la fin, vous êtes débité de ce qui a été utilisé et le reste du montant bloqué est libéré. Le solde ne devient jamais négatif.
- Une requête échouée, ou une tâche d’image ou de vidéo échouée, ne coûte rien. Une tentative de repli qui échoue ne coûte rien.
- Chaque réponse porte son coût dans `usage.cost`, un nombre JSON en USD : sur le chat, sur les images, sur une vidéo réglée, et dans `GET /v1/generations/{id}`. Le tableau de bord affiche chaque requête et vos dépenses par jour, par modèle et par clé.
- Les montants sont exacts : l’argent est compté en milliardièmes de dollar et jamais arrondi via un flottant. Les prix de `GET /v1/models` sont des chaînes décimales.
- Les recharges sont manuelles pour l’instant. Ouvrez une demande de recharge dans le [tableau de bord](https://dash.hyperouter.app/credits) avec le montant, la référence du paiement et une capture d’écran du paiement. Notre équipe vérifie le paiement, crédite votre solde et répond dans le même ticket.
- **Frais de recharge.** Chaque recharge supporte des frais de 5,5 % du montant payé, avec un minimum de 0,80 $, déduits lorsque la recharge est créditée : un paiement de 100 $ crédite 94,50 $, et un paiement de 10 $ crédite 9,20 $. Les frais en vigueur sont affichés sur la page [Crédits](https://dash.hyperouter.app/credits) avant que vous ne demandiez une recharge, et chaque recharge de votre historique indique le montant payé et les frais. Les requêtes elles-mêmes ne supportent aucuns frais.

### Limites de débit {#rate-limits}

Chaque compte a une limite de requêtes par minute et de requêtes en cours simultanément. Les deux comptent ensemble toutes les clés API du compte, et s’appliquent à chaque requête qui démarre un travail : chat completions, créations d’images et de vidéos, et créations brutes. Lire un résultat (`GET /v1/generations/{id}`, `GET /v1/videos…`, une interrogation de tâche brute) ne compte pas ; les interrogations brutes sont comptées à part, par fournisseur, avec la même limite de requêtes par minute.

Votre palier dépend du total que vous avez rechargé, avant déduction des frais de recharge :

| Palier | Rechargé | Requêtes par minute | Requêtes simultanées |
| --- | --- | --- | --- |
| 1 | Moins de $10 | 60 | 10 |
| 2 | $10 ou plus | 300 | 30 |
| 3 | $100 ou plus | 1,200 | 100 |
| 4 | $1,000 ou plus | 6,000 | 300 |

Vos limites actuelles figurent sur la page [Crédits](https://dash.hyperouter.app/credits). Ce sont les valeurs par défaut ; notre équipe peut les modifier pour la plateforme ou pour un compte, et un changement s’applique en moins d’une minute.

Au-delà de l’une ou l’autre limite, la requête est refusée avant que quoi que ce soit ne soit bloqué ou transmis, avec `429`, `type: rate_limit_error`, `code: rate_limited` et un en-tête `Retry-After` en secondes. Le message nomme la limite atteinte. Une requête compte pour la minute même lorsqu’elle est refusée. Une requête en cours, flux compris, compte jusqu’à sa fin.

Pour se protéger contre la devinette de clés, Hyperouter compte les vérifications de clé API échouées par adresse client. Plus de 30 en une minute bloquent cette adresse sur toutes les routes `/v1` pendant 15 minutes : `429`, `rate_limit_error`, `too_many_auth_failures`, avec `Retry-After`. Une clé valide ne lève pas le blocage.

## Support {#support}

Ouvrez un ticket dans le [tableau de bord](https://dash.hyperouter.app/tickets) pour la facturation, les recharges et les questions sur l’API ; notre équipe répond dans le même fil. Vous pouvez aussi écrire à [account@hyperouter.app](mailto:account@hyperouter.app). Veuillez indiquer l’`id` de la réponse lorsque vous signalez une requête précise.
