> ## Documentation Index
> Fetch the complete documentation index at: https://docs.journeybee.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Error codes and handling in the Journeybee API

# Error Handling

The API uses standard HTTP status codes and returns errors in a consistent JSON format.

## Error response format

```json theme={null}
{
  "error": {
    "code": "not_found",
    "message": "Partner not found"
  }
}
```

## Error codes

| HTTP Status | Code                  | Description                                                             |
| ----------- | --------------------- | ----------------------------------------------------------------------- |
| `401`       | `unauthorized`        | Missing or invalid API key                                              |
| `403`       | `forbidden`           | API key lacks required permission (read or write)                       |
| `403`       | `module_disabled`     | The requested module is not enabled for your account                    |
| `404`       | `not_found`           | The requested resource does not exist                                   |
| `409`       | `conflict`            | The action conflicts with existing data (e.g., deleting a stage in use) |
| `400`       | `validation_error`    | Request body or query parameters failed validation                      |
| `429`       | `rate_limit_exceeded` | Too many requests — wait and retry                                      |
| `500`       | `internal_error`      | Unexpected server error                                                 |

## Handling errors

Check the HTTP status code first, then use `error.code` for programmatic handling:

```javascript theme={null}
const response = await fetch("https://api.journeybee.io/v1/partners", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "Acme Corp" }),
});

if (!response.ok) {
  const { error } = await response.json();

  switch (error.code) {
    case "unauthorized":
      // Re-authenticate or check API key
      break;
    case "validation_error":
      // Fix request body based on error.message
      break;
    case "rate_limit_exceeded":
      // Wait for Retry-After header duration
      break;
    default:
      // Log and retry or alert
      break;
  }
}
```

## Validation errors

When request validation fails, the `message` field describes which fields are invalid:

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "body/name: Required"
  }
}
```

## Conflict errors

Delete operations on configuration resources (stages, tiers, categories, tags, custom fields) return `409 Conflict` if the resource is currently in use:

```json theme={null}
{
  "error": {
    "code": "conflict",
    "message": "Stage \"Active\" is in use by 12 record(s) and cannot be deleted"
  }
}
```

Reassign the records to a different stage/tier/category before retrying the delete.

## Custom field validation

Creating or updating a lead, deal, or partner with custom field values —
whether inline on the entity itself, or via `POST`/`PATCH`/`DELETE
/v1/custom-field-values` and the per-entity custom-field endpoints (`PUT`/
`PATCH`/`DELETE /v1/leads/:uuid/custom-fields`, `.../deals/:uuid/custom-fields`,
`.../partners/:uuid/custom-fields`) — is validated server-side against each
field's `required` flag, its `partner_type` scope, and any active custom
field rules (visibility, locking, auto-population). A violation returns a
`400` `validation_error` with a `details` array — one entry per rejected
field:

| Field     | Description                                                   |
| --------- | ------------------------------------------------------------- |
| `field`   | The custom field's UUID                                       |
| `label`   | The custom field's label (omitted when it can't be disclosed) |
| `reason`  | A machine-readable reason code (see below)                    |
| `message` | A human-readable explanation                                  |

`reason` is one of:

| Reason                  | Meaning                                                                        |
| ----------------------- | ------------------------------------------------------------------------------ |
| `missing_required`      | A required field (visible under current rule state) was not supplied on create |
| `required_cannot_blank` | An update attempted to clear a required field that currently has a value       |
| `hidden_by_rule`        | A value was submitted for a field a rule currently hides                       |
| `locked_by_rule`        | A value was submitted for a field a rule locks to a different value            |
| `not_accessible`        | The field exists but isn't available on this surface                           |
| `unknown_field`         | No custom field matches the given UUID                                         |
| `invalid_value`         | The submitted value doesn't match the field's type                             |
| `invalid_option`        | A `select`/`multi_select` value references an option that doesn't exist        |

### Deleting a custom field value

`DELETE` is treated as submitting an empty value for the field, so the same
checks apply:

* Deleting a **visible, required** value returns `required_cannot_blank` —
  clearing it would leave the entity without a value a required field
  demands.
* Deleting a value that's currently **locked by an active custom field rule**
  returns `locked_by_rule` — the value can't be changed (including to empty)
  while the rule that sets it is active.
* Deleting a **hidden** field's value, or a value with no active constraints,
  succeeds normally (`204`).

Example response (`400`):

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "\"Country\" is required; \"Renewal Terms\" is only visible when \"Deal Type\" is \"Enterprise\"",
    "details": [
      {
        "field": "b3f1c2a0-1111-4a2b-9c3d-000000000001",
        "label": "Country",
        "reason": "missing_required",
        "message": "\"Country\" is required"
      },
      {
        "field": "b3f1c2a0-2222-4a2b-9c3d-000000000002",
        "label": "Renewal Terms",
        "reason": "hidden_by_rule",
        "message": "\"Renewal Terms\" is only visible when \"Deal Type\" is \"Enterprise\""
      }
    ]
  }
}
```
