Skip to content

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

GET /api/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

GET /api/builds/id/{build_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)

GET /api/builds/id/{build_id}/logs

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

GET /api/builds/{app_name}/{tag}

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

POST /api/builds

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:

  1. Creates a build record with status queued and a generated tag ({branch}-{hash}).
  2. Increments the app's build_count.
  3. Queues a BUILD action for the agent to pick up — plus a dependent DEPLOY when auto_deploy is true.
  4. 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 (requires deploy).
  • 409 Conflict — the app is quarantined by the git-repo preflight (the detail is 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_slug is the current slug — retry against it. Also group_deleted_during_registration (retry), or group_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