# Referencia de la API en la nube de AfterPack

El contrato de red de api.afterpack.dev: autenticación, el endpoint de ofuscación por lotes, límites, límites de tasa, el presupuesto de tiempo del build y los códigos de diagnóstico que un cliente tiene que manejar.

Source: https://www.afterpack.dev/es/docs/cloud-api

`@afterpack/core` y cada [plugin de framework](https://www.afterpack.dev/es/docs/frameworks) ya hablan este protocolo en cuanto hay una [clave Pro](https://www.afterpack.dev/es/docs/dashboard/api-keys) configurada: autenticación, streaming multipart, reintentos, todo. Lee esto cuando conectes una integración de CI propia, escribas un cliente fuera de los paquetes oficiales o depures una respuesta que tus herramientas no esperaban.

## URL base y versionado

URL base: **`https://api.afterpack.dev/v1/`**

La API se versiona en la URL y `v1` es la única versión. Dentro de ella, los campos de respuesta se añaden, nunca se eliminan ni cambian de significado; un cambio incompatible saldría como `v2`, en paralelo. `GET /v1/version` devuelve la versión desplegada y el entorno, y `GET /` es una comprobación de disponibilidad sin autenticación.

## Compatibilidad de clientes

Dos cabeceras de solicitud opcionales, ambas **ignoradas, nunca un `400`**, cuando faltan o están mal formadas; un script de curl, o un cliente anterior a estas cabeceras, no se ve afectado en ningún caso:

| Cabecera | Contiene |
|---|---|
| `X-AfterPack-Client-Version` | La versión npm instalada de `@afterpack/core`, para un cliente construido sobre él. |
| `X-AfterPack-Client` | El paquete que llama y su versión, p. ej. `afterpack/0.1.0` o `@afterpack/vite/0.1.0`. |

La comparación se hace solo en el servidor, sobre `MAJOR.MINOR.PATCH`, ignorando cualquier sufijo de prerelease (`0.1.0-rc.3` se lee como `0.1.0`); una versión que este servidor no puede interpretar, o la ausencia de cabecera, se acepta siempre. Cada respuesta lleva `X-AfterPack-Min-Client-Version` mientras haya un mínimo configurado, así que un script puede comprobar su propia situación sin provocar un rechazo. Se comprueba justo después de autenticar la clave, antes de leer el cuerpo de la solicitud:

- Por debajo del mínimo configurado: `426 DIAG_CLIENT_UPGRADE_REQUIRED`, con `details.minVersion`.
- Durante una ventana de retirada: `410 DIAG_API_SUNSET`.

Ninguno de los dos está configurado por defecto. Un espacio de trabajo solo los ve cuando AfterPack fija una versión mínima o una fecha de retirada para esta API.

La respuesta también puede llevar un array `notices` de nivel superior en un `batch-summary` correcto, en cualquier cuerpo de error y en el `202` de telemetría del nivel Free: `{severity, code, message, url?}`, como máximo 5, cada `message` limitado a 500 caracteres. Una `severity` desconocida se lee como advertencia; no hay nada que hacer aparte de mostrarla.

## Autenticación

Dos credenciales, y no son intercambiables.

| Credencial | Cabecera | A qué da acceso |
|---|---|---|
| **Una clave de API Pro** | `Authorization: Bearer ap_v1_<...>` | `POST /v1/obfuscate/batch` y `GET /v1/usage`. La clave deduce su propio espacio de trabajo, así que no se envía ningún id de espacio de trabajo. |
| **Una sesión del panel** | La sesión que conserva tu navegador después de [iniciar sesión](https://www.afterpack.dev/es/docs/dashboard) | Todo lo que *gestiona* una cuenta: crear, listar y revocar claves, planes, facturación, miembros, proyectos. Requiere un `workspaceId` explícito y un [rol en el espacio de trabajo](https://www.afterpack.dev/es/docs/dashboard/workspaces). |

Una clave es una credencial de build. Ofusca e informa de su propio uso, y no puede emitir otra clave, leer la facturación ni modificar un espacio de trabajo. Esa contención es precisamente la idea: una clave guardada en CI puede gastar tu asignación, y nada más.

Las claves son `ap_v1_` seguido de 40 caracteres base62. Una clave se **muestra exactamente una vez**, al crearla. En el servidor solo se guarda un hash HMAC-SHA256 con sal, así que una clave en claro nunca se puede recuperar después, solo revocar. Emite una en el [panel](https://www.afterpack.dev/es/docs/dashboard/api-keys).

*(Pro cloud build request-flow diagram — see the web page)*

## Endpoints

### `POST /v1/obfuscate/batch`

El único endpoint de ofuscación. Una llamada es un build. Los resultados llegan en streaming a medida que termina cada archivo.

**Solicitud**: `multipart/form-data`.

| Parte | Obligatoria | Descripción |
|---|---|---|
| `batch` | Sí | El manifiesto del lote, como parte de tipo **cadena** JSON. |
| una parte por archivo | Sí | El código fuente JS, bajo el `partName` que declara su entrada del manifiesto. |
| una parte por source map | Opcional | Un source map previo, bajo el `sourceMapPartName` de esa entrada. |

El manifiesto es:

```jsonc
{
  "version": "1",
  "files": [
    {
      "filePath": "assets/app-a1b2c3.js",  // echoed back on the result part
      "partName": "file0",                 // names the form part carrying the source
      "sourceMapPartName": "map0",         // optional
      "config": { "regions": [ /* ... */ ] } // optional, per-file
    }
  ],
  "config": { "preset": "hard", "seed": 42 }, // batch-level EngineConfig
  "git": { "commitSha": "…", "ref": "…" }     // optional build attribution
}
```

El `config` a nivel de lote es un objeto de configuración del motor: cada clave de [Configuración](https://www.afterpack.dev/es/docs/config), incluidas [`preset`](https://www.afterpack.dev/es/docs/config#preset), [`seed`](https://www.afterpack.dev/es/docs/config#seed) y [`regions`](https://www.afterpack.dev/es/docs/config#regions). `filePath` y `git` son campos del manifiesto; consulta la forma del manifiesto de arriba.

`filePath` no puede contener comillas ni caracteres CR/LF. Se copia tal cual en el `Content-Disposition` de la parte de resultado, y una ruta mal formada se rechaza.

```bash
curl https://api.afterpack.dev/v1/obfuscate/batch \
  -H "Authorization: Bearer ap_v1_xxx" \
  -F 'batch={"version":"1","files":[{"filePath":"dist/app.js","partName":"file0"}],"config":{"preset":"hard"}}' \
  -F file0=@dist/app.js
```

**Respuesta**: `200` con un cuerpo `multipart/mixed` en streaming. Las partes llegan a medida que terminan los archivos, así que escribe cada una a disco en cuanto llegue en lugar de acumular el lote en memoria:

1. Una parte **`obfuscation-result`** por archivo: `{ version, filePath, status, output, outputMap, processingManifest }`. Si falla, `output` y `outputMap` son `null` y `processingManifest.diagnostics` explica el motivo.
2. Una parte final **`batch-summary`**: `{ version, totalFiles, successCount, failureCount, engineVersion, results, totalDurationMs, totalInputBytes, totalOutputBytes }`.

El fallo de un archivo no aborta el lote. Todos los demás archivos siguen llegando con normalidad, y el resumen informa del reparto.

### `GET /v1/usage`

Uso y asignación del espacio de trabajo. Es la única lectura de tipo gestión que puede hacer una clave, para que un job de CI pueda comprobar su asignación restante. Acepta **o bien** una clave de API Pro (que deduce su propio espacio de trabajo) **o bien** una sesión del panel más `?workspaceId=`.

```jsonc
{
  "tier": "indie",
  "workspace": { "id": "…", "name": "…" },
  "allowance": { "remainingMb": 412.5, "totalMb": 500, "resetAt": "…" },
  "credits": { "balanceMb": 20 },
  "subscription": { "status": "active", "plan": "indie_monthly", "renewsAt": "…", "graceUntil": null, "lapsed": false },
  "recentBuilds": [
    {
      "buildId": "…", "builtAt": "…", "fileCount": 61,
      "totalBytes": 3145728, "outputBytes": 9437184,
      "meteredMb": 3.0, "allowanceMb": 3.0, "creditMb": 0, "overageMb": 0,
      "projectId": "…", "cloudMs": 39120
    }
  ]
}
```

`outputBytes` y `cloudMs` son `null` cuando se desconocen, nunca `0` como sustituto. `allowanceMb` / `creditMb` / `overageMb` son el reparto de `meteredMb` entre las tres bolsas.

## Límites

| Límite | Valor |
|---|---|
| Por archivo | 50 MB |
| Archivos por lote | 200 |
| Bytes totales del lote | 500 MB |

Si se supera cualquiera de ellos, la solicitud se rechaza entera con `413` y `DIAG_FILE_TOO_LARGE` o `DIAG_BATCH_TOO_LARGE`, antes de medir nada. Las mismas cifras, desde el lado del panel, están en [Claves de API](https://www.afterpack.dev/es/docs/dashboard/api-keys).

## Límites de tasa

Se cuentan sobre una ventana móvil de **60 segundos**, por clave y por IP de cliente:

| Ámbito | Límite |
|---|---|
| Por clave, espacio de trabajo gratuito | 10 solicitudes / min |
| Por clave, espacio de trabajo de pago | 100 solicitudes / min |
| Por IP de cliente | 100 solicitudes / min |

Superar cualquiera de los dos ámbitos devuelve `429` con `DIAG_RATE_LIMITED` y una cabecera `Retry-After` con el tiempo que falta hasta que la ventana que lo provocó se vacíe de verdad. Si la propia comprobación del límite de tasa no puede ejecutarse, la solicitud falla de forma segura (fail closed) con `503 DIAG_RATE_CHECK_UNAVAILABLE` y `Retry-After: 5`.

## El presupuesto de tiempo del build

Todo un bundle se ofusca dentro de una sola invocación, con un **límite de CPU de 120 segundos**. Dos presupuestos de tiempo real mantienen el build dentro de ese límite.

| Transcurrido | Qué pasa |
|---|---|
| **45 s** | Se detiene la captura del [Protection Map](https://www.afterpack.dev/es/docs/protection-map). La ofuscación continúa hasta el final; el mapa guardado se marca como parcial con el motivo de omisión `deadline`. |
| **90 s** | No se *empieza* ningún archivo más. Cada archivo restante devuelve un fallo explícito con `DIAG_BUILD_DEADLINE` que indica cuántos de N se completaron, y el stream se cierra con normalidad. |

Si alcanzas el segundo presupuesto, divide el lote.

## Códigos de diagnóstico

Los errores llevan un `code` estable junto al estado HTTP, en el mismo espacio de nombres `DIAG_*` que los [diagnósticos](https://www.afterpack.dev/es/docs/diagnostics) del propio motor. Los que un cliente tiene que manejar:

| Código | Estado | Significado |
|---|---|---|
| `DIAG_INVALID_KEY` | 401 | Clave ausente o no reconocida. |
| `DIAG_KEY_REVOKED` | 401 | La clave fue revocada. |
| `DIAG_KEY_EXPIRED` | 401 | La clave ha superado su `expiresAt`. |
| `DIAG_KEY_OUT_OF_SCOPE` | 403 | Clave válida, usada desde una IP fuera de su lista `allowedIPs`. |
| `DIAG_CLIENT_UPGRADE_REQUIRED` | 426 | Cliente por debajo de la versión mínima configurada. Incluye `details.minVersion`. Consulta [Compatibilidad de clientes](#client-compatibility). |
| `DIAG_API_SUNSET` | 410 | Esta versión de la API está retirada. Consulta [Compatibilidad de clientes](#client-compatibility). |
| `DIAG_ENTITLEMENT_LAPSED` | 402 | Pago de la suscripción vencido. Incluye `graceUntil` y `upgradeUrl`. |
| `DIAG_QUOTA_EXHAUSTED` | 402 | La [asignación + créditos](https://www.afterpack.dev/es/docs/dashboard/usage) (+ el [tope de exceso](https://www.afterpack.dev/es/docs/tiers), si está activado) no cubren este build. Incluye `remainingMb`, `requestedMb` y un `reason` que distingue "sin cuota" de "tope de exceso alcanzado". |
| `DIAG_RATE_LIMITED` | 429 | Consulta los límites de tasa más arriba. Incluye `retryAfterSeconds`. |
| `DIAG_RATE_CHECK_UNAVAILABLE` | 503 | Falló la comprobación del límite de tasa; falla de forma segura. Reintenta. |
| `DIAG_INVALID_MANIFEST` | 400 | Cuerpo multipart mal formado, falta la parte `batch`, o un manifiesto que no pasa la validación. |
| `DIAG_FILE_TOO_LARGE` | 413 | Un archivo de más de 50 MB. |
| `DIAG_BATCH_TOO_LARGE` | 413 | Más de 200 archivos o más de 500 MB en total. |
| `DIAG_BUILD_DEADLINE` | n/a | Por archivo, dentro de un stream `200`. Al build se le agotó el presupuesto de tiempo antes de empezar este archivo. |
| `DIAG_ENGINE_FAILURE` | n/a | Por archivo, dentro de un stream `200`. El motor lanzó un error con este archivo. |

Los dos últimos **no** son estados HTTP. Llegan como `status: "failure"` en una parte `obfuscation-result` dentro de una respuesta correcta. Un cliente que solo mira el estado HTTP confundirá un build con fallos parciales con uno limpio.

## Reintentos y backoff

| Estado | Reintentable | Comportamiento |
|---|:---:|---|
| `400`, `401`, `402`, `403`, `413` | No | Corrige la solicitud. |
| `429` | Una vez | Respeta `Retry-After` exactamente, con un tope de 120 s; un segundo `429` falla de forma segura. |
| `500` | No | Bug del motor; repórtalo. |
| `503`, `504` | Sí | 1 s, 4 s, 16 s (tres reintentos, cuatro intentos en total) y después falla de forma segura. |

El cliente oficial aplica además un timeout de 120 segundos a toda la solicitud, y trata los fallos de DNS, TLS y de conexión reiniciada como [nube inalcanzable](https://www.afterpack.dev/es/docs/deployment-modes) en lugar de reintentarlos indefinidamente.

## Ciclo de vida de la clave, desde el lado del cliente

Emitir, restringir y revocar son [operaciones del panel](https://www.afterpack.dev/es/docs/dashboard/api-keys). Dos comportamientos importan al código que llama a esta API:

- **Revocación a mitad de build.** Una clave revocada mientras un lote está en streaming devuelve `401` solo en la *siguiente* solicitud; lo que ya está en curso termina con normalidad.
- **Una clave restringida a una lista de IP** devuelve `403 DIAG_KEY_OUT_OF_SCOPE` cuando se usa desde cualquier otro sitio, distinto del `401` que recibe una clave inválida o revocada, para que un cliente pueda distinguir "clave equivocada" de "clave correcta, lugar equivocado".

> **La medición es atómica y va primero**
>
> Un lote que no cabe en las bolsas del espacio de trabajo se rechaza entero *antes* de despachar ningún archivo, con las bolsas intactas: nunca procesado a medias, nunca facturado a medias. Es la forma a nivel de lote de la [regla de fallo seguro](https://www.afterpack.dev/es/docs/diagnostics#fail-closed-no-exceptions).

## Tratamiento del código fuente

El código fuente enviado para ofuscar se procesa **solo en memoria**, durante esa única solicitud, y nunca se escribe en disco, en logs ni en un índice de búsqueda. Los logs contienen ids de solicitud, recuentos de archivos, totales de bytes y códigos de diagnóstico, nunca contenido de archivos, nombres de identificadores ni valores de constantes.

Un artefacto sobrevive a la solicitud: el [Protection Map](https://www.afterpack.dev/es/docs/protection-map) que se guarda por build para el panel. Contiene tu código fuente original, coloreado según cuánta transformación recibió cada token. Está activado por defecto en cada proyecto, solo lo pueden leer los miembros del espacio de trabajo propietario, se conserva un año **y** los últimos 500 builds por proyecto (lo que se agote antes) y se puede desactivar en los [ajustes del proyecto](https://www.afterpack.dev/es/docs/dashboard/workspaces). Todos los detalles, en [Privacidad y tratamiento de datos](https://www.afterpack.dev/es/docs/privacy).

## Siguiente

- [Modos de despliegue](https://www.afterpack.dev/es/docs/deployment-modes): dónde encaja este protocolo frente a un build Free local.
- [Pro](https://www.afterpack.dev/es/docs/pro): qué añade Pro sobre el motor local.
- [Privacidad y tratamiento de datos](https://www.afterpack.dev/es/docs/privacy): qué se guarda, durante cuánto tiempo y quién puede leerlo.
- [Niveles](https://www.afterpack.dev/es/docs/tiers): asignaciones de MB, exceso y qué desbloquea cada plan frente a esta API.
- [Claves de API](https://www.afterpack.dev/es/docs/dashboard/api-keys): emitir, restringir, rotar y revocar la credencial con la que se autentica esta API.
- [Referencia de la CLI](https://www.afterpack.dev/es/docs/cli): el cliente oficial que implementa estas reglas.
- [Configuración](https://www.afterpack.dev/es/docs/config): la configuración del motor que lleva el campo `config`.
