# L’API KolWrite pour les agents IA

Comment laisser un agent IA (Claude Code, Codex, Cursor ou un script à vous)
transformer des enregistrements en transcriptions, sous-titres SRT et horodatages
mot à mot avec l’API de plateforme KolWrite, sur `https://api.kolwrite.com`. Une
personne crée un token une seule fois ; l’agent le lit dans une variable
d’environnement et appelle simplement HTTPS. Aucun SDK n’est nécessaire.

Voici l’API de plateforme, avec des tokens `kol_`. L’API de crédits de
`https://app.kolwrite.com` (en-tête `x-api-key`) est une autre API, décrite (en anglais) sur
https://console.kolwrite.com/llms.txt si c’est celle que vous cherchez.

## 1. Donner un token à l’agent

- L’accès à l’API est activé compte par compte ; sans lui, créer ou utiliser un token est refusé.
- Une personne crée le token dans l’app KolWrite (**Réglages → Compte → Accès à
  l’API → Créer un token**) et choisit le plus petit ensemble de scopes. Le secret
  (`kol_...`) n’est affiché qu’une fois.
- Rangez-le dans une variable d’environnement, loin des prompts, des journaux et de git.

| Tâche | Scopes |
|---|---|
| Transcrire et exporter | `files:write`, `jobs:write`, `jobs:read`, `transcripts:read` |
| Consulter aussi le solde de crédits | ajoutez `billing:read` |
| Endpoints compatibles OpenAI | `ai:use` (consomme des crédits ; absent du préréglage Automation de l’app) |
| Créer des liens de partage | ajoutez `shares:write` |

Collez ceci dans `AGENTS.md`, `CLAUDE.md` ou `.cursorrules` :

```
## KolWrite platform API
- Base URL https://api.kolwrite.com. The token is in the environment variable KOLWRITE_TOKEN: send it as "Authorization: Bearer $KOLWRITE_TOKEN" and never print it.
- Flow: POST /api/v1/media/upload-session, PUT the bytes to data.upload_url, POST /api/v1/media/complete, POST /api/v1/jobs, poll GET /api/v1/jobs/{job_id} until status is succeeded, then GET /api/v1/jobs/{job_id}/export?format=json
- Spec: https://kolwrite-docs.pages.dev/openapi.json. Guide: https://kolwrite-docs.pages.dev/agents.md
```

## 2. Quel appel utiliser

| Vous voulez | Appel |
|---|---|
| La transcription d’un fichier, avec horodatage des mots et locuteurs | session d’envoi, puis `POST /api/v1/jobs` (`type: transcription`), puis `GET /api/v1/jobs/{job_id}/export?format=json` |
| Des sous-titres SRT ou VTT | `GET /api/v1/jobs/{job_id}/export?format=srt` ou `format=vtt` (offre Plus ou supérieure) |
| Des sous-titres dans une autre langue | `options.target_language` sur le job, puis export |
| Du texte brut | `export?format=text` |
| Le texte d’un court clip vocal, sans job | `POST /api/v1/ai/openai/audio/transcriptions` |
| Chat, réponses, embeddings | `/api/v1/ai/openai/*` ([endpoints compatibles OpenAI](/locales/fr/openai-compatible.md)) |
| La transcription enregistrée, avec ses versions | `GET /api/v1/jobs/{job_id}/artifacts` |
| Le solde de crédits | `GET /api/v1/billing/balance` |
| Un lien public vers une transcription | `POST /api/v1/shares` |
| Quels profils de modèle existent | `GET /api/v1/models` |

## 3. Transcrire un fichier en Python

```python
import json, mimetypes, os, time
import requests

BASE = "https://api.kolwrite.com"
TOKEN = os.environ["KOLWRITE_TOKEN"]


def call(method, path, **kw):
    headers = {"Authorization": f"Bearer {TOKEN}", **kw.pop("headers", {})}
    r = requests.request(method, BASE + path, headers=headers, timeout=120, **kw)
    r.raise_for_status()
    return r


def transcribe(path, profile="oracle", language="auto"):
    name, size = os.path.basename(path), os.path.getsize(path)
    ctype = mimetypes.guess_type(name)[0] or "application/octet-stream"
    session = call("POST", "/api/v1/media/upload-session",
                   json={"filename": name, "file_size_bytes": size,
                         "content_type": ctype}).json()["data"]
    with open(path, "rb") as f:
        requests.put(session["upload_url"], data=f, headers=session["headers"],
                     timeout=3600).raise_for_status()
    media = call("POST", "/api/v1/media/complete",
                 json={"finalize_token": session["finalize_token"]}).json()["data"]
    job = call("POST", "/api/v1/jobs",
               headers={"Idempotency-Key": f"{name}-{size}-{profile}-{language}"},
               json={"type": "transcription",
                     "input": {"type": "media_ref", "media_id": media["media_id"]},
                     "profile": profile,
                     "options": {"language": language,
                                 "diarization": {"mode": "regular"}}}).json()["data"]
    while True:
        status = call("GET", f"/api/v1/jobs/{job['job_id']}").json()["data"]["status"]
        if status in ("succeeded", "failed", "cancelled"):
            break
        time.sleep(5)
    if status != "succeeded":
        raise RuntimeError(f"job {job['job_id']} {status}")
    export = call("GET", f"/api/v1/jobs/{job['job_id']}/export",
                  params={"format": "json"}).json()
    return job["job_id"], json.loads(export["data"])  # data is a JSON string


job_id, transcript = transcribe("interview.wav")
for seg in transcript["segments"]:
    print(f"{seg['start']:.2f}-{seg['end']:.2f} {seg.get('speaker', '')} {seg['text']}")
```

Les fichiers volumineux sont envoyés en plusieurs parties : la session renvoie alors
`data.upload_url` à `null` et une URL par partie dans `data.multipart_part_urls` (voir le
[démarrage rapide](/locales/fr/quickstart.md)).

## 4. Exploiter les horodatages

L’export JSON contient `segments`, chacun avec `start` et `end` en secondes, `text`,
un `speaker` quand la diarisation a tourné, et `words` (`text`, `start`, `end`). Laissez
l’agent choisir des segments par indice et lire les temps dans la transcription ; ne le
laissez jamais saisir lui-même des horodatages.

```python
import subprocess

keep = [s for s in transcript["segments"] if "pricing" in s["text"].lower()]
for i, seg in enumerate(keep):
    subprocess.run(["ffmpeg", "-y", "-i", "talk.mp4", "-ss", str(seg["start"]),
                    "-to", str(seg["end"]), f"clip{i}.mp4"], check=True)
```

Les sous-titres viennent directement de l’API. `include_speakers=false` retire les noms
des locuteurs, et `max_chars_per_entry` fixe la longueur de chaque entrée de sous-titre :

```bash
curl -H "Authorization: Bearer $KOLWRITE_TOKEN" -o talk.srt \
  "https://api.kolwrite.com/api/v1/jobs/JOB_ID/export?format=srt&include_speakers=false&max_chars_per_entry=42"
```

Le téléchargement des SRT et VTT nécessite l’offre Plus ou supérieure. Les autres offres
reçoivent un HTTP 403 avec le code `subtitle_export_requires_plus` ; les exports JSON et
texte ne sont pas concernés.

## 5. Règles pour les agents

- Envoyez un `Idempotency-Key` à la création d’un job, pour qu’une nouvelle tentative n’en lance pas un second.
- Réessayez après `429`, `502`, `503` et `504` avec un délai exponentiel ; sur `429`, attendez
  `Retry-After`. Ne réessayez pas après `401`, `403` ni après une erreur de validation. Voir
  [erreurs](/locales/fr/errors.md).
- Lisez `detail.code`, pas le texte du message.
- Un job réserve des crédits à sa création et les règle à sa fin ; consultez
  `GET /api/v1/billing/balance` avant un gros lot.
- Le fichier OpenAPI fait foi : chaque opération indique son scope requis dans
  `x-required-scope`.

## Questions

### Comment créer des sous-titres SRT à partir d’une vidéo avec un agent IA ?

Envoyez le fichier, créez un job de transcription, attendez `succeeded`, puis appelez
l’endpoint d’export avec `format=srt`. Les sous-titres nécessitent l’offre Plus ou supérieure.

### Claude Code ou Codex peuvent-ils appeler l’API KolWrite ?

Oui. Ils l’appellent en HTTPS avec un token d’accès personnel pris dans une variable
d’environnement. Il n’y a ni SDK ni serveur MCP à installer.

### Comment un agent obtient-il l’horodatage de chaque mot ?

Exportez le job en JSON. Chaque segment porte `words` avec `start` et `end` en secondes.
