# Usar la API

**La API deja que otra aplicación haga por ti lo mismo que haces en el tablero: publicar, reemplazar una versión, compartir, quitar el acceso, archivar y ver quién abrió. Trabaja a tu nombre, con lo que le permitas, y te avisa por correo de lo delicado.**

## Tu llave

En **Cuenta → API**, **Crear llave**: ponle un nombre que te diga dónde la vas a usar («Prospector», «Claude Code») y marca lo que puede hacer. Para una integración que solo consulta, deja marcada solo la primera casilla.

| Permiso | Qué deja hacer |
|---|---|
| `documentos:leer` | Ver la lista y el detalle, con los correos de las personas a las que compartiste |
| `documentos:publicar` | Publicar documentos y reemplazar versiones |
| `documentos:compartir` | Compartir a tu nombre y recibir las ligas |
| `documentos:revocar` | Quitarle el acceso a una persona |
| `documentos:archivar` | Quitarle el acceso a todos y archivar; te avisamos y lo puedes deshacer |

Tres cosas que conviene saber:

- **La llave se muestra una sola vez.** Si la pierdes, revócala y crea otra.
- **Para crearla tienes que haber entrado hace poco.** Si tu sesión es de hace días, te mandamos una liga a tu correo y la creas desde esa sesión nueva. Así, alguien que tuviera tu sesión abierta en otro lado no puede fabricarse una.
- **Actúa como tú, sobre todos los documentos de tu cuenta**, también los que publicó otra persona de tu equipo. No puede administrar la cuenta ni crear otras llaves. Vence a los 12 meses.

Cada vez que se crea o se revoca una llave te llega un correo. Si recibes uno que no esperabas, revócala y en **Cuenta** usa **Cerrar mis otras sesiones**.

## Probarla

La llave va en la cabecera `Authorization`:

```bash
curl https://api.cartaventa.com/v1/yo -H "Authorization: Bearer cv_live_…"
```

Responde quién eres, tu cuenta y los permisos de la llave. Si da `401`, la llave está mal copiada, se revocó o venció.

## Las rutas

| Ruta | Permiso | Qué hace |
|---|---|---|
| `GET /v1/yo` | leer | Quién es esta llave |
| `GET /v1/documentos` | leer | Tus documentos, de 20 en 20 (`?estado=activo\|archivado\|todos&limite=…&cursor=…`) |
| `POST /v1/documentos` | publicar | Publica un documento nuevo; responde `201` |
| `GET /v1/documentos/{id}` | leer | El detalle: versiones, y cada persona con su estado y sus aperturas |
| `POST /v1/documentos/{id}/versiones` | publicar | Versión nueva, misma liga |
| `POST /v1/documentos/{id}/compartir` | compartir | Comparte y devuelve la liga de cada persona |
| `POST /v1/documentos/{id}/revocar` | revocar | Le quita el acceso a una persona |
| `POST /v1/documentos/{id}/revocar-todos` | archivar | Le quita el acceso a todos |
| `POST /v1/documentos/{id}/archivar` | archivar | Archiva el documento |

`{id}` es el `public_id` que devuelven publicar y la lista; es el mismo de la liga del documento. Las fechas salen en UTC con `Z`, y lo que lista viene en `{ datos, siguiente_cursor }`: con `siguiente_cursor` en `null` ya no hay más.

## Publicar y reemplazar

El HTML sigue las mismas reglas que en el tablero ([Cómo debe ser el HTML](/guias/como-debe-ser-el-html)), pasa por la misma moderación y comparte el tope de 30 publicaciones por hora. Se manda de una de tres formas:

**El archivo tal cual**, hasta 5 MB, con el título en la dirección. Es la forma para archivos grandes:

```bash
curl -X POST "https://api.cartaventa.com/v1/documentos?titulo=Propuesta%20Acme" \
  -H "Authorization: Bearer cv_live_…" \
  -H "Content-Type: text/html" \
  --data-binary @propuesta.html
```

**Como formulario**, con los campos `titulo` y `archivo`, también hasta 5 MB:

```bash
curl -X POST https://api.cartaventa.com/v1/documentos \
  -H "Authorization: Bearer cv_live_…" \
  -F titulo="Propuesta Acme" -F archivo=@propuesta.html
```

**En JSON**, `{ "titulo": "…", "html": "…" }`, hasta 512 KB. Es lo cómodo si el HTML ya está en una variable.

En los tres puedes pedir `restriccion`: `estandar` (la de siempre) o `estricta`, donde cada persona entra con un código que le llega a su correo. Para integraciones recomendamos estricta: la liga que te devuelve compartir solo identifica a la persona, no abre por sí sola. Va en la dirección (`&restriccion=estricta`), como campo del formulario o en el JSON.

Para reemplazar, lo mismo contra `/v1/documentos/{id}/versiones`, sin título. Responde la versión nueva y cuántas personas verán el cambio la próxima vez que abran.

## Compartir

```bash
curl -X POST https://api.cartaventa.com/v1/documentos/{id}/compartir \
  -H "Authorization: Bearer cv_live_…" \
  -H "Content-Type: application/json" \
  -d '{"destinatarios": [{"nombre": "Juan Pérez", "correo": "juan@acme.com"}], "mensaje": "Te comparto la propuesta."}'
```

- Cada destinatario lleva `nombre`, `correo` o `telefono` (su WhatsApp), o varios. En un documento estricto el correo es obligatorio.
- Una persona que no cumple no tira la llamada: va a `invalidos` con su posición, su código y el motivo, y las demás se comparten.
- Con `"enviar_correo": false` no le mandamos nada a nadie: tu aplicación recibe las ligas y las manda por su lado.
- La respuesta trae `creados`, `repetidos`, `invalidos` y `fallidos`, y **cada persona con su liga**. Es la única respuesta de la API que trae ligas; guárdalas como guardas una contraseña.
- `repetidos` son personas que ya tenían acceso: no les llega otro correo, pero reciben una liga nueva para que siempre tengas la vigente. La que le habías pedido antes a la API deja de servir; las del correo y las que copiaste en el tablero, no.
- `fallidos` son personas que quedaron dadas de alta pero cuyo correo no salió: su liga viene ahí para que la mandes tú.

Topes, contra un asistente engañado por un correo malicioso: **5 personas y 2 empresas distintas (dominios de correo) por llamada; 20 personas al día por usuario y 60 por cuenta**. Cuentan también las repetidas y las que no traen correo. Para más, compártelo desde el tablero.

Cada llamada que comparte te manda **un correo** con a quién se compartió. Si no fuiste tú, revoca la llave.

## Quitar el acceso y archivar

- `POST …/revocar` con `{ "destinatario_id": "…" }` (el de la respuesta de compartir o del detalle) o `{ "correo": "…" }`. Repetirlo no hace daño. Una misma llave le quita el acceso a máximo 10 personas por hora.
- `POST …/revocar-todos` le quita el acceso a todos los que lo tenían.
- `POST …/archivar` apaga el documento; si ya estaba archivado, responde igual.

**Revocar a todos y archivar se pueden deshacer desde el correo**: en cuanto pasan te llega un aviso con un botón **Deshacer** que funciona sin iniciar sesión, durante 72 horas (revocar a todos) o 7 días (archivar). Devuelve el acceso con las mismas ligas de antes y a tus clientes no les llega nada. Por la API no hay desarchivar: eso se hace en el tablero.

Si una llave hace cuatro de estas cosas en 24 horas —archivar, quitarle el acceso a todos o a una persona—, en vez de un correo por cada una te llega uno solo con un botón que **deshace todo lo que hizo esa llave desde entonces y la corta**.

Si el documento es de otra persona de tu equipo, a ella le llega además su propio aviso, con su propio botón **Deshacer**, con la misma regla: tres sueltos y, a la cuarta, uno solo que deshace todo lo de sus documentos. Su botón sigue sirviendo aunque desactiven a quien creó la llave, y cuando alguien deshace, la confirmación les llega a los dos.

## Reintentos

Repetir una escritura no la hace dos veces. Si la misma llave manda **la misma petición** dentro de 60 segundos, sin otra escritura en medio, recibe la respuesta original —con las mismas ligas, sin volver a mandar correos— y la cabecera `Idempotent-Replayed: true`. Quitar el acceso, revocar a todos y archivar no necesitan esa ventana: repetirlos ya no cambia nada. Para reintentar más tarde sin duplicar, manda tu propia clave:

```bash
-H "Idempotency-Key: compartir-propuesta-acme-juan"
```

Con ella la misma operación se reconoce durante 24 horas. Usa una clave distinta para cada operación distinta: la misma clave con otro cuerpo da error.

## Límites

| Qué | Límite |
|---|---|
| Consultas (`GET`) | 120 por minuto por llave |
| Publicar o reemplazar | 30 por hora por usuario, en cualquier superficie |
| Compartir | 5 personas y 2 dominios por llamada; 20 al día por usuario, 60 por cuenta |
| Cambios de cualquier tipo (`POST`) | 60 por minuto por llave |
| Quitarle el acceso a una persona | 10 por hora por llave |
| Llaves | 10 vivas y 5 creadas por hora por usuario |

Al topar, `429` con `limite_ritmo`, el nombre del límite y, cuando se sabe, la cabecera `Retry-After`.

## Avisos que te llegan

Además de los de compartir y de archivar o revocar, te escribimos si una llave abre el detalle de **más de 25 documentos en una hora**: barrer el catálogo es la firma de un asistente al que le metieron instrucciones. Si es lo que le pediste, no hagas nada.

Si tu cuenta no debe sacar los correos de tus clientes hacia otras aplicaciones, el propietario puede encender en **Cuenta → API** «No exponer correos de destinatarios por API»: el detalle los muestra como `j***@acme.com` y sin nombre.

## Errores

Todo error llega en `application/problem+json`:

```json
{
  "type": "https://cartaventa.com/guias/usar-la-api#limite_ritmo",
  "title": "Se topó un límite de ritmo",
  "status": 429,
  "codigo": "limite_ritmo",
  "mensaje": "Subiste 30 documentos o versiones en la última hora…",
  "limite": "publicar_por_hora"
}
```

`codigo` no cambia nunca; el texto de `mensaje` sí puede cambiar. Los datos para corregirse, como la regla de moderación o el límite, vienen junto, al mismo nivel.

### `moderacion_bloqueada`

La moderación bloqueó el documento. `reglas` dice cuál regla disparó y por qué; corrige el HTML y vuelve a mandarlo. Ver [Cuando un documento no se publica](/guias/cuando-un-documento-no-se-publica).

### `html_invalido`

No es un HTML que Cartaventa acepte: no es HTML, trae algo prohibido o una imagen rota. El mensaje dice qué.

### `html_demasiado_grande`

El documento pesa demasiado. Por JSON caben 512 KB; como archivo o formulario, 5 MB, y lo que se guarda, ya con las imágenes comprimidas, tampoco pasa de 5 MB.

### `cuota_paginas_activas`

Tu plan llegó a su límite de documentos compartidos. Archiva uno que ya no uses o sube de plan; lo ya compartido sigue abriendo.

### `slug_sin_confirmar`

La cuenta todavía no confirma su subdominio. Entra al tablero una vez y confírmalo.

### `correo_invalido`

Ese correo no parece válido. Al quitar el acceso, `"todos"` también da este error: para eso está `revocar-todos`.

### `demasiados_destinatarios`

Más de 5 personas en una sola llamada. Pártelas, o compártelo desde el tablero.

### `demasiados_dominios`

Personas de más de 2 empresas distintas en una sola llamada. Pártelas por empresa.

### `correo_requerido_en_estricta`

El documento es estricto y esa persona no trae correo: sin él no le puede llegar su código.

### `sin_datos`

Esa persona no trae ni nombre, ni correo, ni WhatsApp, o no viene como objeto.

### `telefono_invalido`

Ese WhatsApp no parece un número: 10 dígitos, o el número completo con su lada (`+52…`).

### `documento_archivado`

El documento está archivado y esa acción no aplica. Desarchívalo en el tablero.

### `documento_no_encontrado`

Ese documento no existe, es de otra cuenta, o está archivado y la acción no aplica a archivados. Las tres respuestas son idénticas a propósito.

### `destinatario_no_encontrado`

Esa persona no está entre los destinatarios del documento.

### `limite_ritmo`

Se topó un límite; `limite` dice cuál y `Retry-After`, cuándo reintentar. Ver [Límites](#limites).

### `organizacion_suspendida`

La cuenta está suspendida. Escríbenos.

### `entrada_invalida`

Falta un dato o no tiene la forma esperada; `campo` dice cuál. También si el cuerpo no es JSON válido.

### `no_autenticado`

Falta la llave, está mal copiada, se revocó o venció. Siempre la misma respuesta, sin decir cuál de las cuatro.

### `permiso_insuficiente`

La llave no tiene el permiso de esa ruta; la cabecera `WWW-Authenticate` dice cuál falta. Crea otra llave con ese permiso.

### `ruta_no_encontrada`

Esa ruta o ese método no existen. Las rutas viven bajo `/v1`.

### `error_interno`

Algo falló de nuestro lado y ya quedó registrado. Reintenta en un momento; si sigue, escríbenos con la hora.

### `tipo_no_soportado`

Ese `Content-Type` no se acepta en esa ruta. Publicar y reemplazar aceptan `text/html`, `multipart/form-data` o `application/json`; compartir y revocar, `application/json`.

### `cuerpo_demasiado_grande`

El cuerpo pasa de 512 KB en una ruta que no recibe documentos.

### `solicitud_en_curso`

Esa misma petición todavía se está procesando. Espera unos segundos y reintenta: recibirás el resultado de la primera.

### `clave_idempotencia_reusada`

Esa `Idempotency-Key` ya se usó con otra petición. Usa una clave nueva para cada operación distinta.

¿Algo que no cuadra? Escríbenos por [WhatsApp](https://wa.me/message/ETAWOVY3AKNVK1).
