Skip to main content

Error Catalogue

Catalogue version: 1 — 15 codes

Every GraphQL error response carries an extensions block with a stable string code, the logical http_status, and a typed data payload. Switch on code rather than parsing the human-readable message, which is not part of the stable contract.

Codes

ATTRIBUTE_CONSTRAINT_VIOLATION

A node attribute value failed a schema-defined constraint (e.g. regex, length, range).

  • Stability: evolving
  • HTTP status: 422

data payload

FieldTypeRequired
node_kindstringyes
field_namestringyes
constraintstringyes
detailstring | nullno

Example response

{
"data": null,
"errors": [
{
"message": "A node attribute value failed a schema-defined constraint (e.g. regex, length, range).",
"extensions": {
"code": "ATTRIBUTE_CONSTRAINT_VIOLATION",
"http_status": 422,
"data": {
"node_kind": "example",
"field_name": "example",
"constraint": "example",
"detail": "example"
}
}
}
]
}

ATTRIBUTE_INVALID_TYPE

A node attribute received a value that does not match its declared type.

  • Stability: stable
  • HTTP status: 422

data payload

FieldTypeRequired
node_kindstringyes
field_namestringyes
expected_typestringyes
received_typestringyes

Example response

{
"data": null,
"errors": [
{
"message": "A node attribute received a value that does not match its declared type.",
"extensions": {
"code": "ATTRIBUTE_INVALID_TYPE",
"http_status": 422,
"data": {
"node_kind": "example",
"field_name": "example",
"expected_type": "example",
"received_type": "example"
}
}
}
]
}

ATTRIBUTE_REQUIRED

A mandatory node attribute was not provided.

  • Stability: stable
  • HTTP status: 422

data payload

FieldTypeRequired
node_kindstringyes
field_namestringyes

Example response

{
"data": null,
"errors": [
{
"message": "A mandatory node attribute was not provided.",
"extensions": {
"code": "ATTRIBUTE_REQUIRED",
"http_status": 422,
"data": {
"node_kind": "example",
"field_name": "example"
}
}
}
]
}

AUTHENTICATION_REQUIRED

The request requires authentication and none was provided or it was invalid.

  • Stability: stable
  • HTTP status: 401

This code carries no data payload; data is always {}.

Example response

{
"data": null,
"errors": [
{
"message": "The request requires authentication and none was provided or it was invalid.",
"extensions": {
"code": "AUTHENTICATION_REQUIRED",
"http_status": 401,
"data": {}
}
}
]
}

BRANCH_ALREADY_MERGED

The target branch has been merged and is permanently read-only.

  • Stability: stable
  • HTTP status: 400

data payload

FieldTypeRequired
branch_namestringyes

Example response

{
"data": null,
"errors": [
{
"message": "The target branch has been merged and is permanently read-only.",
"extensions": {
"code": "BRANCH_ALREADY_MERGED",
"http_status": 400,
"data": {
"branch_name": "example"
}
}
}
]
}

BRANCH_NEEDS_REBASE

The target branch must be rebased before it accepts new changes.

  • Stability: stable
  • HTTP status: 400

data payload

FieldTypeRequired
branch_namestringyes

Example response

{
"data": null,
"errors": [
{
"message": "The target branch must be rebased before it accepts new changes.",
"extensions": {
"code": "BRANCH_NEEDS_REBASE",
"http_status": 400,
"data": {
"branch_name": "example"
}
}
}
]
}

BRANCH_NOT_FOUND

The requested branch does not exist.

  • Stability: stable
  • HTTP status: 400

data payload

FieldTypeRequired
branch_namestringyes

Example response

{
"data": null,
"errors": [
{
"message": "The requested branch does not exist.",
"extensions": {
"code": "BRANCH_NOT_FOUND",
"http_status": 400,
"data": {
"branch_name": "example"
}
}
}
]
}

MERGE_IN_PROGRESS

The write was rejected because a branch merge is in progress. The block is transient; retry once the merge completes.

  • Stability: evolving
  • HTTP status: 423

data payload

FieldTypeRequired
branch_namestringyes
merging_branchstringyes

Example response

{
"data": null,
"errors": [
{
"message": "The write was rejected because a branch merge is in progress. The block is transient; retry once the merge completes.",
"extensions": {
"code": "MERGE_IN_PROGRESS",
"http_status": 423,
"data": {
"branch_name": "example",
"merging_branch": "example"
}
}
}
]
}

MERGE_RECOVERY_REQUIRED

The write was rejected because a previous branch merge failed and left the default branch protected. Recovery is required: an administrator must run infrahub recover merge. Unlike MERGE_IN_PROGRESS this is not retryable.

  • Stability: evolving
  • HTTP status: 423

data payload

FieldTypeRequired
branch_namestringyes
merging_branchstringyes

Example response

{
"data": null,
"errors": [
{
"message": "The write was rejected because a previous branch merge failed and left the default branch protected. Recovery is required: an administrator must run `infrahub recover merge`. Unlike MERGE_IN_PROGRESS this is not retryable.",
"extensions": {
"code": "MERGE_RECOVERY_REQUIRED",
"http_status": 423,
"data": {
"branch_name": "example",
"merging_branch": "example"
}
}
}
]
}

NODE_NOT_FOUND

The requested node does not exist in the database.

  • Stability: stable
  • HTTP status: 404

data payload

FieldTypeRequired
node_kindstringyes
identifierstringyes

Example response

{
"data": null,
"errors": [
{
"message": "The requested node does not exist in the database.",
"extensions": {
"code": "NODE_NOT_FOUND",
"http_status": 404,
"data": {
"node_kind": "example",
"identifier": "example"
}
}
}
]
}

PERMISSION_DENIED

The authenticated user is not permitted to perform the requested action.

  • Stability: stable
  • HTTP status: 403

data payload

FieldTypeRequired
actionstring | nullno
resource_kindstring | nullno

Example response

{
"data": null,
"errors": [
{
"message": "The authenticated user is not permitted to perform the requested action.",
"extensions": {
"code": "PERMISSION_DENIED",
"http_status": 403,
"data": {
"action": "example",
"resource_kind": "example"
}
}
}
]
}

SCHEMA_NOT_FOUND

The requested schema kind is not registered in the active schema.

  • Stability: stable
  • HTTP status: 422

data payload

FieldTypeRequired
kindstringyes

Example response

{
"data": null,
"errors": [
{
"message": "The requested schema kind is not registered in the active schema.",
"extensions": {
"code": "SCHEMA_NOT_FOUND",
"http_status": 422,
"data": {
"kind": "example"
}
}
}
]
}

TOKEN_EXPIRED

The authentication token has expired and a silent refresh is required.

  • Stability: stable
  • HTTP status: 401

data payload

FieldTypeRequired
expired_atstring (date-time) | nullno

Example response

{
"data": null,
"errors": [
{
"message": "The authentication token has expired and a silent refresh is required.",
"extensions": {
"code": "TOKEN_EXPIRED",
"http_status": 401,
"data": {
"expired_at": "2026-01-01T00:00:00Z"
}
}
}
]
}

UNDEFINED_ERROR

An error not yet covered by the catalogue. Its occurrence indicates a catalogue gap and should be triaged.

  • Stability: stable
  • HTTP status: 500

This code carries no data payload; data is always {}.

Example response

{
"data": null,
"errors": [
{
"message": "An error not yet covered by the catalogue. Its occurrence indicates a catalogue gap and should be triaged.",
"extensions": {
"code": "UNDEFINED_ERROR",
"http_status": 500,
"data": {}
}
}
]
}

UNIQUENESS_VIOLATION

The submitted values collide with an existing object on a uniqueness constraint. The constraint members may be relationships as well as attributes.

  • Stability: evolving
  • HTTP status: 422

data payload

FieldTypeRequired
node_kindstringyes
fieldsstring[]yes

Example response

{
"data": null,
"errors": [
{
"message": "The submitted values collide with an existing object on a uniqueness constraint. The constraint members may be relationships as well as attributes.",
"extensions": {
"code": "UNIQUENESS_VIOLATION",
"http_status": 422,
"data": {
"node_kind": "example",
"fields": [
"example"
]
}
}
}
]
}

For SDK and third-party consumers

The catalogue is a machine-readable JSON Schema document committed to the repository at schema/error-catalogue.json. Consumers read it from a checkout of the desired Infrahub version. The top-level infrahub_catalogue_version field tracks the wrapper shape. Within a single wrapper version, codes may be added and existing codes may gain additive optional data fields; consumers MUST treat any unrecognised code as UNDEFINED_ERROR. A wrapper-version bump signals an incompatible change to the envelope itself and follows the same deprecation policy as removing a code.

Third-party consumers

External integrators can generate their own bindings or validators directly from schema/error-catalogue.json. For example, to list every published code with jq:

jq '.codes | keys' schema/error-catalogue.json

Known gaps

  • BRANCH_NOT_FOUND and SCHEMA_NOT_FOUND currently mirror their existing REST http_status values (400/422) rather than a semantically correct 404. Aligning them is deferred to a dedicated deprecation-cycle release that bundles the REST status change, the catalogue update, and a changelog fragment.
  • Not every ValidationError raise site on the GraphQL path has been migrated to the typed ATTRIBUTE_* subclasses yet. Un-migrated sites surface as UNDEFINED_ERROR until the remaining raise sites are promoted to typed exceptions.
  • UNIQUENESS_VIOLATION reports 422 rather than a semantically correct 409 Conflict, for the same REST-parity reason as the entry above. Its path also stops at the mutation field instead of extending to the offending input, because the constraint is evaluated deep inside the write and a composite constraint has no single offending field. Use data.fields to identify the colliding members.