Git repository builds

Use these routes to trigger Git repository builds manually, inspect history, and read incremental logs. The older route names remain available for existing GitHub projects.

Required permissions

Use project.deploy.trigger on project:<project-id> to trigger a deploy and project.logs.read to read builds and logs. Source and command configuration requires project.source.update. owner has full access; assistant, member, and organization API Keys need the corresponding grant.

Update GitHub commands and root directory

PATCH /v1/project/:id/github/commands

Updates the commands and root directory of a GitHub project and starts a new build. Requires project.source.update. Send at least one of the fields below; omitted fields keep their current value.

FieldTypeDescription
start_commandstringStart command, 1 to 256 characters.
pre_build_commandstring | nullCommand run before the build, 1 to 256 characters. null removes the command.
build_commandstring | nullBuild command, 1 to 256 characters. null removes the command.
context_pathstringBuild root directory, relative to the repository root. Default ".". Follows the rules in Build root directory.
{
  "start_command": "npm start",
  "build_command": "npm run build",
  "context_path": "apps/api"
}

Response

{
  "status": "success",
  "message": "GitHub commands updated and build triggered successfully",
  "data": {
    "build_id": "6650f1a2b3c4d5e6f7a8b9c1"
  }
}

The 202 response includes the build_id of the started build; follow it with the build routes below.

Errors

HTTPWhen it happens
400Empty body, command outside the limits, invalid context_path (absolute path, .., backslash, or more than 256 characters), or project without a connected GitHub repository.
401Missing or invalid credential.
403The credential does not have project.source.update for the project.
404The project does not exist or is not available to the current organization.
500The API could not save the commands or start the build.

Update GitHub deployment settings

PATCH /v1/project/:id/github/deploy-settings

Send at least one of the fields below. Supplied properties are applied to the current settings, then the resulting mode combination is validated.

FieldTypeDescription
auto_deploybooleantrue selects Automatic by branch. It cannot be enabled while version_deploy.enabled is true.
version_deployobjectConfigures By Tag and By Release modes. With enabled: false, defaults for the other fields are event: "tag", tag_pattern: "*", and include_prereleases: false. Older settings may omit this object; its absence keeps tag and release triggers disabled.
version_deploy.enabledbooleanEnables or disables version-based mode.
version_deploy.eventtag | releasetag updates on a newly created tag; release updates when a release is published.
version_deploy.tag_patternstringRequired when version mode is enabled. Matches the full tag name, is case-sensitive, and accepts 1 to 255 characters.
version_deploy.include_prereleasesbooleanFor event: "release", includes prereleases. The default is false; drafts do not start publications.

The pattern supports only * (any sequence of characters) and ? (one character). For example, v* matches v1.2.0; character classes such as [12], control characters, and other glob syntax are not accepted. In tag mode, only tag creation starts a publication; tag updates and deletions do not.

The modes are exclusive. When switching from the current mode, send the properties needed to leave the final state in exactly one mode. These examples show a complete body for each option:

ModeBody
Manual{"auto_deploy":false,"version_deploy":{"enabled":false,"event":"tag","tag_pattern":"*","include_prereleases":false}}
Automatic by branch{"auto_deploy":true,"version_deploy":{"enabled":false,"event":"tag","tag_pattern":"*","include_prereleases":false}}
By Tag{"auto_deploy":false,"version_deploy":{"enabled":true,"event":"tag","tag_pattern":"v*","include_prereleases":false}}
By Release{"auto_deploy":false,"version_deploy":{"enabled":true,"event":"release","tag_pattern":"v*","include_prereleases":false}}

Example request to publish when a new v* tag is created:

PATCH /v1/project/507f1f77bcf86cd799439011/github/deploy-settings
Content-Type: application/json
{
  "auto_deploy": false,
  "version_deploy": {
    "enabled": true,
    "event": "tag",
    "tag_pattern": "v*",
    "include_prereleases": false
  }
}

Response

For brevity, this example shows the public fields related to the update mode; the response can also include the project's configured runtime and commands.

{
  "status": "success",
  "message": "GitHub deploy settings updated successfully",
  "data": {
    "repository_owner": "example-org",
    "repository_name": "web-app",
    "branch": "main",
    "auto_deploy": false,
    "version_deploy": {
      "enabled": true,
      "event": "tag",
      "tag_pattern": "v*",
      "include_prereleases": false
    }
  }
}

data contains the public GitHub configuration. Older settings may omit version_deploy; without that object, tag and release publications are disabled, and auto_deploy indicates whether the project uses manual or automatic-by-branch mode.

Errors

HTTPWhen it occurs
400Empty body, invalid pattern, contradictory modes (for example, auto_deploy: true with version_deploy.enabled: true), or a GitHub repository that must be reconnected before enabling tags or releases.
401Missing or invalid credentials.
403The credential does not have project.source.update on the project.
404The project does not exist or is not available to the current organization.
409The GitHub source changed while settings were being updated; read the current project settings and retry.
429The limit of 10 updates per minute was exceeded.
500The API could not save the settings.

These logs are different from GET /v1/project/:id/logs, which returns logs from the running application. Build routes return publication history and logs.

Neutral routes for Git builds

Use these routes for projects with a Git source. Response envelopes retain the existing public build and log fields.

Trigger a manual deploy

POST /v1/project/:id/deploy

Rate limit: 10 per minute (counted per source IP).

The body accepts optional branch and commit_sha; commit_sha must be 40 or 64 hexadecimal characters. Without an override, the project uses its configured branch. For GitHub projects in By Tag and By Release modes, send an empty body: the build publishes the latest version matching the pattern. In these modes, branch or commit_sha return 400 (GIT_VERSION_REF_OVERRIDE), and no matching version returns 409 (GIT_VERSION_NOT_FOUND). The response is 202:

{
  "status": "success",
  "message": "Build triggered successfully",
  "data": {
    "build_id": "665f1f77bcf86cd799439011"
  }
}

List builds

GET /v1/project/:id/builds

Rate limit: 60 per minute (counted per source IP).

Optional query parameters are page, limit, branch, and status.

{
  "status": "success",
  "data": [],
  "pagination": {
    "total": 0,
    "page": 1,
    "limit": 10
  }
}

Get a build

GET /v1/project/:id/builds/:buildId

Rate limit: 60 per minute (counted per source IP).

The response uses { "status": "success", "data": Build }. Build fields remain the existing public fields; responses do not include credentials or internal source identifiers.

Read incremental logs

GET /v1/project/:id/builds/:buildId/logs?cursor=0&limit=200

Rate limit: 120 per minute (counted per source IP).

cursor and limit are numeric. The response contains new chunks, the next cursor, and current status:

{
  "status": "success",
  "data": {
    "logs": [],
    "next_cursor": 0,
    "status": "building",
    "finished": false,
    "truncated": false
  }
}

Use next_cursor in the next request to fetch only new lines. finished says the build reached a final state (success, failed, or superseded, when a newer build of the project replaced it and it will not be published); truncated says the public log was limited.

GitHub compatibility routes

The /github/* routes below preserve compatibility with existing GitHub projects. For a generic Git source, use the neutral routes on this page.

Trigger a manual build

POST /v1/project/:id/github/deploy

Rate limit: 10 per minute (counted per source IP).

Body

FieldTypeRequiredDescription
branchstringNoBranch used for the build. When omitted, the API uses the branch configured on the project
commit_shastringNoSpecific commit used to trigger the build (40 hexadecimal characters)

In By Tag and By Release modes, send an empty body: the build publishes the latest version matching the pattern. branch or commit_sha return 400, and no matching version returns 409.

Response

The response is 202:

{
  "status": "success",
  "message": "Build triggered successfully",
  "data": {
    "build_id": "665f1f77bcf86cd799439011"
  }
}

List project builds

GET /v1/project/:id/github/builds

Rate limit: 50 per minute (counted per source IP).

Query

FieldTypeRequiredDescription
pagenumberNoCurrent page. Default 1
limitnumberNoPage size. Default 10, maximum 50
branchstringNoFilters by branch
statusstringNoFilters by pending, building, success, failed, or superseded

Response

{
  "status": "success",
  "message": "builds listed successfully",
  "data": [
    {
      "id": "665f1f77bcf86cd799439011",
      "commit_sha": "abc123def456789012345678901234567890abcd",
      "branch": "main",
      "status": "failed",
      "started_at": "2026-06-03T14:10:00.000Z",
      "finished_at": "2026-06-03T14:11:09.000Z",
      "error_code": "NPM_LOCKFILE_REQUIRED",
      "error_message": "O package-lock.json não foi encontrado no diretório raiz da aplicação.",
      "triggered_by": "manual",
      "created_at": "2026-06-03T14:10:00.000Z",
      "updated_at": "2026-06-03T14:11:09.000Z"
    }
  ],
  "pagination": {
    "total": 12,
    "page": 1,
    "limit": 10
  }
}

Get a specific build

GET /v1/project/:id/github/builds/:buildId

Rate limit: 50 per minute (counted per source IP).

Response

{
  "status": "success",
  "message": "build retrieved successfully",
  "data": {
    "id": "665f1f77bcf86cd799439011",
    "commit_sha": "abc123def456789012345678901234567890abcd",
    "branch": "main",
    "status": "failed",
    "started_at": "2026-06-03T14:10:00.000Z",
    "finished_at": "2026-06-03T14:11:09.000Z",
    "error_code": "NPM_LOCKFILE_REQUIRED",
    "error_message": "O package-lock.json não foi encontrado no diretório raiz da aplicação.",
    "triggered_by": "manual",
    "created_at": "2026-06-03T14:10:00.000Z",
    "updated_at": "2026-06-03T14:10:00.000Z"
  }
}

Failure fields

The codes below document GitHub compatibility routes. Neutral routes may add generic build codes; clients should keep error_message as a fallback.

List and detail responses can include these fields when a build fails:

FieldTypeDescription
error_codestringOptional code that identifies a known cause
error_messagestringPublic message with the available diagnosis
diagnosis_statusstringLegacy field. New builds return not_requested; older builds may carry available or unavailable
diagnosis_codestringLegacy field, present only on older builds

Legacy builds may not have an error_code. API clients should use error_message as the fallback when error_code is absent or unrecognized. The text and language of error_message are not a stable localization contract; clients should prefer error_code for their own localized presentation.

The supported codes are:

CodeMeaning
NPM_LOCKFILE_REQUIREDThe Node.js project does not contain a usable package-lock.json in the expected directory
NPM_LOCKFILE_OUT_OF_SYNCpackage-lock.json is not synchronized with package.json
GITHUB_COMMIT_UNAVAILABLEThe requested commit is unavailable in the selected repository and branch
GITHUB_REPOSITORY_AUTH_FAILEDZenifra could not access the repository with the current GitHub connection
NODE_BUILD_SCRIPT_MISSINGThe build expects a Node.js script that is not declared in package.json
BUILD_FAILED_UNCLASSIFIEDThe cause could not be classified automatically; review the message and Build Logs
GITHUB_REPOSITORY_REAUTHORIZATION_REQUIREDZenifra could not renew access to the same repository; reconnect it in the project
BUILD_COMMAND_FAILEDA project command (install, pre-build, or build) exited with an error; its output is in Build Logs
ROOT_DIRECTORY_NOT_FOUNDThe configured root directory does not exist in the build commit
ROOT_DIRECTORY_INVALIDThe configured root directory is not a valid relative path inside the repository
GITHUB_ACCESS_TOKEN_MISSINGThe GitHub connection did not authorize access to the repository to start the build
PROJECT_PUBLISH_TIMEOUTThe build finished, but publishing did not respond in time; try again
PROJECT_PUBLISH_FAILEDThe build finished, but publishing failed; try again

diagnosis_status and diagnosis_code are kept for compatibility. Use error_code and error_message to present the failure cause.

For lockfile fixes and Node.js command validation, see Node.js dependencies.

Get incremental build logs

GET /v1/project/:id/github/builds/:buildId/logs

Rate limit: 200 per minute (counted per source IP).

Query

FieldTypeRequiredDescription
cursornumberNoLast consumed sequence. Default 0
limitnumberNoMaximum number of chunks. Default 200, maximum 500

Response

{
  "status": "success",
  "message": "build logs retrieved successfully",
  "data": {
    "logs": [
      {
        "sequence": 1,
        "timestamp": "2026-06-03T14:10:01.000Z",
        "level": "info",
        "step": "build",
        "message": "npm ci",
        "final": false
      }
    ],
    "next_cursor": 1,
    "status": "building",
    "finished": false,
    "truncated": false
  }
}

Use next_cursor in the next request to fetch only new lines.

How to interpret it

FieldDescription
logsIncremental list of public build lines
next_cursorLast returned sequence
statusCurrent build state
finishedtrue when the build ended with success, failed, or superseded
truncatedtrue when the public log was truncated to protect platform stability

Retention

GitHub build history is retained for up to:

  • 30 builds per project
  • 30 days of age

Whichever limit comes first removes the oldest records, together with their log chunks.

Polling example

curl -X GET "https://api.zenifra.com/v1/project/507f1f77bcf86cd799439011/github/builds/665f1f77bcf86cd799439011/logs?cursor=0&limit=200" \
  -H "x-api-key: your-api-key" \
  -H "x-organization-id: your-organization-id"

Next steps

Last updated on

On this page