# Internal builder API

> The internal API falak-builder uses — builder tokens, job long-polling, NDJSON build events, heartbeats and artifact URLs — for operators' own builders.

Source: https://falak.sh/docs/api/builder-internal/

This API is for `falak-builder` only. It is documented so operators can run and debug builders. It is not a stable public API.

## Authentication

`Authorization: Bearer kbt_…` with a builder token:

| Builder | Token |
|---|---|
| The control plane host builder | `FALAK_LOCAL_BUILDER_TOKEN` (the installer's `FALAK_BUILDER_TOKEN`); serves every organization |
| `builder` servers | Installed automatically when the server finishes provisioning |
| External builders | Created under **Settings → Builders** (organization-scoped) |

`401` for unknown or disabled tokens.

```bash title="Run a builder"
falak-builder serve --url https://falak.example.com --token kbt_… --name build-1
# env: FALAK_URL, FALAK_BUILDER_TOKEN, FALAK_BUILDER_NAME
```

## `GET /api/internal/builds/next?wait=<s>&builder=<name>&run=<run id>`

Long-poll (up to 25 s) for the next job this builder is eligible for (organization and build mode). `204` when nothing is queued; `200` with a job:

```json
{"id": "01k…", "mode": "native", "timeout_s": 1800, "runtime": "php",
 "repo": {"url": "git@github.com:acme/shop.git", "ref": "main", "commit": "a1b2…",
          "deploy_key": "-----BEGIN OPENSSH PRIVATE KEY-----…", "known_hosts": "…"},
 "env": {"VITE_APP_NAME": "Shop"},
 "native": {"upload": {"url": "https://falak.example.com/api/internal/artifacts/…?expires=…&signature=…",
                       "headers": {"Content-Type": "application/octet-stream"}}}}
```

- `run` is a random id per `falak-builder` process. A poll with a new run id fails the builds the same builder name claimed under a previous run ("Builder &lt;name&gt; restarted during the build."). Two builder processes sharing a token must use different `--name`s.
- Docker jobs carry `"docker": {"image", "dockerfile", "build_args", "registry": {"server", "username", "password"}, "push": true}` instead of `native`.
- Clone credentials are fetched at hand-out time and never stored. HTTPS clones use `token`/`username` instead of `deploy_key`.
- `env` holds public front-end variables and variables exposed to the deploy script.
- Native jobs carry `native.install_command` / `native.build_command` when the site defines `FALAK_INSTALL_COMMAND` / `FALAK_BUILD_COMMAND`.

## `POST /api/internal/builds/{build}/events`

NDJSON body, one event per line (`started`, `output`, `progress`, `finished`), idempotent on `(build, seq)`. `finished` with `exit_code` 0 and a result (`artifact.sha256|size_bytes|format` or `image.ref|digest`) marks the build succeeded; exit code `124` means timed out.

| Response | Meaning |
|---|---|
| `204` | Accepted |
| `404` | Unknown build, or assigned to another builder |
| `410` | The build was cancelled: the builder aborts it |
| `413` | Batch larger than 8 MiB |
| `422` | Malformed line |

## `POST /api/internal/builds/{build}/heartbeat`

Every 20 s while building. `204`; `404` unknown or another builder's; `410` the build is over (cancelled, failed by the watchdog, reaped) and the builder aborts. A running build fails after `FALAK_BUILD_HEARTBEAT_TIMEOUT` (90 s) without a heartbeat or event.

## Artifacts

With the local driver, `PUT /api/internal/artifacts/{key}` (upload) and `GET /api/internal/artifacts/{key}` (download by agents) are authorized by the signed, expiring URL alone (`403` otherwise). URLs are always `https` (`FALAK_ARTIFACTS_URL`, default `APP_URL`). With `FALAK_ARTIFACTS_DRIVER=s3`, builders and agents use SigV4-presigned bucket URLs instead.

## `GET /install/builder/linux-{amd64|arm64}`

The `falak-builder` binary for builder servers (from `FALAK_BUILDER_BINARIES_PATH`, or `FALAK_BUILDER_DOWNLOAD_URL`).
