@afterpack/core y cada plugin de framework ya hablan este protocolo en cuanto hay una clave Pro 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, condetails.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 | 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. |
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.
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:
{
"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, incluidas preset, seed y 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.
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.jsRespuesta: 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:
- Una parte
obfuscation-resultpor archivo:{ version, filePath, status, output, outputMap, processingManifest }. Si falla,outputyoutputMapsonnullyprocessingManifest.diagnosticsexplica el motivo. - 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=.
{
"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.
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. 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 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. |
DIAG_API_SUNSET | 410 | Esta versión de la API está retirada. Consulta Compatibilidad de clientes. |
DIAG_ENTITLEMENT_LAPSED | 402 | Pago de la suscripción vencido. Incluye graceUntil y upgradeUrl. |
DIAG_QUOTA_EXHAUSTED | 402 | La asignación + créditos (+ el tope de exceso, 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 en lugar de reintentarlos indefinidamente.
Ciclo de vida de la clave, desde el lado del cliente
Emitir, restringir y revocar son operaciones del panel. 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
401solo 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_SCOPEcuando se usa desde cualquier otro sitio, distinto del401que recibe una clave inválida o revocada, para que un cliente pueda distinguir "clave equivocada" de "clave correcta, lugar equivocado".
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.
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 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. Todos los detalles, en Privacidad y tratamiento de datos.
Siguiente
- Modos de despliegue: dónde encaja este protocolo frente a un build Free local.
- Pro: qué añade Pro sobre el motor local.
- Privacidad y tratamiento de datos: qué se guarda, durante cuánto tiempo y quién puede leerlo.
- Niveles: asignaciones de MB, exceso y qué desbloquea cada plan frente a esta API.
- Claves de API: emitir, restringir, rotar y revocar la credencial con la que se autentica esta API.
- Referencia de la CLI: el cliente oficial que implementa estas reglas.
- Configuración: la configuración del motor que lleva el campo
config.