# API de KolWrite para agentes de IA

Cómo dejar que un agente de IA (Claude Code, Codex, Cursor o un script tuyo)
convierta grabaciones en transcripciones, subtítulos SRT y tiempos por palabra
con la API de plataforma de KolWrite en `https://api.kolwrite.com`. Una persona
crea un token una sola vez; el agente lo lee de una variable de entorno y llama a
HTTPS sin más. No hace falta ningún SDK.

Esta es la API de plataforma con tokens `kol_`. La API de créditos de
`https://app.kolwrite.com` (cabecera `x-api-key`) es otra API distinta, descrita (en inglés) en
https://console.kolwrite.com/llms.txt para quien la busque.

## 1. Dale un token al agente

- El acceso a la API se activa por cuenta; sin él, crear o usar un token se rechaza.
- Una persona crea el token en la app de KolWrite (**Ajustes → Cuenta → Acceso a
  la API → Crear token**) y elige el conjunto más pequeño de ámbitos. El secreto
  (`kol_...`) se muestra una sola vez.
- Guárdalo en una variable de entorno, lejos de prompts, registros y git.

| Tarea | Ámbitos |
|---|---|
| Transcribir y exportar | `files:write`, `jobs:write`, `jobs:read`, `transcripts:read` |
| Consultar también el saldo de créditos | añade `billing:read` |
| Endpoints compatibles con OpenAI | `ai:use` (gasta créditos; no forma parte del ajuste Automation de la app) |
| Crear enlaces para compartir | añade `shares:write` |

Pega esto en `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. Qué llamada usar

| Quieres | Llamada |
|---|---|
| La transcripción de un archivo, con tiempos por palabra y hablantes | sesión de subida, luego `POST /api/v1/jobs` (`type: transcription`), luego `GET /api/v1/jobs/{job_id}/export?format=json` |
| Subtítulos SRT o VTT | `GET /api/v1/jobs/{job_id}/export?format=srt` o `format=vtt` (plan Plus o superior) |
| Subtítulos en otro idioma | `options.target_language` en el trabajo y luego exportar |
| Texto sin formato | `export?format=text` |
| El texto de un clip de voz corto, sin trabajo | `POST /api/v1/ai/openai/audio/transcriptions` |
| Chat, respuestas, embeddings | `/api/v1/ai/openai/*` ([endpoints compatibles con OpenAI](/locales/es/openai-compatible.md)) |
| La transcripción guardada, con versiones | `GET /api/v1/jobs/{job_id}/artifacts` |
| El saldo de créditos | `GET /api/v1/billing/balance` |
| Un enlace público a una transcripción | `POST /api/v1/shares` |
| Qué perfiles de modelo existen | `GET /api/v1/models` |

## 3. Transcribir un archivo 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']}")
```

Los archivos grandes se suben por partes: la sesión devuelve entonces `data.upload_url`
como `null` y una URL por parte en `data.multipart_part_urls` (consulta el
[inicio rápido](/locales/es/quickstart.md)).

## 4. Usar los tiempos

La exportación JSON trae `segments`, cada uno con `start` y `end` en segundos, `text`,
un `speaker` cuando se ejecutó la diarización y `words` (`text`, `start`, `end`). Deja que
el agente elija segmentos por índice y lea los tiempos de la transcripción; nunca dejes
que un modelo escriba marcas de tiempo.

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

Los subtítulos salen directamente de la API. `include_speakers=false` omite los nombres
de los hablantes y `max_chars_per_entry` fija la longitud de cada entrada de subtítulo:

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

Las descargas de SRT y VTT requieren el plan Plus o superior. Los demás planes reciben
HTTP 403 con el código `subtitle_export_requires_plus`; las exportaciones JSON y de texto
no se ven afectadas.

## 5. Reglas para agentes

- Envía un `Idempotency-Key` al crear un trabajo, para que un reintento no inicie un segundo trabajo.
- Reintenta tras `429`, `502`, `503` y `504` con espera exponencial; ante un `429` espera
  `Retry-After`. No reintentes tras `401`, `403` ni errores de validación. Consulta
  [errores](/locales/es/errors.md).
- Lee `detail.code`, no el texto del mensaje.
- Un trabajo reserva créditos al crearse y los liquida al terminar; consulta
  `GET /api/v1/billing/balance` antes de un lote grande.
- El archivo OpenAPI es el contrato: cada operación indica su ámbito requerido en
  `x-required-scope`.

## Preguntas

### ¿Cómo hago subtítulos SRT de un vídeo con un agente de IA?

Sube el archivo, crea un trabajo de transcripción, espera a `succeeded` y llama al endpoint
de exportación con `format=srt`. Los subtítulos requieren el plan Plus o superior.

### ¿Pueden Claude Code o Codex llamar a la API de KolWrite?

Sí. La llaman por HTTPS con un token de acceso personal tomado de una variable de entorno.
No hay SDK ni servidor MCP que instalar.

### ¿Cómo obtiene un agente las marcas de tiempo por palabra?

Exporta el trabajo como JSON. Cada segmento incluye `words` con `start` y `end` en segundos.
