Skip to Content

Error codes

Every error response from the Deploy API has the same shape:

{ "status": 409, "message": "acme.com is already registered on Faable. A hostname can only point at one app — if you added it in another of your projects, remove it there first.", "error_code": "domain_taken", "docs": "https://faable.com/docs/deploy/errors#domain_taken", "statusCode": 409, "error": "Conflict", "code": "domain_taken" }
  • error_code is the field to branch on. It is stable: a code is never renamed or reused with another meaning, and each one maps to exactly one HTTP status.
  • message is written for a person and may change between releases. Log it, show it if it reads well, but do not compare it.
  • details is optional and structured. validation_error carries fields and issues (one { path, message } per failing field); repository_already_linked carries the app that holds the repository.
  • action appears when there is a concrete next step the caller can offer — connect_github or install_github_app.
  • statusCode, error and code are deprecated aliases kept for existing clients. code holds the same value as error_code. They will be removed; do not write new code against them.

The same contract, with its own catalogue, applies to Faable Auth.

With the SDK

@faable/deploy-sdk throws a FaableApiError on any non-2xx, and every generated method lists the codes it can throw in its docstring:

import { FaableApiError, FaableDeployApi } from '@faable/deploy-sdk' try { await deploy.appLinkRepository(app_id, { repository }) } catch (e) { if (e instanceof FaableApiError) { switch (e.error_code) { case 'github_identity_missing': return redirectTo('/connect/github') case 'repository_already_linked': return `Already linked to ${e.details?.app_name}.` case 'github_installation_missing': return redirectTo(installUrl) } } throw e }

ApiErrorCode is exported as a TypeScript union, so a switch over it is checked by the compiler.

Errors worth handling by name

Most codes are self-explanatory from the table below, but these four change what your integration should do:

CodeWhat to do
github_identity_missing (412)Send the user to connect GitHub. action says so too.
github_installation_missing (412)Send them to install the Faable GitHub App.
repository_already_linked (409)Offer the app that already holds it — details.app_id and details.app_name.
app_disabled / moderation_hold (409/403)Stop retrying. The app is held, and a deploy will not succeed until it is released.

The OpenAPI document

Every operation in /docs/json documents its error responses: one entry per status, with error_code restricted to the codes that operation can emit. Code generators pick them up from there.

All codes

Generated from the server’s catalogue (npm run dump-error-codes); the same list the OpenAPI ApiErrorCode schema enumerates.

CodeStatusMeaning
already_exists400A resource with the same unique field already exists. details.fields names the field.
ambiguous_app_for_repository400Several apps are linked to that repository; pass --app-slug.
app_secrets_only400Only app secrets can be written in bulk.
bad_request400The request is malformed. message says what is wrong.
branch_missing400The deploy branch does not exist on the repository. message lists the branches that do.
deploy_mode_conflict400The app deploys from its own workflow, so the platform will not create a deployment for it.
domain_invalid400Not a valid hostname. No protocol, port or path.
invalid_expand400An ?expand= path is not allowed on this resource.
invalid_id400The id in the path is not a valid id for this resource.
invalid_installation_id400The installation_id is not a number.
invalid_json400The request body is not valid JSON.
invalid_project_name400The project name is not acceptable. message says why.
invalid_query400The ?query= FaableQL expression could not be parsed.
invalid_root_dir400The Root Directory must be a path inside the repository — no leading / and no ...
invalid_secret_context400Secrets are scoped to an app or to a profile, nothing else.
invalid_secret_name400Secret names are 1-255 printable ASCII characters and cannot contain ’=’.
missing_github_event400The x-github-event header is missing.
no_deploy_workflow400No workflow in the repository runs faable deploy.
no_workflows400The repository has no GitHub Actions workflows, so a CI gate would never open.
owner_not_removable400The project owner cannot be removed. Delete the project instead.
repository_empty400The repository has no commits on the deploy branch yet.
repository_not_linked400The app has no linked repository.
root_dir_mismatch400The workflow deploys from one directory and platform builds would run from another. Declare rootDir in faable.json.
root_dir_not_found400The Root Directory does not exist on that ref of the repository.
search_not_supported400This resource does not support ?q=.
team_required400The operation needs a project: send it in the x-faable-team header.
user_not_found400No user with that id in Faable Auth.
validation_error400The body, query or path failed schema validation. details.issues lists each failing field.
access_denied401The row exists but does not belong to the caller.
apikey_expired401The API key predates projects and must be recreated.
build_token_app_missing401The app the build token was minted for no longer exists.
build_token_expired401The build token is past its lifetime.
build_token_unusable401The deployment reached a terminal phase, so its build token no longer works.
github_token_invalid401The stored GitHub token was rejected; the user must reconnect.
invalid_apikey401The API key does not exist or has been revoked.
invalid_build_token401The build token is not valid for this deployment.
invalid_token401The bearer token is missing, malformed or expired.
oidc_untrusted401The GitHub Actions OIDC token was rejected. message says why.
secrets_access_denied401The caller may not read or write the secrets of that app or profile.
team_or_scope_required401Listing needs either a project in x-faable-team or a global scope on the token.
unauthorized401The request carries no valid credentials.
user_token_required401The endpoint acts on behalf of a person and the credential carries no user (an API key or a service account).
payment_required402The feature is not included in the current plan.
project_plan_limited402The Free plan project cap is reached. Upgrade to create more.
app_repository_mismatch403The app does not exist or is not linked to the repository the token comes from.
domain_reserved403Hostnames under that domain are assigned by Faable.
forbidden403The credentials are valid but do not allow this operation.
github_installation_suspended403The GitHub App installation is suspended.
github_permission_denied403The installation lacks a permission this operation needs.
github_repository_blocked403GitHub refuses to serve the source of that repository (403 or 451).
moderation_hold403The project contains an app disabled by moderation and cannot be deleted. Contact support@faable.com.
owner_only403Only the project owner can manage collaborators.
plan_required403The build or feature needs a higher plan. message says which.
status_forbidden_field403That field of a deployment status is platform bookkeeping. message names it.
status_forbidden_phase403That phase is set by the platform, not by the client.
status_forbidden_resource403Only a platform service writes app or domain status.
superadmin_only403The operation is restricted to platform staff.
user_suspended403The account is suspended. Contact support@faable.com.
app_not_found404No app with that id or slug in this project.
collaborator_not_found404That user is not a collaborator on the project.
github_repository_not_found404The repository does not exist or the installation cannot see it.
no_app_for_repository404No app is linked to that repository.
not_found404The resource does not exist, or not in the caller’s project.
project_not_found404No project with that id for this caller.
waf_rule_not_found404No WAF rule with that pattern on this app.
method_not_allowed405The HTTP method is not supported on this path.
app_disabled409The app is disabled, so it cannot be deployed or provisioned. message says why when the reason is public.
artifact_deploy_disabled409Artifact deploys are not enabled on this environment.
conflict409The request conflicts with the current state of the resource.
deploy_refused409The deploy was refused by quota, plan or an in-flight deployment. message says which.
domain_taken409That hostname is already registered on Faable.
remote_build_disabled409Remote builds are off for this environment or this app. Deploy with a local build instead.
repository_already_linked409The repository is already linked to another app. details.app_id / details.app_name name it.
transfer_refused409The app cannot be moved to that project. message lists the blockers.
gone410The resource existed and is gone for good; retrying will not bring it back.
github_identity_missing412The user has not connected GitHub. Send them through connect_github.
github_installation_missing412The Faable GitHub App is not installed on any repository of theirs.
github_installation_not_found412The installation id is unknown or the App was uninstalled.
artifact_too_large413The artifact exceeds the size allowed for the plan.
payload_too_large413The request body exceeds the size limit.
unsupported_media_type415The Content-Type is not accepted by this endpoint.
unprocessable_entity422The request is well-formed but cannot be processed.
app_rate_limited429A new account may only create a few apps in its first 24 hours.
project_rate_limited429A new account may only create a few projects in its first hours. It opens with time, or with a plan.
too_many_requests429Rate limit exceeded. Honour the Retry-After header.
internal_error500Unexpected server error. Retry later; the request id is logged.
bad_gateway502An upstream the API depends on failed.
github_error502GitHub failed upstream. message carries what it said.
metrics_unavailable503Traffic metrics are temporarily unavailable — this is not an app with no traffic.
service_unavailable503The feature is not configured on this environment, or is temporarily out of service.

Last updated on