# Довідник AfterPack Cloud API

Протокол взаємодії з api.afterpack.dev: автентифікація, ендпоінт пакетної обфускації, обмеження, ліміти запитів, часовий бюджет збірки та діагностичні коди, які має обробляти клієнт.

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

`@afterpack/core` і кожен [плагін для фреймворку](https://www.afterpack.dev/uk/docs/frameworks) вже працюють за цим протоколом, щойно налаштовано [ключ Pro](https://www.afterpack.dev/uk/docs/dashboard/api-keys): автентифікація, потокова передача multipart, повторні спроби, усе це. Читайте цю сторінку, коли підключаєте власну інтеграцію з CI, пишете клієнт поза офіційними пакетами або налагоджуєте відповідь, якої ваш інструментарій не очікував.

## Базовий URL і версіонування

Базовий URL: **`https://api.afterpack.dev/v1/`**

API версіонується через URL, і `v1` є єдиною версією. У її межах поля відповіді додаються, але ніколи не видаляються і не змінюють призначення; несумісна зміна вийшла б як `v2` поруч із нею. `GET /v1/version` повертає розгорнуту версію та середовище, а `GET /` є перевіркою доступності без автентифікації.

## Сумісність клієнтів

Два необов'язкові заголовки запиту; якщо їх немає або вони некоректні, вони **ігноруються, а не спричиняють `400`**. Скрипт на curl або клієнт, старший за ці заголовки, це ніяк не зачіпає:

| Заголовок | Що передає |
|---|---|
| `X-AfterPack-Client-Version` | Встановлену npm-версію `@afterpack/core` для клієнта, побудованого на ньому. |
| `X-AfterPack-Client` | Пакет, що викликає API, і його версію, наприклад `afterpack/0.1.0` або `@afterpack/vite/0.1.0`. |

Порівняння відбувається лише на сервері, за `MAJOR.MINOR.PATCH`, без урахування суфікса пререлізу (`0.1.0-rc.3` читається як `0.1.0`); версію, яку сервер не може розібрати, або відсутність заголовка завжди приймають. Поки мінімальну версію налаштовано, кожна відповідь містить `X-AfterPack-Min-Client-Version`, тож скрипт може перевірити свій стан, не наражаючись на відмову. Перевірка відбувається одразу після автентифікації ключа, до читання тіла запиту:

- Нижче налаштованого мінімуму: `426 DIAG_CLIENT_UPGRADE_REQUIRED` із `details.minVersion`.
- Під час періоду виведення з експлуатації: `410 DIAG_API_SUNSET`.

За замовчуванням жодне з них не налаштовано. Робочий простір побачить їх лише тоді, коли AfterPack задасть мінімальну версію або дату виведення з експлуатації для цього API.

Відповідь також може містити масив `notices` верхнього рівня: в успішному `batch-summary`, у будь-якому тілі помилки та у відповіді `202` телеметрії тарифу Free. Формат `{severity, code, message, url?}`, не більше 5 елементів, кожне `message` обмежене 500 символами. Невідоме значення `severity` трактується як попередження; окрім показу, робити з ним нічого не потрібно.

## Автентифікація

Два види облікових даних, і вони не взаємозамінні.

| Облікові дані | Заголовок | До чого дають доступ |
|---|---|---|
| **API-ключ Pro** | `Authorization: Bearer ap_v1_<...>` | `POST /v1/obfuscate/batch` і `GET /v1/usage`. Ключ сам визначає свій робочий простір, тож ідентифікатор робочого простору не надсилається. |
| **Сесія дашборду** | Сесія, яку тримає ваш браузер після [входу](https://www.afterpack.dev/uk/docs/dashboard) | Усе, що *керує* обліковим записом: створення, перегляд і відкликання ключів, плани, оплата, учасники, проєкти. Потребує явного `workspaceId` і [ролі в робочому просторі](https://www.afterpack.dev/uk/docs/dashboard/workspaces). |

Ключ є обліковими даними для збірки. Він обфускує та звітує про власне використання і не може створити інший ключ, прочитати дані про оплату чи змінити робочий простір. Саме в цьому обмеженні суть: ключ, що лежить у CI, може витрачати ваш ліміт, і нічого більше.

Ключі мають вигляд `ap_v1_`, за яким іде 40 символів base62. Ключ **показується рівно один раз**, під час створення. На сервері зберігається лише солений хеш HMAC-SHA256, тож сирий ключ пізніше неможливо відновити, лише відкликати. Створіть ключ у [дашборді](https://www.afterpack.dev/uk/docs/dashboard/api-keys).

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

## Ендпоінти

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

Єдиний ендпоінт обфускації. Один виклик дорівнює одній збірці. Результати повертаються потоком у міру завершення кожного файлу.

**Запит**: `multipart/form-data`.

| Частина | Обов'язкова | Опис |
|---|---|---|
| `batch` | Так | Маніфест пакета як **рядкова** JSON-частина. |
| по одній частині на файл | Так | Вихідний код JS під іменем `partName`, яке оголошує його запис у маніфесті. |
| по одній частині на source map | Необов'язкова | Вхідний source map під іменем `sourceMapPartName` цього запису. |

Маніфест має такий вигляд:

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

`config` рівня пакета є об'єктом конфігурації рушія: кожен ключ зі сторінки [Конфігурація](https://www.afterpack.dev/uk/docs/config), зокрема [`preset`](https://www.afterpack.dev/uk/docs/config#preset), [`seed`](https://www.afterpack.dev/uk/docs/config#seed) і [`regions`](https://www.afterpack.dev/uk/docs/config#regions). `filePath` і `git` є полями рівня маніфесту; див. структуру маніфесту вище.

`filePath` не може містити лапки або символи CR/LF. Його дослівно повертають у `Content-Disposition` частини з результатом, а некоректний шлях відхиляється.

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

**Відповідь**: `200` з потоковим тілом `multipart/mixed`. Частини надходять у міру завершення файлів, тож записуйте кожну на диск, щойно вона прийшла, а не буферизуйте весь пакет:

1. По одній частині **`obfuscation-result`** на файл: `{ version, filePath, status, output, outputMap, processingManifest }`. У разі помилки `output` і `outputMap` дорівнюють `null`, а `processingManifest.diagnostics` пояснює причину.
2. Завершальна частина **`batch-summary`**: `{ version, totalFiles, successCount, failureCount, engineVersion, results, totalDurationMs, totalInputBytes, totalOutputBytes }`.

Помилка в одному файлі не перериває пакет. Усі інші файли так само надходять потоком, а підсумок показує розподіл.

### `GET /v1/usage`

Використання та ліміт робочого простору. Єдине читання з категорії керування, доступне ключу, щоб завдання CI могло перевірити свій залишок ліміту. Приймає **або** API-ключ Pro (який сам визначає свій робочий простір), **або** сесію дашборду разом із `?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` і `cloudMs` дорівнюють `null`, коли значення невідоме, і ніколи не `0` як заглушка. `allowanceMb` / `creditMb` / `overageMb` показують, як `meteredMb` розподілено між трьома пулами.

## Обмеження

| Обмеження | Значення |
|---|---|
| На файл | 50 MB |
| Файлів у пакеті | 200 |
| Загальний обсяг пакета | 500 MB |

Якщо перевищено будь-яке з них, запит відхиляється повністю з `413` і `DIAG_FILE_TOO_LARGE` або `DIAG_BATCH_TOO_LARGE`, ще до будь-якого обліку. Ті самі числа з боку дашборду наведено в розділі [API-ключі](https://www.afterpack.dev/uk/docs/dashboard/api-keys).

## Ліміти запитів

Рахуються в ковзному вікні **60 секунд**, окремо для ключа та для IP-адреси клієнта:

| Область | Ліміт |
|---|---|
| На ключ, безкоштовний робочий простір | 10 запитів / хв |
| На ключ, платний робочий простір | 100 запитів / хв |
| На IP-адресу клієнта | 100 запитів / хв |

Перевищення будь-якої з областей повертає `429` з `DIAG_RATE_LIMITED` і заголовком `Retry-After`, що дорівнює часу, за який вікно, яке спрацювало, справді звільниться. Якщо саму перевірку ліміту неможливо виконати, запит зупиняється з помилкою (fail closed): `503 DIAG_RATE_CHECK_UNAVAILABLE` і `Retry-After: 5`.

## Часовий бюджет збірки

Увесь бандл обфускується в межах одного виклику з **лімітом CPU у 120 секунд**. Два бюджети реального часу утримують збірку в цьому ліміті.

| Минуло | Що відбувається |
|---|---|
| **45 s** | Збирання даних для [Protection Map](https://www.afterpack.dev/uk/docs/protection-map) припиняється. Обфускація триває до завершення; збережену мапу позначено як часткову з причиною пропуску `deadline`. |
| **90 s** | Жоден новий файл більше не *починається*. Для кожного файлу, що залишився, потоком надходить явна помилка `DIAG_BUILD_DEADLINE` із зазначенням, скільки з N завершено, і потік закривається штатно. |

Якщо ви досягли другого бюджету, розділіть пакет.

## Діагностичні коди

Помилки містять стабільний `code` разом зі статусом HTTP, у тому самому просторі імен `DIAG_*`, що й власна [діагностика](https://www.afterpack.dev/uk/docs/diagnostics) рушія. Ті, які має обробляти клієнт:

| Код | Статус | Значення |
|---|---|---|
| `DIAG_INVALID_KEY` | 401 | Ключ відсутній або не розпізнаний. |
| `DIAG_KEY_REVOKED` | 401 | Ключ відкликано. |
| `DIAG_KEY_EXPIRED` | 401 | Термін дії ключа (`expiresAt`) минув. |
| `DIAG_KEY_OUT_OF_SCOPE` | 403 | Дійсний ключ використано з IP-адреси поза його списком `allowedIPs`. |
| `DIAG_CLIENT_UPGRADE_REQUIRED` | 426 | Версія клієнта нижча за налаштований мінімум. Містить `details.minVersion`. Див. [Сумісність клієнтів](#client-compatibility). |
| `DIAG_API_SUNSET` | 410 | Цю версію API виведено з експлуатації. Див. [Сумісність клієнтів](#client-compatibility). |
| `DIAG_ENTITLEMENT_LAPSED` | 402 | Платіж за підписку прострочено. Містить `graceUntil` і `upgradeUrl`. |
| `DIAG_QUOTA_EXHAUSTED` | 402 | [Ліміт + кредити](https://www.afterpack.dev/uk/docs/dashboard/usage) (+ [ліміт перевищення](https://www.afterpack.dev/uk/docs/tiers), якщо увімкнено) не покривають цю збірку. Містить `remainingMb`, `requestedMb` і `reason`, що розрізняє «ліміт вичерпано» та «досягнуто ліміту перевищення». |
| `DIAG_RATE_LIMITED` | 429 | Див. ліміти запитів вище. Містить `retryAfterSeconds`. |
| `DIAG_RATE_CHECK_UNAVAILABLE` | 503 | Перевірка ліміту запитів не вдалася; зупинка з помилкою (fail closed). Повторіть спробу. |
| `DIAG_INVALID_MANIFEST` | 400 | Некоректне тіло multipart, відсутня частина `batch` або маніфест, що не пройшов валідацію. |
| `DIAG_FILE_TOO_LARGE` | 413 | Один файл понад 50 MB. |
| `DIAG_BATCH_TOO_LARGE` | 413 | Понад 200 файлів або понад 500 MB загалом. |
| `DIAG_BUILD_DEADLINE` | н/д | Для окремого файлу, усередині потоку `200`. Часовий бюджет збірки вичерпався до того, як цей файл було розпочато. |
| `DIAG_ENGINE_FAILURE` | н/д | Для окремого файлу, усередині потоку `200`. Рушій видав помилку на цьому файлі. |

Останні два **не є** статусами HTTP. Вони надходять як `status: "failure"` у частині `obfuscation-result` усередині успішної відповіді. Клієнт, який перевіряє лише статус HTTP, сприйме частково невдалу збірку за чисту.

## Повторні спроби та відступ

| Статус | Повторювати | Поведінка |
|---|:---:|---|
| `400`, `401`, `402`, `403`, `413` | Ні | Виправте запит. |
| `429` | Один раз | Дотримуйтеся `Retry-After` точно, не більше 120 s; другий `429` зупиняє з помилкою (fail closed). |
| `500` | Ні | Помилка рушія; повідомте про неї. |
| `503`, `504` | Так | 1 s, 4 s, 16 s (три повтори, усього чотири спроби), потім зупинка з помилкою (fail closed). |

Офіційний клієнт також застосовує 120-секундний тайм-аут на весь запит і трактує збої DNS, TLS і скидання з'єднання як [недоступність хмари](https://www.afterpack.dev/uk/docs/deployment-modes), а не повторює їх безкінечно.

## Життєвий цикл ключа з боку клієнта

Створення, обмеження та відкликання ключів є [операціями дашборду](https://www.afterpack.dev/uk/docs/dashboard/api-keys). Для коду, що викликає цей API, важливі дві особливості поведінки:

- **Відкликання посеред збірки.** Ключ, відкликаний під час потокової передачі пакета, повертає `401` лише на *наступний* запит; усе, що вже виконується, завершується штатно.
- **Ключ, обмежений списком IP-адрес**, повертає `403 DIAG_KEY_OUT_OF_SCOPE`, коли його використовують звідкись іще. Це відрізняється від `401`, який отримує недійсний або відкликаний ключ, тож клієнт може відрізнити «не той ключ» від «правильний ключ, не те місце».

> **Облік атомарний і виконується першим**
>
> Пакет, який не вміщається в пули робочого простору, відхиляється повністю *до* того, як буде відправлено будь-який файл, а пули залишаються незмінними: ніколи не обробляється частково і ніколи не оплачується частково. Це форма [правила fail closed](https://www.afterpack.dev/uk/docs/diagnostics#fail-closed-no-exceptions) на рівні пакета.

## Поводження з вихідним кодом

Вихідний код, надісланий на обфускацію, обробляється **лише в пам'яті**, протягом одного цього запиту, і ніколи не записується на диск, у журнали чи в пошуковий індекс. Журнали містять ідентифікатори запитів, кількість файлів, сумарні обсяги в байтах і діагностичні коди, але ніколи не вміст файлів, імена ідентифікаторів чи значення констант.

Один артефакт живе довше за запит: [Protection Map](https://www.afterpack.dev/uk/docs/protection-map), яка зберігається для кожної збірки для дашборду. Вона містить ваш оригінальний вихідний код, розфарбований за тим, скільки трансформацій зазнав кожен токен. Вона увімкнена за замовчуванням для кожного проєкту, доступна для читання лише учасникам робочого простору-власника, зберігається один рік **і** в межах останніх 500 збірок на проєкт (залежно від того, що настане раніше), і її можна вимкнути в [налаштуваннях проєкту](https://www.afterpack.dev/uk/docs/dashboard/workspaces). Докладніше в розділі [Конфіденційність і обробка даних](https://www.afterpack.dev/uk/docs/privacy).

## Далі

- [Режими розгортання](https://www.afterpack.dev/uk/docs/deployment-modes): яке місце цей протокол посідає порівняно з локальною збіркою Free.
- [Pro](https://www.afterpack.dev/uk/docs/pro): що Pro додає понад локальний рушій.
- [Конфіденційність і обробка даних](https://www.afterpack.dev/uk/docs/privacy): що зберігається, як довго і хто може це читати.
- [Тарифи](https://www.afterpack.dev/uk/docs/tiers): ліміти в MB, перевищення і що кожен план відкриває в цьому API.
- [API-ключі](https://www.afterpack.dev/uk/docs/dashboard/api-keys): створення, обмеження, ротація та відкликання облікових даних, якими автентифікується цей API.
- [Довідник CLI](https://www.afterpack.dev/uk/docs/cli): офіційний клієнт, який реалізує ці правила.
- [Конфігурація](https://www.afterpack.dev/uk/docs/config): конфігурація рушія, яку передає поле `config`.
