# Die KolWrite-API für KI-Agenten

So lassen Sie einen KI-Agenten (Claude Code, Codex, Cursor oder ein eigenes Skript)
Aufnahmen in Transkripte, SRT-Untertitel und Wortzeiten verwandeln – mit der
KolWrite-Plattform-API unter `https://api.kolwrite.com`. Eine Person erstellt
einmal ein Token; der Agent liest es aus einer Umgebungsvariablen und ruft
schlicht HTTPS auf. Ein SDK ist nicht nötig.

Dies ist die Plattform-API mit `kol_`-Tokens. Die Credits-API unter
`https://app.kolwrite.com` (Header `x-api-key`) ist eine andere API und wird (auf Englisch) unter
https://console.kolwrite.com/llms.txt beschrieben, falls Sie diese suchen.

## 1. Dem Agenten ein Token geben

- Der API-Zugriff wird pro Konto freigeschaltet; ohne ihn werden Erstellen und Nutzen eines Tokens abgelehnt.
- Eine Person erstellt das Token in der KolWrite-App (**Einstellungen → Konto →
  API-Zugriff → Token erstellen**) und wählt die kleinstmögliche Menge an Bereichen.
  Das Geheimnis (`kol_...`) wird nur einmal angezeigt.
- Legen Sie es in einer Umgebungsvariablen ab, fern von Prompts, Logs und git.

| Aufgabe | Bereiche |
|---|---|
| Transkribieren und exportieren | `files:write`, `jobs:write`, `jobs:read`, `transcripts:read` |
| Zusätzlich das Credit-Guthaben prüfen | `billing:read` ergänzen |
| OpenAI-kompatible Endpunkte | `ai:use` (verbraucht Credits; nicht Teil der Voreinstellung Automatisierung der App) |
| Freigabelinks erstellen | `shares:write` ergänzen |

Fügen Sie dies in `AGENTS.md`, `CLAUDE.md` oder `.cursorrules` ein:

```
## 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. Welchen Aufruf Sie wann nutzen

| Sie möchten | Aufruf |
|---|---|
| Das Transkript einer Datei, mit Wortzeiten und Sprechern | Upload-Sitzung, dann `POST /api/v1/jobs` (`type: transcription`), dann `GET /api/v1/jobs/{job_id}/export?format=json` |
| SRT- oder VTT-Untertitel | `GET /api/v1/jobs/{job_id}/export?format=srt` oder `format=vtt` (Plus-Tarif oder höher) |
| Untertitel in einer anderen Sprache | `options.target_language` am Job, dann exportieren |
| Reinen Text | `export?format=text` |
| Den Text eines kurzen Sprachclips, ohne Job | `POST /api/v1/ai/openai/audio/transcriptions` |
| Chat, Antworten, Embeddings | `/api/v1/ai/openai/*` ([OpenAI-kompatible Endpunkte](/locales/de/openai-compatible.md)) |
| Das gespeicherte, versionierte Transkript | `GET /api/v1/jobs/{job_id}/artifacts` |
| Das Credit-Guthaben | `GET /api/v1/billing/balance` |
| Einen öffentlichen Link zu einem Transkript | `POST /api/v1/shares` |
| Welche Modellprofile es gibt | `GET /api/v1/models` |

## 3. Eine Datei in Python transkribieren

```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']}")
```

Große Dateien werden in Teilen hochgeladen: Die Sitzung liefert dann `data.upload_url`
als `null` und je Teil eine URL in `data.multipart_part_urls` (siehe
[Schnellstart](/locales/de/quickstart.md)).

## 4. Die Zeiten nutzen

Der JSON-Export enthält `segments`, jedes mit `start` und `end` in Sekunden, `text`,
einem `speaker`, wenn die Sprechererkennung lief, und `words` (`text`, `start`, `end`).
Lassen Sie den Agenten Segmente nach Index wählen und die Zeiten aus dem Transkript
lesen; lassen Sie ein Modell niemals Zeitstempel selbst eintippen.

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

Untertitel kommen direkt aus der API. `include_speakers=false` lässt die Sprechernamen
weg, und `max_chars_per_entry` legt die Länge jedes Untertiteleintrags fest:

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

Der Download von SRT und VTT setzt einen Plus-Tarif oder höher voraus. Andere Tarife
erhalten HTTP 403 mit dem Code `subtitle_export_requires_plus`; JSON- und Text-Exporte
sind nicht betroffen.

## 5. Regeln für Agenten

- Senden Sie beim Erstellen eines Jobs einen `Idempotency-Key`, damit eine Wiederholung keinen zweiten Job startet.
- Wiederholen Sie `429`, `502`, `503` und `504` mit exponentiellem Backoff; bei `429` warten Sie auf
  `Retry-After`. Wiederholen Sie `401`, `403` oder Validierungsfehler nicht. Siehe
  [Fehler](/locales/de/errors.md).
- Lesen Sie `detail.code`, nicht den Meldungstext.
- Ein Job reserviert Credits bei der Erstellung und rechnet bei Abschluss ab; prüfen Sie
  `GET /api/v1/billing/balance` vor einem großen Stapel.
- Die OpenAPI-Datei ist der Vertrag: Jede Operation nennt ihren erforderlichen Bereich in
  `x-required-scope`.

## Fragen

### Wie erstelle ich SRT-Untertitel aus einem Video mit einem KI-Agenten?

Laden Sie die Datei hoch, erstellen Sie einen Transkriptionsjob, warten Sie auf `succeeded` und
rufen Sie den Export-Endpunkt mit `format=srt` auf. Untertitel setzen einen Plus-Tarif oder höher voraus.

### Können Claude Code oder Codex die KolWrite-API aufrufen?

Ja. Sie rufen sie per HTTPS mit einem persönlichen Zugriffstoken aus einer Umgebungsvariablen
auf. Es ist weder ein SDK noch ein MCP-Server zu installieren.

### Wie bekommt ein Agent Zeitstempel für jedes Wort?

Exportieren Sie den Job als JSON. Jedes Segment enthält `words` mit `start` und `end` in Sekunden.
