Builds Endpoints¶
Manage container builds. Builds are uniquely identified by (app_name, environment, tag) — but the environment is not a request parameter: the API process is bound to a single environment and resolves it server-side.
Key Design Decision¶
Build reads come from PostgreSQL, never from the build service.
Querying the build service once per build when listing does not scale — it rate-limits at a few dozen apps. The AppMecca API stores build status in the builds table and queries it directly, so a list returns in well under a second no matter how many apps or builds exist.
The agent writes build progress into PostgreSQL as it runs, so the table is the live view, not a lagging copy.
Group scoping
Every route is caller-scoped. A build belonging to another group returns 404, never 403.
Endpoints¶
| Method | Path | Scope | Description |
|---|---|---|---|
GET |
/api/builds?app_name=&branch=&since= |
read | List builds |
GET |
/api/builds/id/{build_id} |
read | Get a build by id (prefix accepted) |
GET |
/api/builds/id/{build_id}/logs |
read | Build logs by id |
GET |
/api/builds/{app_name}/{tag} |
read | Get a single build by app + tag |
POST |
/api/builds |
deploy | Trigger a new build → 202 |
List Builds¶
Optional query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
app_name |
string | — | Filter by app name |
branch |
string | — | Filter by branch (omit to span every branch) |
since |
datetime | — | Only builds started after this time (ISO 8601) |
limit |
integer | 50 |
Max results (1–200) |
offset |
integer | 0 |
Pagination offset |
With no app_name, results are filtered to the apps the caller can see.
Response: 200 OK
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"app_name": "my-web-app",
"display_name": "My Web App",
"tag": "main-abc12345",
"commit_hash": "abc12345",
"commit_message": "Fix the checkout total rounding",
"commit_author": "Alice Smith",
"commit_committed_at": "2026-03-15T09:08:11Z",
"branch": "main",
"status": "completed",
"codebuild_id": "appmecca-dev-my-web-app:build-id-123",
"codebuild_phase": "COMPLETED",
"duration_seconds": 142,
"images": [
"…/appmecca/dev/my-web-app/web:abc12345"
],
"created_by": "alice",
"started_at": "2026-03-15T09:10:00Z",
"completed_at": "2026-03-15T09:12:22Z",
"created_at": "2026-03-15T09:10:00Z",
"updated_at": "2026-03-15T09:12:22Z"
}
],
"total": 1
}
The commit_* fields are captured at build time from the repo, so clients can
render the human commit message rather than only the image tag. They are
null on builds that predate the feature.
Get Build by ID¶
build_id may be a unique prefix, but must be at least 8 characters and
hex-or-hyphen only — a shorter or wildcard-shaped value is rejected rather
than matched loosely.
Response: 200 OK — a single BuildResponse.
Errors:
404 Not Found— no build with that id, or it belongs to another group.422 Unprocessable Entity— the id prefix is too short or malformed.
Get Build Logs (by build ID)¶
Path parameters:
| Parameter | Type | Description |
|---|---|---|
build_id |
string | UUID of the build, or a unique prefix (≥ 8 hex characters). Shares the id with the queue action. |
Response: 200 OK with this shape:
| Field | Type | Description |
|---|---|---|
build_id |
string | Full UUID of the build. |
app_name |
string | App the build belongs to. |
tag |
string | Build tag (e.g. main-abc12345). |
status |
string | The builds row status — only finalised at build completion (queued → succeeded / failed). Stays at queued for the whole run window. |
queue_status |
string | null | Authoritative live status from the deploy queue (queued → build_in_progress → build_completed). Use this to render progress; fall back to status if null. |
message |
string | null | A human hint that explains the current state — e.g. Live stream from build service (in progress). or Build starting; log stream not yet created. Clients should render this in place of the default "hasn't started yet" hint. |
log |
string | The build's log output. |
lines |
integer | Line count of log, or 0 when empty. |
source |
string | absent | Present and equal to "queue" when the response was synthesised from the queue fallback path (see below). Absent on the regular path. |
Live tail while a build is running¶
The builds row's log is only written at completion, so an in-flight build
would otherwise show nothing at all. When the queue says the build is running
and no log has landed yet, the endpoint pulls a live tail from the log
collector and returns it immediately, with a message naming it as an
in-progress stream (and flagging truncation when the tail was clipped). If the
live pull fails for any reason the response degrades to a "retry in a moment"
message rather than an error — the build itself is unaffected.
Builds-row-missing fallback¶
A build that fails before its record is created — a missing config path, a
missing git credential, a build-project provisioning failure — never gets a
builds row. The queue row, however, carries the full failure context.
When no build record exists, the endpoint falls back to the queue action of
the same id and synthesises the response from it: the status, the error
message (or a hint pointing at appmecca queue diagnose <id> when there is
none), and whatever per-phase output the agent stamped. source: "queue" is
set so clients can render an "early failure" hint and not assume the build
ever started.
The endpoint only 404s when both lookups fail; the error message names both.
Errors:
404 Not Found—Build '<id>' not found in builds table or deploy queue.(Both lookups failed.)422 Unprocessable Entity— the id prefix is too short or malformed.
Get Build¶
Path parameters:
| Parameter | Type | Description |
|---|---|---|
app_name |
string | The app name |
tag |
string | The build tag (e.g. main-abc12345) |
Response: 200 OK — single BuildResponse object.
Errors:
404 Not Found— build does not exist, or belongs to another group.
Trigger Build¶
Scope: deploy or higher.
Request body:
{
"app_name": "my-web-app",
"repository": "git@github.com:org/my-web-app.git",
"branch": "main",
"auto_deploy": true
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
app_name |
string | yes | — | App to build |
repository |
string | yes | — | Git repository URL |
branch |
string | no | main |
Branch to build |
auto_deploy |
boolean | no | true |
Auto-deploy on successful build |
config_path |
string | no | appmecca.yaml |
Path to appmecca.yaml in the repo |
There is no environment field — the API resolves its own environment.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
skip_preflight |
boolean | false |
Bypass the git-repo preflight gate (escape hatch for a false-negative check) |
Behavior:
- Creates a build record with status
queuedand a generated tag ({branch}-{hash}). - Increments the app's
build_count. - Queues a
BUILDaction for the agent to pick up — plus a dependentDEPLOYwhenauto_deployis true. - Returns immediately (fire-and-forget) — the agent runs the build asynchronously.
If the app is not registered yet it is auto-registered under the caller's resolved group.
Response: 202 Accepted
{
"tag": "main-a1b2c3d4",
"action_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"build_id": "550e8400-e29b-41d4-a716-446655440000"
}
action_id and build_id are the same identifier — the queue action and the
build record share an id.
Errors:
403 Forbidden— insufficient scope (requiresdeploy).409 Conflict— the app is quarantined by the git-repo preflight (thedetailis the verbatim remediation text), or the branch collides with an existing one after sanitisation.409 Conflict—group_renamed_during_registration: the group's slug changed while the app's name was being derived;detail.group_slugis the current slug — retry against it. Alsogroup_deleted_during_registration(retry), orgroup_busy(another change to the group holds its lock; retry in a few seconds).404 Not Found— the group was deleted while the request waited for its lock; nothing was written.422 Unprocessable Entity— the repository URL is not on a supported git host.
Build Statuses¶
| Status | Description |
|---|---|
pending |
Default initial state |
queued |
Build record created, waiting for agent |
in_progress |
Agent has claimed the build and it is running |
completed |
Build succeeded, images pushed to the private image registry |
failed |
Build failed (see error_message) |
cancelled |
Build was cancelled |
The live lifecycle on the queue action uses the parallel
queued → build_in_progress → build_completed / build_failed / build_cancelled
names; that is what queue_status reports.
Data Model¶
Builds are stored in the builds PostgreSQL table. See Schema Reference for the full table definition.
Key fields:
| Field | Description |
|---|---|
tag |
Unique build identifier within app+environment (e.g. main-abc12345) |
commit_hash |
The commit actually built — this, not tag, is the image tag pushed |
commit_message / commit_author / commit_committed_at |
Commit metadata captured at build time; null on older builds |
codebuild_id |
The build service's job id (used to correlate live log output) |
codebuild_phase |
Current build phase (SUBMITTED, PROVISIONING, BUILD, COMPLETED) |
duration_seconds |
Total build duration in seconds |
images |
JSONB — image URIs produced by the build |
error_message |
Error details if the build failed |