Skip to main content

Loha API

The Loha REST API lets the Loha CLI and your own tools drive the platform programmatically: list and trigger builds, validate .loha.yml, manage repositories, and read logs.

The versioned surface lives under /api/v1. Explore it interactively in the API reference explorer (powered by Scalar), or consume the machine-readable OpenAPI 3.1 document directly:

GET https://app.loha.longtumlab.com/api/v1/openapi.json

Authentication​

Every request authenticates with a workspace-scoped bearer token: either a personal access token (PAT) (acts as you; create under your workspace's Settings → API Keys) or a service account key (acts as a workspace-owned machine identity; the right choice for CI/CD). Both are bound to a single workspace and carry explicit scopes; the API treats them identically. See Personal access tokens for the create / scope / revoke guide.

The principal field returned by GET /api/v1/whoami tells you which kind you authenticated as (user vs service_account).

Send it as a bearer token (or the x-api-key header):

curl https://app.loha.longtumlab.com/api/v1/whoami \
-H "Authorization: Bearer loha_xxxxxxxxxxxxxxxxxxxx"

GET /api/v1/whoami is the cheapest way to verify a token. It returns the bound workspace, the token's scopes, and the acting principal.

Scopes​

Tokens carry explicit scopes. There is no all-access token. A request to an endpoint whose scope the token lacks returns 403.

ScopeGrants
builds:readRead builds and logs
builds:writeMutate builds (e.g. cancel a running build)
triggers:runFire a pipeline run (manual / deploy)
triggers:writeCreate / update triggers
pipelines:readRead triggers, validate .loha.yml
repos:readList and read repositories
repos:code:readRead source code (diffs and file contents)
variables:readRead variable metadata (never values)
variables:values:readRead plain variable values in a build plan (never secrets)
variables:writeCreate / update / delete variables
audit:readRead audit logs
webhooks:readRead outbound webhooks and delivery history
webhooks:writeCreate / update / rotate / delete webhooks

A token can never reach outside the workspace it was issued for, regardless of its scopes. The workspace is bound to the token, not to your session.

Rate limits​

API-token requests are limited per token. When you exceed the limit you get 429 with Retry-After and X-RateLimit-* headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716742800
Retry-After: 42

Errors​

Errors use a consistent envelope:

{ "error": "API key does not have required scope" }
StatusMeaning
401Missing, invalid, expired, or revoked token
403Token lacks the required scope, or its owner lost workspace access
404Resource not found (or not in this workspace)
422Validation failed (e.g. an invalid .loha.yml)
429Rate limit exceeded

Endpoints​

MethodPathScopePurpose
GET/api/v1/whoami—Verify token, resolve workspace
GET/api/v1/buildsbuilds:readList builds (filterable)
GET/api/v1/builds/{id}builds:readBuild detail
DELETE/api/v1/builds/{id}builds:writeCancel a build
GET/api/v1/builds/{id}/logsbuilds:readBuild logs (JSON)
GET/api/v1/reposrepos:readList repositories
GET/api/v1/triggerspipelines:readList triggers
POST/api/v1/triggers/createtriggers:writeCreate a trigger. See Scheduled triggers
POST/api/v1/triggers/{id}/runtriggers:runRun a trigger (deploy)
POST/api/v1/pipelines/validatepipelines:readValidate a .loha.yml
POST/api/v1/pipelines/planpipelines:readPreview what would run. See loha plan
—/api/v1/webhooks …webhooks:*Manage outbound webhooks. See Webhooks

See the full request/response shapes in the OpenAPI document.

Example: trigger a deploy​

TOKEN="loha_xxxxxxxxxxxxxxxxxxxx"

curl -X POST https://app.loha.longtumlab.com/api/v1/triggers/$TRIGGER_ID/run \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch":"main"}'

A deploy runs against a committed git ref. Loha builds from your repository and ships through its GitOps path. Commit your changes, then trigger the run.

Scheduled triggers​

POST /api/v1/triggers/create accepts type: "schedule" alongside manual and webhook. A schedule trigger takes two extra body fields:

FieldTypeNotes
scheduleCronstringFive-field cron, evaluated in Asia/Bangkok. Required for type: "schedule" and refused on any other type (400).
scheduleSkipIfUnchangedbooleanOptional, default false. Skip a scheduled run when the branch has no new commits since the last scheduled run. Needs a repo_file source; true without a repository returns 400.
curl -X POST https://app.loha.longtumlab.com/api/v1/triggers/create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "nightly main",
"type": "schedule",
"scheduleCron": "0 2 * * *",
"scheduleSkipIfUnchanged": true,
"source": { "kind": "repo_file", "repositoryId": "'"$REPO_ID"'", "branch": "main" }
}'

Scheduled triggers are rolled out per workspace, and their number is capped by the workspace's limit preset. A refused create returns 403 with a code:

codeMeaning
FEATURE_DISABLEDScheduled triggers are not enabled for this workspace
SCHEDULED_TRIGGER_LIMITThe workspace already has as many enabled scheduled triggers as it may have

A cron that runs more often than the preset's minimum interval returns 400.

The create response's trigger object, and every item from GET /api/v1/triggers, carry five schedule fields. On a non-schedule trigger they are null (false for scheduleSkipIfUnchanged), except scheduleTimezone, which is always Asia/Bangkok:

FieldTypeMeaning
scheduleCronstring | nullThe cron expression
scheduleTimezonestring | nullThe timezone the cron is evaluated in: Asia/Bangkok
scheduleNextRunAtstring | nullISO time of the next scheduled run; null if it cannot be computed
scheduleLastFiredAtstring | nullISO time of the last scheduled firing, run or skipped
scheduleSkipIfUnchangedbooleanWhether runs are skipped when the branch has no new commits