ReferenceReference by module

Build API

Every Build action (10) and data model (2): endpoints, scopes, input, preconditions, events and examples.

Turn an idea into a real app: ERA composes it from its own building blocks, or your coding tools change your repository and ERA opens the pull request with a preview.

Module code build · 10 actions · 2 data models · 6 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: build (everything), build:read (read only) or build:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
build_projectsbuild_project/api/v1/build_projects
build_runsbuild_run/api/v1/build_runs

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
build.archive_projectbuild:deleteArchive a project
build.cancel_runbuild:writeStop a build
build.complete_runbuild:handleFinish a build
build.create_projectbuild:writeStart a project
build.fail_runbuild:handleMark a build failed
build.record_previewbuild:handleRecord a preview
build.record_progressbuild:handleRecord build progress
build.record_publishbuild:handleRecord that an app went live
build.request_runbuild:writeBuild or change something
build.update_projectbuild:writeChange a project

build.archive_project

Archive a project — Takes it off the Projects list. Its site, repository and pull requests are not touched.

Needs build:delete · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/build_projects/{id}/actions/archive_project
API key or OAuth token · build:delete
  • Canonical address: POST /api/v1/actions/build.archive_project

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Project · id of build_project

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe project must exist in this workspace.
run_openNothing may be building in it.

What it writes: build_project (update) — → archived

curl -X POST https://app.getera.io/api/v1/build_projects/BUILD_PROJECT_ID/actions/archive_project \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.buildProjects.archiveProject("BUILD_PROJECT_ID")
era build_projects archive_project BUILD_PROJECT_ID
tools/call
{
  "name": "era__build__archive_project",
  "arguments": {
    "id": "BUILD_PROJECT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold build:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, run_open · 429 slow down

build.cancel_run

Stop a build — Stops it; the coding tool on the computer is stopped too. Nothing is committed.

Needs build:write · removes or cannot be undone

POSThttps://app.getera.io/api/v1/build_runs/{id}/actions/cancel_run
API key or OAuth token · build:write
  • Canonical address: POST /api/v1/actions/build.cancel_run

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Build · id of build_run

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe build must exist.
run_closedOnly an open build can be stopped.

What it writes: build_run (update) — → cancelled; build_project (update) — Back to ready or draft

curl -X POST https://app.getera.io/api/v1/build_runs/BUILD_RUN_ID/actions/cancel_run \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.buildRuns.cancelRun("BUILD_RUN_ID")
era build_runs cancel_run BUILD_RUN_ID
tools/call
{
  "name": "era__build__cancel_run",
  "arguments": {
    "id": "BUILD_RUN_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold build:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, run_closed · 429 slow down

build.complete_run

Finish a build — Records what was built: the pull request and its files, or the composed app and where it is live. Machine-only.

Needs build:handle · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/build_runs/{id}/actions/complete_run
API key or OAuth token · build:handle
  • Canonical address: POST /api/v1/actions/build.complete_run

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Build · id of build_run
summarystringSummary · max 8000
commitShastringCommit · max 64
prNumbernumberPull request · min 1
prUrlstringPull request address · max 500
previewUrlstringPreview · max 500
liveUrlstringLive address · max 500
filesChangednumberFiles changed · min 0
changesobject / arrayChanged files
usageobject / arrayUsage
siteIduuidSite
blueprintCodestringBlueprint · max 80
planobject / arrayWhat was composed
awaitingPublishbooleanStopped at the draft
externalUrlstringExpo build page · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe build must exist.
run_closedA build finishes once.

Events it publishes: build.run_succeeded

What it writes: build_run (update) — → succeeded, with its result; build_project (update) — → ready, with its preview / live address

Errors: 401 no or expired credential · 403 the credential does not hold build:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, run_closed · 429 slow down

build.create_project

Start a project — An app ERA composes from its own building blocks, or a code repository ERA works on with your coding tools.

Needs build:write · changes data

POSThttps://app.getera.io/api/v1/build_projects/actions/create_project
API key or OAuth token · build:write
  • Canonical address: POST /api/v1/actions/build.create_project

Input

FieldTypeRequiredNotes
kindstring (enum)yesKind · Allowed values: app, code
namestringyesName · max 160
descriptionstringWhat it is · max 4000
credentialIduuidGitHub / GitLab connection · id of credential
repoProviderstring (enum)Provider · Allowed values: github, gitlab
repoFullNamestringRepository · owner/name · max 200
repoUrlstringRepository address · max 400
defaultBranchstringMain branch · max 200
harnessstringCoding tool · claude = Claude Code · codex = Codex · gemini = Antigravity · grok = Grok Build · copilot = Copilot CLI · cursor = Cursor CLI · kimi = Kimi Code CLI · vibe = Mistral Vibe · opencode = opencode · amp = Amp CLI · kiro = Kiro CLI · cline = Cline CLI · auggie = Auggie CLI · max 32

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
empty_nameA project needs a name.
repo_incompleteA repository needs its connection, provider and full name together.
credential_not_foundThe repository connection must be a GitHub or GitLab connection of this workspace.
connection_not_usableThe connection must be the person’s own, the workspace’s, or shared with them.
harness_not_supportedThe coding tool must be one the Harness Manager runs.

Events it publishes: build.project_created

What it writes: build_project (insert) — Writes the project

curl -X POST https://app.getera.io/api/v1/build_projects/actions/create_project \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"kind":"app","name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "build.project_created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.buildProjects.createProject({
  kind: "app",
  name: "Name"
})
era build_projects create_project \
  --kind "app" \
  --name "Name"
tools/call
{
  "name": "era__build__create_project",
  "arguments": {
    "kind": "app",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold build:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_name, repo_incomplete, credential_not_found, connection_not_usable, harness_not_supported · 429 slow down

build.fail_run

Mark a build failed — Records why it stopped and at which step. Machine-only.

Needs build:handle · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/build_runs/{id}/actions/fail_run
API key or OAuth token · build:handle
  • Canonical address: POST /api/v1/actions/build.fail_run

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Build · id of build_run
errorstringWhy · max 2000
stepstringStep · max 40

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe build must exist.
run_closedA build finishes once.

Events it publishes: build.run_failed

What it writes: build_run (update) — → failed; build_project (update) — → failed, unless an earlier build left it ready

Errors: 401 no or expired credential · 403 the credential does not hold build:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, run_closed · 429 slow down

build.record_preview

Record a preview — The pull request’s preview deployment appeared (Vercel, Netlify, Cloudflare Pages…). Machine-only.

Needs build:handle · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/build_runs/{id}/actions/record_preview
API key or OAuth token · build:handle
  • Canonical address: POST /api/v1/actions/build.record_preview

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Build · id of build_run
previewUrlstringyesPreview · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundOnly a finished build gets a preview.
bad_urlA preview is a web address.

Events it publishes: build.preview_ready

What it writes: build_run (update) — Sets the preview; build_project (update) — Sets the project’s latest preview

Errors: 401 no or expired credential · 403 the credential does not hold build:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, bad_url · 429 slow down

build.record_progress

Record build progress — Moves a build to its next step (getting the repo ready, writing, opening the pull request). Machine-only.

Needs build:handle · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/build_runs/{id}/actions/record_progress
API key or OAuth token · build:handle
  • Canonical address: POST /api/v1/actions/build.record_progress

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Build · id of build_run
statusstring (enum)Status · Allowed values: waiting, running
stepstringStep · max 40
stepStatusstring (enum)Step status · Allowed values: doing, done, failed, skipped
detailstringDetail · max 300
harnessstringCoding tool · max 20
deviceIdstringComputer · max 64
runnerJobIdstringRunner job · max 64
branchstringBranch · max 200
baseBranchstringBase branch · max 200
baseShastringBase commit · max 64
externalIdstringExpo build · max 120
externalUrlstringExpo build page · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe build must exist.
run_closedOnly an open build moves.

What it writes: build_run (update) — Status, step and where it runs

Errors: 401 no or expired credential · 403 the credential does not hold build:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, run_closed · 429 slow down

build.record_publish

Record that an app went live — The person approved the draft and its site was published (site.publish, as them). Machine-only: it records the live address.

Needs build:handle · reaches outside (sends, publishes or charges)

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/build_projects/{id}/actions/record_publish
API key or OAuth token · build:handle
  • Canonical address: POST /api/v1/actions/build.record_publish

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Project · id of build_project
liveUrlstringyesLive address · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bad_urlA live address is a web address.
not_foundThe project must exist.
not_draftOnly an app waiting at its draft is published.

Events it publishes: build.app_published

What it writes: build_project (update) — → live, with its address; build_run (update) — The last app run’s "live" step done

Errors: 401 no or expired credential · 403 the credential does not hold build:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_url, not_found, not_draft · 429 slow down

build.request_run

Build or change something — For an app project: ERA composes the app from its building blocks (modules, workflows, Eras, a site) and shows it as a draft to publish — or puts it live when you say "publish it". With a linked repository, your coding tool (Claude Code, Codex, Antigravity, Copilot, Cursor … any the Harness Manager runs) changes the code on your computer and ERA opens a pull request with a preview; "mobile" asks Expo for an iPhone / Android build.

Needs build:write · changes data

POSThttps://app.getera.io/api/v1/build_projects/{id}/actions/request_run
API key or OAuth token · build:write
  • Canonical address: POST /api/v1/actions/build.request_run

Input

FieldTypeRequiredNotes
projectIduuidyesFilled from the path {id} · Project · id of build_project
promptstringyesWhat to build or change · max 8000
modestring (enum)Kind of run · Allowed values: compose, code, mobile
publishbooleanPublish when done
harnessstringCoding tool · claude = Claude Code · codex = Codex · gemini = Antigravity · grok = Grok Build · copilot = Copilot CLI · cursor = Cursor CLI · kimi = Kimi Code CLI · vibe = Mistral Vibe · opencode = opencode · amp = Amp CLI · kiro = Kiro CLI · cline = Cline CLI · auggie = Auggie CLI · max 32
deviceIdstringComputer · max 64
viastring (enum)Asked from · Allowed values: web, era, work, phone, workflow, desktop
workStepIduuidPlan step

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
empty_promptThere must be something to build.
not_foundThe project must exist and not be archived.
no_repoChanging code or building for phones needs the repository connected.
no_mobileA phone build needs the Expo project set.
harness_not_supportedThe coding tool must be one the Harness Manager runs.
connection_not_usableThe repository’s connection must be usable by the person asking.
run_openOne build at a time per project.

Events it publishes: build.run_requested

What it writes: build_run (insert) — Queues the run with its steps; build_project (update) — → building

curl -X POST https://app.getera.io/api/v1/build_projects/BUILD_PROJECT_ID/actions/request_run \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"prompt":"What to build or change"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "build.run_requested"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.buildProjects.requestRun("BUILD_PROJECT_ID", {
  prompt: "What to build or change"
})
era build_projects request_run BUILD_PROJECT_ID \
  --prompt "What to build or change"
tools/call
{
  "name": "era__build__request_run",
  "arguments": {
    "projectId": "BUILD_PROJECT_ID",
    "prompt": "What to build or change"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold build:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_prompt, not_found, no_repo, no_mobile, harness_not_supported, connection_not_usable, run_open · 429 slow down

build.update_project

Change a project — Rename it, describe it, connect its repository (an app’s custom code too), pick the coding tool and computer, or set the Expo project for phone builds.

Needs build:write · changes data

POSThttps://app.getera.io/api/v1/build_projects/{id}/actions/update_project
API key or OAuth token · build:write
  • Canonical address: POST /api/v1/actions/build.update_project

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Project · id of build_project
namestringName · max 160
descriptionstringWhat it is · max 4000
credentialIduuidGitHub / GitLab connection · id of credential
repoProviderstring (enum)Provider · Allowed values: github, gitlab
repoFullNamestringRepository · owner/name · max 200
repoUrlstringRepository address · max 400
defaultBranchstringMain branch · max 200
harnessstringCoding tool · claude = Claude Code · codex = Codex · gemini = Antigravity · grok = Grok Build · copilot = Copilot CLI · cursor = Cursor CLI · kimi = Kimi Code CLI · vibe = Mistral Vibe · opencode = opencode · amp = Amp CLI · kiro = Kiro CLI · cline = Cline CLI · auggie = Auggie CLI · max 32
deviceIdstringComputer · A paired computer (Era Desktop or the cloud computer); empty = the first one that is on. · max 64
expoProjectIdstringExpo project (phone builds) · expo.dev → your project → Project ID. Empty = no phone builds. · max 64
platformsobject / arrayPhones · ["ios","android"]

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe project must exist in this workspace.
empty_nameA project needs a name.
repo_incompleteA repository needs its connection, provider and full name together.
credential_not_foundThe repository connection must be a GitHub or GitLab connection of this workspace.
connection_not_usableThe connection must be the person’s own, the workspace’s, or shared with them.
harness_not_supportedThe coding tool must be one the Harness Manager runs.
bad_mobileA phone build names its Expo project (a uuid) and iPhone and/or Android.

What it writes: build_project (update) — Changes the project

curl -X POST https://app.getera.io/api/v1/build_projects/BUILD_PROJECT_ID/actions/update_project \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.buildProjects.updateProject("BUILD_PROJECT_ID", {
  name: "Name"
})
era build_projects update_project BUILD_PROJECT_ID \
  --name "Name"
tools/call
{
  "name": "era__build__update_project",
  "arguments": {
    "id": "BUILD_PROJECT_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold build:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, empty_name, repo_incomplete, credential_not_found, connection_not_usable, harness_not_supported, bad_mobile · 429 slow down

Data models

BuildProject

Table build_project · Resource build_projects

  • GET /api/v1/build_projects
  • GET /api/v1/build_projects/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
created_bystring (uuid) · uuidyesapp_user
kindstring · textapp, code
namestring · text
descriptionstring · textyes
statusstring · textdraft, building, ready, failed, archived
repo_providerstring · textyes
repo_full_namestring · textyes
repo_urlstring · textyes
default_branchstring · textyes
harnessstring · textyes
device_idstring · textyes
blueprint_codestring · textyes
planobject · jsonb
site_idstring (uuid) · uuidyes
preview_urlstring · textyes
live_urlstring · textyes
last_run_idstring (uuid) · uuidyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
site_statestring · textyes
mobileobject · jsonb

Primary key: id

Checks:

  • CHECK (((harness IS NULL) OR (harness ~ '^[a-z][a-z0-9_]{1,31}$'::text)))
  • CHECK ((length(btrim(name)) > 0))
  • CHECK (((repo_provider IS NULL) OR (repo_provider = ANY (ARRAY['github'::text, 'gitlab'::text]))))
  • CHECK (((site_state IS NULL) OR (site_state = ANY (ARRAY['draft'::text, 'live'::text]))))

BuildRun

Table build_run · Resource build_runs

  • GET /api/v1/build_runs
  • GET /api/v1/build_runs/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
project_idstring (uuid) · uuidbuild_project
created_bystring (uuid) · uuidyesapp_user
kindstring · textcompose, code, mobile
promptstring · text
statusstring · textqueued, waiting, running, succeeded, failed, cancelled
stepsobject · jsonb
harnessstring · textyes
device_idstring · textyes
runner_job_idstring · textyes
branchstring · textyes
base_branchstring · textyes
base_shastring · textyes
commit_shastring · textyes
pr_numberinteger · int4yes
pr_urlstring · textyes
preview_urlstring · textyes
live_urlstring · textyes
files_changedinteger · int4
changesobject · jsonb
summarystring · textyes
errorstring · textyes
usageobject · jsonbyes
requested_viastring · textweb, era, work, phone, workflow, desktop
work_step_idstring (uuid) · uuidyes
started_atstring (date-time) · timestamptzyes
finished_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
publishboolean · boolyes
external_idstring · textyes
external_urlstring · textyes

Primary key: id

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
build.app_publishedAn app’s draft was approved and is live.projectId, liveUrl, recipientUserId
build.preview_readyA pull request’s preview deployment is ready to look at.id, projectId, previewUrl, recipientUserId
build.project_createdA project was started (an app or a code repository).id, kind, name
build.run_failedA build did not finish.id, projectId, error, recipientUserId
build.run_requestedSomebody (or Era) asked for something to be built.id, projectId, kind, harness
build.run_succeededA build finished: a pull request was opened, the app is ready (live, or a draft waiting for Publish), or a phone build is ready.id, projectId, kind, prUrl, previewUrl, liveUrl, awaitingPublish, recipientUserId
Was this page helpful?