> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apimart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Génération vidéo apimart-zoe

> Génération et montage vidéo avec texte, images et références vidéo/audio : 480p–4k, dernière image, MOV et passage du brouillon à la vidéo finale.

> [!NOTE]
>
> Cette API est asynchrone. Après envoi, récupérez `data[0].task_id`, puis consultez le [statut de la tâche](https://docs.apimart.ai/fr/api-reference/tasks/status.md). La génération prend généralement 1 à 3 minutes ; interrogez le statut toutes les 5 à 10 secondes. Le délai réel dépend de la tâche.

```bash cURL theme={null}
curl --request POST \
  --url https://api.apimart.ai/v1/videos/generations \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "model": "apimart-zoe",
    "prompt": "Un chaton roux sur un rebord de fenêtre regarde la caméra, éclairé par le soleil de côté, avec un lent travelling avant",
    "resolution": "1080p",
    "aspect_ratio": "16:9",
    "duration": 8,
    "audio": true,
    "nsfw_check": true,
    "return_last_frame": true
  }'
```

```python Python theme={null}
import requests

response = requests.post(
    "https://api.apimart.ai/v1/videos/generations",
    headers={"Authorization": "Bearer <token>"},
    json={
        "model": "apimart-zoe",
        "prompt": "Un chaton roux sur un rebord de fenêtre regarde la caméra, éclairé par le soleil de côté, avec un lent travelling avant",
        "resolution": "1080p",
        "aspect_ratio": "16:9",
        "duration": 8,
        "audio": True,
        "nsfw_check": True,
        "return_last_frame": True
    }
)
response.raise_for_status()
print(response.json())
```

```javascript JavaScript theme={null}
const response = await fetch("https://api.apimart.ai/v1/videos/generations", {
  method: "POST",
  headers: {
    Authorization: "Bearer <token>",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "model": "apimart-zoe",
    "prompt": "Un chaton roux sur un rebord de fenêtre regarde la caméra, éclairé par le soleil de côté, avec un lent travelling avant",
    "resolution": "1080p",
    "aspect_ratio": "16:9",
    "duration": 8,
    "audio": true,
    "nsfw_check": true,
    "return_last_frame": true
  })
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

```json 200 theme={null}
{
  "code": 200,
  "data": [
    {
      "status": "submitted",
      "task_id": "task_01KZOEEXAMPLE"
    }
  ]
}
```

## Choisir un mode

| Mode | Paramètres |
| - | - |
| Texte vers vidéo | Envoyez `prompt` sans média. |
| Images de début/fin vers vidéo | Utilisez les rôles `first_frame` et éventuellement `last_frame` dans `image_with_roles`, ou `first_frame_image` / `last_frame_image`. |
| Références vers vidéo | Images ou vidéos de référence, éventuellement accompagnées d’audio. Audio seul interdit. |
| Brouillon | `draft: true` produit une sortie `480p`. |
| Vidéo finale | `draft_task_id` d’un brouillon réussi produit une sortie `1080p`. |
| Montage vidéo | Utilisez `omni_reference_task_type: "edit"` et au moins une vidéo source. Durée et ratio suivent la source. |

## En-têtes

**Authorization** `string` *required*

Authentification Bearer : `Bearer <token>`, où `<token>` est votre clé API APIMart. Envoyez le corps avec `Content-Type: application/json`.

## Paramètres

**model** `string` *required*

Valeur fixe : `apimart-zoe`.

**prompt** `string`

Jusqu’à 204 800 caractères. Obligatoire pour le texte vers vidéo ; facultatif avec des médias seuls. Interdit avec `draft_task_id` : le prompt est repris du brouillon.

**resolution** `string`

`480p`, `720p`, `1080p`, `2k`, `4k`, sans distinction de casse. Génération normale : `720p` par défaut. Avec `draft: true`, seul `480p` est accepté, y compris par défaut. Avec `draft_task_id`, seul `1080p` est accepté, y compris par défaut.

**aspect\_ratio** `string`

`21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `adaptive`. Par défaut : `adaptive`, déterminé par le modèle ou les médias. À omettre pour le rendu final, qui reprend le format du brouillon.

En montage (`edit`), omettez ce champ ou utilisez `adaptive` pour suivre la source.

**size** `string`

Autre nom de `aspect_ratio`, avec les mêmes valeurs. Utilisez de préférence un seul des deux champs. À omettre pour le rendu final.

**duration** `integer`

Génération normale : Secondes entières de `4` à `30`, par défaut `5`. La durée demandée ou par défaut est facturée sans ajustement à la longueur du résultat. Avec `-1`, le modèle décide : réservation de 30 secondes, puis régularisation selon la durée réelle arrondie à la seconde la plus proche (0.5 vers le haut). À omettre pour le rendu final, qui reprend la durée du brouillon.

En montage (`edit`), omettez ce champ ou utilisez `-1`. Son omission équivaut à `-1` ; la durée suit la vidéo source.

**seconds** `integer`

Alias de `duration`, avec les mêmes règles. Utilisez un seul des deux champs. À omettre pour le rendu final.

**audio** `boolean`

Booléen contrôlant la génération audio avec la vidéo. Alias : `generate_audio`. Si omis, le modèle décide. Interdit pour le rendu final. Contrôle le son de sortie, contrairement aux références d’entrée `audio_urls`.

Si `audio` et `generate_audio` sont tous deux fournis, leurs valeurs doivent être identiques, sinon HTTP 400. Sans valeur, le modèle décide et inclut généralement du son.

**generate\_audio** `boolean`

Alias de `audio`, avec les mêmes règles. Utilisez un seul des champs et omettez-le pour le rendu final.

Si `audio` et `generate_audio` sont tous deux fournis, leurs valeurs doivent être identiques, sinon HTTP 400. Sans valeur, le modèle décide et inclut généralement du son.

**seed** `integer`

Graine aléatoire entière. Interdite pour le rendu final.

**nsfw\_check** `boolean`

La modération est désactivée par défaut ; `true` l’active.

Booléen facultatif. `true` active la modération de `prompt`, `image_urls`, `image_with_roles`, des images de début/fin et des images `asset://`. Les vidéos et audios de référence ne sont pas modérés.

La requête finale ne contient ni prompt ni média. Dans le flux brouillon → vidéo finale, la modération a lieu à la génération du brouillon.

Une détection renvoie HTTP 400 (`nsfw_content_detected`) avec les catégories dans `error.message`. Affichez un refus de modération plutôt qu’une erreur générique de paramètres. En cas d’échec ou d’indisponibilité du service de modération, la génération continue ; ce paramètre ne garantit donc pas une modération achevée pour chaque requête.

**omni\_reference\_task\_type** `string`

Valeurs : `auto` (défaut, équivaut à omettre ; le modèle décide), `reference`, `edit`. `reference` / `edit` nécessitent des médias de référence ; texte seul ou images de début/fin donnent HTTP 400 avec ces valeurs. `edit` exige au moins une entrée dans `video_urls` et interdit `draft: true`. `extend` est indisponible et renvoie HTTP 400. Omettre pour la génération finale.

**return\_last\_frame** `boolean`

Avec `true`, renvoie aussi la dernière image en PNG dans `data.result.last_frame_url`. Désactivé par défaut. Omettre pour la génération finale.

**output\_format** `string`

`mp4` (défaut) ou `mov` (conteneur QuickTime) ; autres valeurs : HTTP 400. Alias : `response_format`. Si les deux sont fournis, `output_format` est prioritaire. Omettre pour la génération finale.

**response\_format** `string`

Alias de `output_format`, accepte `mp4` / `mov`. Si les deux sont fournis, `output_format` est prioritaire. Omettre pour la génération finale.

**enhance\_prompt** `boolean`

Avec `true`, optimise automatiquement le prompt avant génération. Désactivé par défaut : le prompt original est utilisé. Omettre pour la génération finale.

**image\_with\_roles** `object[]`

Chaque élément contient `url` et `role`. Rôles : `first_frame` (début), `last_frame` (fin), `reference_image` (référence). Une image de début et une de fin au maximum ; la fin exige un début. Ces images ne peuvent pas être combinées avec des médias de référence.

Ne transmettez pas `image_urls` et `image_with_roles` ensemble : cela renvoie HTTP 400 (`invalid_request_error`) à l’envoi, sans frais. Les alias `image`, `images` et `image_url` comptent comme `image_urls` et ne contournent pas cette restriction.

**image\_urls** `string[]`

Jusqu’à 30 images de référence. Consultez les exigences de format, dimensions et taille des fichiers ci-dessous.

Ne transmettez pas `image_urls` et `image_with_roles` ensemble : cela renvoie HTTP 400 (`invalid_request_error`) à l’envoi, sans frais. Les alias `image`, `images` et `image_url` comptent comme `image_urls` et ne contournent pas cette restriction.

**first\_frame\_image** `string`

Adresse de la première image, équivalente au rôle `first_frame` dans `image_with_roles`.

**last\_frame\_image** `string`

Adresse de la dernière image, équivalente au rôle `last_frame`. Une première image est également obligatoire.

**video\_urls** `string[]`

Jusqu’à 10 vidéos de référence. URL `http(s)` publique ou `asset://<ID du média>`, sans `data:` Base64. Dimensions, durée exacte et fréquence d’images sont vérifiées à l’envoi selon les règles ci-dessous.

Pour la facturation, les durées sont additionnées puis la somme est arrondie à l’entier inférieur, sans ajustement ultérieur. Cet arrondi ne sert pas à valider la durée de chaque vidéo. Accès ou mesure de durée impossible : HTTP 400 (`input_video_probe_failed`), sans frais.

`auto` / `reference` : 2–15 secondes, plage acceptée `1.8 ≤ t ≤ 15.2`. Sources pour `edit` : 4–30 secondes, plage acceptée `3.8 ≤ t < 30.2`. Vérifier la durée exacte sans arrondir.

**audio\_urls** `string[]`

Jusqu’à 10 ; WAV / MP3 ; chacun 2–15 secondes et `≤ 15 MB`, au plus 30 secondes au total. Tolérance ±0.2 seconde : `1.8 ≤ t ≤ 15.2` par extrait, total `≤ 30.2`. Nécessite une image ou vidéo de référence.

URL publique `http(s)` ou `asset://<ID du média>` ; pas de `data:` Base64. La durée audio est vérifiée à l’envoi ; toute durée invalide renvoie HTTP 400 synchrone sans frais.

**draft** `boolean`

`true` génère un brouillon `480p`. Les autres paramètres suivent les règles normales. Ne pas envoyer ce champ pour le rendu final.

Le montage vidéo (`edit`) ne permet pas de générer un brouillon.

**draft\_task\_id** `string`

`task_id` d’un brouillon réussi pour générer une vidéo finale `1080p`. À utiliser dans les 24 heures suivant la création du brouillon. Prompt, médias, durée et format sont repris ; ne les envoyez pas à nouveau. Le rendu final interdit aussi `seed`, `audio` et son alias `generate_audio`.

## Adresses et limites des médias

Images, vidéos et audios acceptent des URL publiques `http(s)` ou des médias APIMart `asset://<ID du média>`. Aucun n’accepte `data:` Base64 ; téléversez les médias au préalable. Leur enregistrement est automatique, sans appel API supplémentaire. Un échec fait échouer la tâche avec remboursement.

| Média | Exigences |
| - | - |
| Images de référence | Jusqu’à 30 ; JPEG / PNG / WebP / BMP / TIFF / GIF / HEIC / HEIF ; ratio 0.4–2.5 ; largeur et hauteur chacune de 300 à 6000 pixels ; chacune `< 30 MB`. |
| Vidéos de référence | Jusqu’à 10 ; MP4 / MOV (H.264 / H.265; piste audio: AAC / MP3) ; chacune `≤ 200 MB`. Voir les règles de dimensions, durée et fréquence d’images ci-dessous. |
| Audio de référence | Jusqu’à 10 ; WAV / MP3 ; chacun 2–15 secondes et `≤ 15 MB`, au plus 30 secondes au total. Tolérance ±0.2 seconde : `1.8 ≤ t ≤ 15.2` par extrait, total `≤ 30.2`. Nécessite une image ou vidéo de référence. |

### Validation des vidéos de référence

| Propriété | Exigence |
| - | - |
| Largeur et hauteur | Largeur et hauteur chacune de 300 à 6000 pixels. |
| Surface en pixels (largeur × hauteur) | 409 600–8 295 044 pixels. |
| Ratio (largeur / hauteur) | 0.4–2.5. |
| Durée par vidéo | `auto` / `reference` : 2–15 secondes, plage acceptée `1.8 ≤ t ≤ 15.2`. Sources pour `edit` : 4–30 secondes, plage acceptée `3.8 ≤ t < 30.2`. Vérifier la durée exacte sans arrondir. |
| Fréquence d’images | Arrondir à l’entier le plus proche (0.5 vers le haut), puis vérifier la plage 24–60fps. 23.976 et 59.94 sont acceptés. |

Dimensions vidéo, surface en pixels, ratio, durée exacte, fréquence d’images et durée audio de référence sont vérifiés à l’envoi. Toute violation renvoie HTTP 400 synchrone sans frais. Les erreurs vidéo utilisent `invalid_request_error` et incluent les mesures. Utilisez la surface en pixels, pas un petit côté minimum de 480 et un grand côté maximum de 4096.

Pour les dimensions seules, 420×1000 (420 000 pixels) convient. 720×480 (345 600 pixels), 848×480 (407 040 pixels) et 4096×2160 (8 847 360 pixels) ne respectent pas la surface autorisée. La durée, la fréquence d’images et les autres exigences restent applicables.

Dimensions des images, taille des fichiers et encodage sont vérifiés pendant la génération. Un média invalide fait échouer la tâche avec remboursement automatique. Vérifiez ces propriétés avant l’appel lorsque possible.

### Règles de combinaison des médias

* Ne transmettez pas `image_urls` et `image_with_roles` ensemble : cela renvoie HTTP 400 (`invalid_request_error`) à l’envoi, sans frais. Les alias `image`, `images` et `image_url` comptent comme `image_urls` et ne contournent pas cette restriction.
* Les images de début/fin ne peuvent pas figurer dans la même requête que des références image, vidéo ou audio.
* Une image de début et une de fin au maximum. La fin exige un début.
* Jusqu’à 30 images, 10 vidéos et 10 audios de référence, dans la limite de 50 médias au total.
* Toute violation de ces combinaisons ou quantités entraîne un HTTP 400 synchrone, sans facturation.
* `omni_reference_task_type: "edit"` exige au moins une vidéo source (`3.8 ≤ t < 30.2` secondes). `duration` / `seconds` : omission ou `-1` uniquement ; `aspect_ratio` / `size` : omission ou `adaptive` uniquement. Interdit avec `draft: true`. Les modes auto/reference conservent la limite 2–15 secondes et sa tolérance.

> [!WARNING]
>
> Ces champs renvoient HTTP 400 dès qu’ils sont présents, même avec `""`, `[]`, `false` ou `null` ; supprimez-les entièrement : `negative_prompt`, `camera_fixed`, `camerafixed`, `extend_from_task_id`, `mask_url`, `video_list`, `element_list`, `multi_shot`, `tools`. Omettez `n` ou utilisez `1`.

## Exemples de requêtes

Les URL ci-dessous sont fictives. Remplacez-les par des adresses de médias accessibles.

### Première et dernière images

```json theme={null}
{
  "model": "apimart-zoe",
  "prompt": "Transition naturelle de la première à la dernière image, avec un éclairage cohérent",
  "image_with_roles": [
    {
      "url": "https://example.com/start.jpg",
      "role": "first_frame"
    },
    {
      "url": "https://example.com/end.jpg",
      "role": "last_frame"
    }
  ],
  "duration": 6
}
```

Vous pouvez aussi utiliser `first_frame_image` et `last_frame_image`. N’ajoutez pas de références image, vidéo ou audio à cette même requête.

### Références image, vidéo et audio

```json theme={null}
{
  "model": "apimart-zoe",
  "prompt": "Le personnage de référence se déplace naturellement dans la scène de la vidéo, avec une musique adaptée au rythme des images",
  "image_urls": [
    "https://example.com/character.jpg"
  ],
  "video_urls": [
    "https://example.com/scene.mp4"
  ],
  "audio_urls": [
    "https://example.com/bgm.mp3"
  ],
  "duration": -1
}
```

Les images de `image_urls` sont des références. Avec `duration: -1`, le modèle choisit la durée du résultat.

### Montage vidéo

`omni_reference_task_type: "edit"` exige au moins une vidéo source (`3.8 ≤ t < 30.2` secondes). `duration` / `seconds` : omission ou `-1` uniquement ; `aspect_ratio` / `size` : omission ou `adaptive` uniquement. Interdit avec `draft: true`. Les modes auto/reference conservent la limite 2–15 secondes et sa tolérance.

```json theme={null}
{
  "model": "apimart-zoe",
  "prompt": "Transformer toute la scène en film classique noir et blanc en conservant les mouvements de caméra et la composition",
  "video_urls": [
    "https://example.com/source_20s.mp4"
  ],
  "omni_reference_task_type": "edit"
}
```

Le montage utilise le tarif avec vidéo de référence. Réservez 30 secondes de sortie, puis ajustez selon la durée réelle arrondie à la seconde, en incluant la durée des vidéos de référence selon la formule ci-dessous.

### Dernière image, sortie MOV et optimisation du prompt

```json theme={null}
{
  "model": "apimart-zoe",
  "prompt": "Un chaton roux sur un rebord de fenêtre regarde la caméra, éclairé par le soleil de côté, avec un lent travelling avant",
  "duration": 5,
  "return_last_frame": true,
  "output_format": "mov",
  "enhance_prompt": true
}
```

## Brouillon → vidéo finale

1. Envoyez le brouillon avec `draft: true`. Si `resolution` est omis, `480p` est utilisé ; si le champ est présent, seule cette valeur est acceptée.

```json theme={null}
{
  "model": "apimart-zoe",
  "prompt": "Un chaton roux sur un rebord de fenêtre regarde la caméra, éclairé par le soleil de côté, avec un lent travelling avant",
  "draft": true
}
```

2. Attendez la réussite du brouillon, puis envoyez son `task_id` pour le rendu final. `model` et `draft_task_id` suffisent. Si vous indiquez `resolution`, utilisez uniquement `1080p`.

```json theme={null}
{
  "model": "apimart-zoe",
  "draft_task_id": "task_01KZOEEXAMPLE"
}
```

> [!WARNING]
>
> La vidéo finale reprend prompt, médias, durée, ratio, seed et réglages audio du brouillon. Envoyez `model`, `draft_task_id`, éventuellement `resolution: "1080p"` et les champs communs comme `nsfw_check` / `webhook`. Les champs de génération suivants doivent être absents ; chaînes/tableaux vides, `false` et `null` donnent aussi HTTP 400 : `prompt`, `image_urls`, `image_with_roles`, `image`, `images`, `image_url`, `first_frame_image`, `last_frame_image`, `video_urls`, `audio_urls`, `duration`, `seconds`, `aspect_ratio`, `size`, `seed`, `audio`, `generate_audio`, `draft`, `omni_reference_task_type`, `return_last_frame`, `output_format`, `response_format`, `enhance_prompt`. Utilisez le brouillon dans les 24 heures suivant sa création ; les deux générations sont facturées séparément.

## Statut et résultat

```bash cURL theme={null}
curl --request GET \
  --url https://api.apimart.ai/v1/tasks/task_01KZOEEXAMPLE \
  --header 'Authorization: Bearer <token>'
```

À l’envoi, `data` est un tableau ; lors de la consultation, c’est un objet tâche. `data.status` : `pending` = en attente, `processing` = génération, `completed` = réussite, `failed` = échec. En cas de réussite, lisez l’adresse dans le tableau `data.result.videos[].url`, par exemple `data.result.videos[0].url[0]`. Les vidéos sont copiées vers le stockage APIMart. Seuls les champs principaux d’une réponse réussie sont présentés ci-dessous.

```json theme={null}
{
  "code": 200,
  "data": {
    "id": "task_01KZOEEXAMPLE",
    "status": "completed",
    "result": {
      "videos": [
        {
          "url": [
            "https://example.com/generated-video.mp4"
          ],
          "expires_at": 1794475442
        }
      ],
      "last_frame_url": "https://example.com/generated-last-frame.png"
    },
    "progress": 100,
    "completed": 1794389042
  }
}
```

En cas d’échec, consultez `data.error.message` pour connaître la raison. Les frais prélevés sont remboursés automatiquement.

| Champ | Description |
| - | - |
| `data.progress` | Progression, 0–100. |
| `data.result.videos[].url` | Tableau des adresses vidéo copiées dans le stockage APIMart. |
| `data.result.videos[].expires_at` | L’adresse expire 24 heures après la fin de la tâche. Téléchargez rapidement le résultat. |
| `data.result.last_frame_url` | URL PNG de la dernière image stockée chez APIMart, uniquement si la tâche réussit avec `return_last_frame: true` dans la requête. |
| `data.error.message` | Raison de l’échec, uniquement pour `failed`. |
| `data.error.code` | Chaîne `task_failed` en cas d’échec. |
| `data.cost` | Coût de la tâche ; 0 en cas d’échec, après remboursement du prélèvement provisoire. |

## Règles de facturation

Facturation à la seconde selon la résolution et la présence de vidéos de référence. Tarifs et remises sur la [page des prix](https://apimart.ai/pricing). `D` désigne la durée de sortie utilisée pour la facturation, en secondes.

| Cas | Durée de sortie facturable D |
| - | - |
| Génération normale: `duration: 4–30` ou omission (défaut 5) | Facturation des secondes demandées, sans ajustement à la durée du résultat. |
| `duration: -1` / montage vidéo `edit` | Réservation de 30 secondes, puis régularisation selon la durée réelle arrondie à la seconde la plus proche (0.5 vers le haut). |
| Vidéo finale | Reprend la durée du brouillon. Si le brouillon utilisait `duration: -1`, sa durée réelle sert de base ; aucun nouvel ajustement à la durée du rendu final. |

* Durée totale des vidéos de référence `R` : mesure de chaque vidéo à l’envoi, addition, puis arrondi de la somme à l’entier inférieur. Aucun ajustement ultérieur.
* Sans vidéo de référence : secondes facturées = `D`. Avec référence : `D + max(R, ceil(D × 2/3))`. `ceil` arrondit à l’entier supérieur. Le tarif avec vidéo de référence s’applique.
* Exemple : une demande de 8 secondes conserve `D = 8`, même si le résultat dure 8.2 secondes. Une durée automatique de 8.5 donne `D = 9`. Deux références de 2.8 et 3.8 secondes sont additionnées à 6.6 puis arrondies vers le bas : `R = 6`.
* Brouillon (`480p`) et rendu final (`1080p`) sont facturés séparément. Les vidéos de référence du brouillon conservent leur durée initiale dans la facturation du rendu final, sans nouvel envoi.
* Aucun frais si l’envoi échoue. Les tâches échouées sont remboursées automatiquement.

## Erreurs courantes

Un envoi échoué renvoie un objet `error` : détails dans `error.message`, code sous forme de chaîne dans `error.code`, distinct du statut HTTP. Aucun frais n’est prélevé.

| HTTP | `error.code` | Cause et solution |
| - | - | - |
| 400 | `invalid_request_error` | Vérifiez durée, résolution, ratio, nombre/combinaison de médias, paramètres non acceptés et contraintes brouillon/final. Aussi renvoyé si `draft_task_id` est inconnu, n’est pas un brouillon, est inachevé ou a plus de 24 heures. Couvre aussi les dimensions, la durée exacte ou la fréquence d’images invalides, ainsi que la combinaison de `image_urls` (alias inclus) et `image_with_roles`. Les erreurs vidéo incluent les mesures. Vérifiez aussi durée audio de référence, type de tâche/contraintes de montage, `output_format`, égalité des alias audio et présence de champs interdits. |
| 400 | `nsfw_content_detected` | Refus de modération, avec catégories dans `error.message`. Affichez ce refus et modifiez le contenu avant de renvoyer la requête. Sans frais. |
| 400 | `input_video_probe_failed` | Vidéo de référence inaccessible ou durée indétectable. Vérifiez adresse et contenu. |
| 402 | `payment_required` | Solde insuffisant ; la durée automatique réserve 30 secondes. |
| 503 | `draft_channel_unavailable` / `draft_task_unavailable` | Canal de génération du brouillon indisponible ou consultation temporairement impossible. Réessayez plus tard ou créez un nouveau brouillon. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.