# L’API di KolWrite per gli agenti IA

Come lasciare che un agente IA (Claude Code, Codex, Cursor o uno script tuo)
trasformi le registrazioni in trascrizioni, sottotitoli SRT e tempi per parola con
l’API di piattaforma di KolWrite su `https://api.kolwrite.com`. Una persona crea
un token una sola volta; l’agente lo legge da una variabile d’ambiente e chiama
semplicemente HTTPS. Non serve nessun SDK.

Questa è l’API di piattaforma, con token `kol_`. L’API dei crediti su
`https://app.kolwrite.com` (intestazione `x-api-key`) è un’altra API, descritta (in inglese) su
https://console.kolwrite.com/llms.txt per chi cerca quella.

## 1. Dare un token all’agente

- L’accesso all’API è attivato per singolo account; senza, creare o usare un token viene rifiutato.
- Una persona crea il token nell’app KolWrite (**Impostazioni → Account → Accesso
  API → Crea token**) e sceglie il più piccolo insieme di ambiti. Il segreto
  (`kol_...`) viene mostrato una sola volta.
- Conservalo in una variabile d’ambiente, lontano da prompt, log e git.

| Attività | Ambiti |
|---|---|
| Trascrivere ed esportare | `files:write`, `jobs:write`, `jobs:read`, `transcripts:read` |
| Controllare anche il saldo crediti | aggiungi `billing:read` |
| Endpoint compatibili con OpenAI | `ai:use` (consuma crediti; non fa parte della preimpostazione Automazione dell’app) |
| Creare link di condivisione | aggiungi `shares:write` |

Incolla questo in `AGENTS.md`, `CLAUDE.md` o `.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. Quale chiamata usare

| Vuoi | Chiamata |
|---|---|
| La trascrizione di un file, con tempi per parola e parlanti | sessione di caricamento, poi `POST /api/v1/jobs` (`type: transcription`), poi `GET /api/v1/jobs/{job_id}/export?format=json` |
| Sottotitoli SRT o VTT | `GET /api/v1/jobs/{job_id}/export?format=srt` oppure `format=vtt` (piano Plus o superiore) |
| Sottotitoli in un’altra lingua | `options.target_language` sul job, poi esportare |
| Testo semplice | `export?format=text` |
| Il testo di una breve clip vocale, senza job | `POST /api/v1/ai/openai/audio/transcriptions` |
| Chat, risposte, embeddings | `/api/v1/ai/openai/*` ([endpoint compatibili con OpenAI](/locales/it/openai-compatible.md)) |
| La trascrizione salvata, con le versioni | `GET /api/v1/jobs/{job_id}/artifacts` |
| Il saldo crediti | `GET /api/v1/billing/balance` |
| Un link pubblico a una trascrizione | `POST /api/v1/shares` |
| Quali profili di modello esistono | `GET /api/v1/models` |

## 3. Trascrivere un file in 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']}")
```

I file grandi vengono caricati a parti: la sessione restituisce allora `data.upload_url`
come `null` e un URL per ogni parte in `data.multipart_part_urls` (vedi la
[guida rapida](/locales/it/quickstart.md)).

## 4. Usare i tempi

L’esportazione JSON contiene `segments`, ciascuno con `start` e `end` in secondi, `text`,
uno `speaker` quando la separazione dei parlanti è stata eseguita e `words` (`text`, `start`,
`end`). Lascia che l’agente scelga i segmenti per indice e legga i tempi dalla trascrizione;
non lasciare mai che un modello scriva a mano i timestamp.

```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)
```

I sottotitoli arrivano direttamente dall’API. `include_speakers=false` omette i nomi dei
parlanti e `max_chars_per_entry` imposta la lunghezza di ogni voce di sottotitolo:

```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"
```

Il download di SRT e VTT richiede il piano Plus o superiore. Gli altri piani ricevono
HTTP 403 con il codice `subtitle_export_requires_plus`; le esportazioni JSON e di testo
non sono interessate.

## 5. Regole per gli agenti

- Invia un `Idempotency-Key` quando crei un job, così un nuovo tentativo non ne avvia un secondo.
- Riprova dopo `429`, `502`, `503` e `504` con backoff esponenziale; su `429` attendi
  `Retry-After`. Non riprovare dopo `401`, `403` o errori di validazione. Vedi
  [errori](/locales/it/errors.md).
- Leggi `detail.code`, non il testo del messaggio.
- Un job riserva crediti alla creazione e li regola al termine; controlla
  `GET /api/v1/billing/balance` prima di un lotto grande.
- Il file OpenAPI è il contratto: ogni operazione indica l’ambito richiesto in
  `x-required-scope`.

## Domande

### Come faccio i sottotitoli SRT da un video con un agente IA?

Carica il file, crea un job di trascrizione, attendi `succeeded` e chiama l’endpoint di
esportazione con `format=srt`. I sottotitoli richiedono il piano Plus o superiore.

### Claude Code o Codex possono chiamare l’API di KolWrite?

Sì. La chiamano via HTTPS con un token di accesso personale letto da una variabile d’ambiente.
Non c’è nessun SDK né server MCP da installare.

### Come ottiene un agente il timestamp di ogni parola?

Esporta il job come JSON. Ogni segmento contiene `words` con `start` e `end` in secondi.
