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

# API Error Codes

> Structured API error responses, error codes, and HTTP status reference for Blnk Core 0.15.0 and later.

export const CtaCallout = props => {
  const {title, buttonLabel, href, trackingEvent, buttonTarget, rel = "noopener noreferrer", children} = props;
  const handleCtaClick = () => {
    if (typeof window === "undefined" || !trackingEvent) {
      return;
    }
    try {
      window.dispatchEvent(new CustomEvent("blnk:docs-cta", {
        detail: {
          name: trackingEvent,
          href
        }
      }));
    } catch {}
    try {
      window.posthog?.capture?.(trackingEvent, {
        href
      });
    } catch {}
    const gaPayload = {
      cta_href: href
    };
    try {
      window.gtag?.("event", trackingEvent, gaPayload);
    } catch {}
    try {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: trackingEvent,
        ...gaPayload
      });
    } catch {}
  };
  const isExternal = typeof href === "string" && (/^https?:\/\//i).test(href);
  const target = buttonTarget ?? (isExternal ? "_blank" : undefined);
  const linkRel = isExternal ? rel : undefined;
  return <section className="cta-callout not-prose relative my-8 w-full min-w-0 overflow-hidden rounded-xl border border-zinc-200 p-5 dark:border-white/10">
      <div className="cta-callout-noise" aria-hidden="true" />
      <div className="cta-callout-layout">
        {title ? <div className="cta-callout-title-row">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" width="14" height="14" className="cta-callout-icon shrink-0 text-zinc-800 dark:text-zinc-200" aria-hidden="true">
              <g fill="none" fillRule="nonzero">
                <path d="M28 0v28H0V0h28ZM14.691833333333335 27.134333333333334l-0.012833333333333334 0.0023333333333333335 -0.08283333333333333 0.04083333333333334 -0.023333333333333334 0.004666666666666667 -0.016333333333333335 -0.004666666666666667 -0.08283333333333333 -0.04083333333333334c-0.011666666666666667 -0.004666666666666667 -0.022166666666666668 -0.0011666666666666668 -0.028000000000000004 0.005833333333333334l-0.004666666666666667 0.011666666666666667 -0.019833333333333335 0.49933333333333335 0.005833333333333334 0.023333333333333334 0.011666666666666667 0.015166666666666667 0.12133333333333333 0.08633333333333333 0.0175 0.004666666666666667 0.014000000000000002 -0.004666666666666667 0.12133333333333333 -0.08633333333333333 0.014000000000000002 -0.018666666666666668 0.004666666666666667 -0.019833333333333335 -0.019833333333333335 -0.4981666666666667c-0.0023333333333333335 -0.011666666666666667 -0.0105 -0.019833333333333335 -0.019833333333333335 -0.021Zm0.3091666666666667 -0.13183333333333336 -0.015166666666666667 0.0023333333333333335 -0.21583333333333335 0.1085 -0.011666666666666667 0.011666666666666667 -0.0035000000000000005 0.012833333333333334 0.021 0.5016666666666667 0.005833333333333334 0.014000000000000002 0.009333333333333334 0.008166666666666668 0.23450000000000004 0.1085c0.014000000000000002 0.004666666666666667 0.026833333333333334 0 0.03383333333333334 -0.009333333333333334l0.004666666666666667 -0.016333333333333335 -0.03966666666666667 -0.7163333333333334c-0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.023333333333333334 -0.023333333333333334 -0.025666666666666667Zm-0.8341666666666667 0.0023333333333333335a0.026833333333333334 0.026833333333334334 0 0 0 -0.0315 0.007000000000000001l-0.007000000000000001 0.016333333333333335 -0.03966666666666667 0.7163333333333334c0 0.014000000000000002 0.008166666666666668 0.023333333333333334 0.019833333333333335 0.028000000000000004l0.0175 -0.0023333333333333335 0.23450000000000004 -0.1085 0.011666666666666667 -0.009333333333333334 0.004666666666666667 -0.012833333333333334 0.019833333333333335 -0.5016666666666667 -0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.011666666666666667 -0.21466666666666667 -0.10733333333333334Z" strokeWidth="1.1667" />
                <path fill="currentColor" d="M14 2.916666666666667A1.75 1.75 0 0 1 15.750000000000002 4.666666666666667v6.302333333333334L21.207666666666668 7.816666666666667a1.75 1.75 0 0 1 1.75 3.031L17.5 14l5.457666666666667 3.151166666666667a1.75 1.75 0 0 1 -1.75 3.031l-5.457666666666667 -3.1500000000000004V23.333333333333336a1.75 1.75 0 0 1 -3.5 0v-6.302333333333334L6.792333333333334 20.183333333333337a1.75 1.75 0 1 1 -1.75 -3.031L10.5 14 5.042333333333334 10.848833333333333a1.75 1.75 0 0 1 1.75 -3.031l5.457666666666667 3.1500000000000004V4.666666666666667A1.75 1.75 0 0 1 14 2.916666666666667Z" strokeWidth="1.1667" />
              </g>
            </svg>
            <p className="cta-callout-title min-w-0 font-semibold text-zinc-800 dark:text-zinc-200">
              {title}
            </p>
          </div> : null}
        <div className={`cta-callout-body text-sm leading-normal text-zinc-800 dark:text-zinc-200${title ? " cta-callout-body--indented" : ""}`}>
          {children}
        </div>
        <a href={href} target={target} rel={linkRel} onClick={handleCtaClick} data-docs-cta={trackingEvent || undefined} className="cta-callout-button inline-flex items-center justify-center gap-1 rounded-full bg-white px-3 py-1.5 text-sm font-semibold transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 dark:bg-white dark:hover:bg-zinc-200">
          {buttonLabel}
          <span className="cta-callout-button-arrow" aria-hidden="true">
            →
          </span>
        </a>
      </div>
    </section>;
};

<Info>Available from Blnk Core 0.15.0.</Info>

Blnk returns a stable `error_detail.code` on API errors. Use this code for programmatic handling.

Treat `error`, `errors`, and `error_detail.message` as display text only.

***

## Error response shape

Error responses include `error_detail` and `error` (or `errors` on some list and filter endpoints):

```json Error response theme={"system"}
{
  "error": "transaction not found",
  "error_detail": {
    "code": "TXN_NOT_FOUND",
    "message": "transaction not found",
    "details": {}
  }
}
```

| Field                  | Description                                                                                          |
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `error_detail.code`    | Stable code from the catalog below. Branch on this field, not on message text or `error` / `errors`. |
| `error_detail.message` | Human-readable message. May change between releases.                                                 |
| `error_detail.details` | Optional structured context (filter parse errors, reindex progress, batch IDs, and similar).         |
| `error` or `errors`    | Exist for backwards compatibility and display.                                                       |

<Note>
  Unclassified server failures return HTTP `500` with the sanitized message `"internal server error"`. The underlying error is logged server-side and is not echoed to clients.
</Note>

***

## How to handle errors

Handle Blnk API errors in two steps:

1. Use the HTTP status code to understand the type of failure.
2. Use `error_detail.code` to decide what your application should do.

```js Recommended flow wrap expandable theme={"system"}
const body = await response.json();

if (!response.ok) {
  const code = body.error_detail?.code;

  switch (code) {
    case "TXN_NOT_FOUND":
      // Show a not found state or retry with a valid transaction ID.
      break;
    case "TXN_DUPLICATE_REFERENCE":
      // Treat as a conflict. You may fetch the existing transaction by reference.
      break;
    case "TXN_INSUFFICIENT_FUNDS":
      // Ask the user to fund the source balance or choose another source.
      break;
    default:
      // Fall back to status-based handling.
      break;
  }
}
```

When debugging, log:

* HTTP status `code`
* `error_detail.code`
* `error_detail.details` when present

These fields give you the clearest context without depending on message text.

<Warning>
  Do not build logic around `error`, `errors`, or `error_detail.message`. These fields are meant for display and may change between releases.
</Warning>

***

## Error-code catalog

Codes are domain-prefixed. Each code has one default HTTP status.

### Platform

<Tabs>
  <Tab title="Generic">
    Errors that apply across endpoints: malformed requests, validation failures, rate limits, lock contention, and unexpected server failures. Codes use the `GEN_*` prefix.

    | Code                    | HTTP | Meaning                                                                         |
    | ----------------------- | ---- | ------------------------------------------------------------------------------- |
    | `GEN_MALFORMED_REQUEST` | 400  | Request body could not be parsed (invalid JSON, wrong types, or body too large) |
    | `GEN_VALIDATION_ERROR`  | 400  | Request failed validation (invalid params, filters, fields)                     |
    | `GEN_MISSING_PARAMETER` | 400  | A required route or query parameter is missing                                  |
    | `GEN_BAD_REQUEST`       | 400  | Request rejected; no more specific code applies                                 |
    | `GEN_NOT_FOUND`         | 404  | Resource not found; no domain-specific code applies                             |
    | `GEN_CONFLICT`          | 409  | Request conflicts with current resource state                                   |
    | `GEN_RESOURCE_LOCKED`   | 423  | A concurrent operation holds the lock; retry shortly                            |
    | `GEN_RATE_LIMITED`      | 429  | Too many requests                                                               |
    | `GEN_INTERNAL`          | 500  | Unexpected server failure; message is sanitized                                 |
  </Tab>

  <Tab title="Authentication">
    Errors from API key authentication, authorization scopes, and metrics bearer tokens. Codes use the `AUTH_*` prefix.

    | Code                            | HTTP | Meaning                                                      |
    | ------------------------------- | ---- | ------------------------------------------------------------ |
    | `AUTH_MISSING_API_KEY`          | 401  | No `X-Blnk-Key` header supplied                              |
    | `AUTH_INVALID_API_KEY`          | 401  | API key is unknown                                           |
    | `AUTH_EXPIRED_API_KEY`          | 401  | API key is expired or revoked                                |
    | `AUTH_MISSING_PRINCIPAL`        | 401  | Authenticated API key principal missing from request context |
    | `AUTH_INSUFFICIENT_PERMISSIONS` | 403  | API key lacks the required `resource:action` scope           |
    | `AUTH_UNKNOWN_RESOURCE`         | 403  | Request path maps to no known resource                       |
    | `AUTH_MASTER_KEY_REQUIRED`      | 403  | Endpoint (hooks management) requires the master key          |
    | `AUTH_CROSS_OWNER_ACCESS`       | 403  | Attempt to manage another owner's API keys                   |
    | `AUTH_SCOPE_ESCALATION`         | 403  | Attempt to grant scopes broader than the caller's            |
    | `AUTH_METRICS_TOKEN_REQUIRED`   | 401  | `/metrics` requires `Authorization: Bearer <token>`          |
    | `AUTH_INVALID_BEARER_TOKEN`     | 401  | `/metrics` bearer token mismatch                             |
    | `AUTH_METRICS_DISABLED`         | 403  | Secure mode on but no metrics bearer token configured        |
  </Tab>

  <Tab title="API keys">
    Errors when creating, listing, or validating API keys. Codes use the `APIKEY_*` prefix.

    | Code                    | HTTP | Meaning                                                          |
    | ----------------------- | ---- | ---------------------------------------------------------------- |
    | `APIKEY_NOT_FOUND`      | 404  | API key does not exist                                           |
    | `APIKEY_OWNER_REQUIRED` | 400  | `owner` is required when creating or listing with the master key |
    | `APIKEY_INVALID`        | 400  | API key payload validation failure                               |
  </Tab>
</Tabs>

### Core resources

<Tabs>
  <Tab title="Ledgers">
    Errors when creating or fetching ledgers. Codes use the `LGR_*` prefix.

    | Code            | HTTP | Meaning                                    |
    | --------------- | ---- | ------------------------------------------ |
    | `LGR_NOT_FOUND` | 404  | Ledger does not exist                      |
    | `LGR_DUPLICATE` | 409  | Ledger with this name or ID already exists |
  </Tab>

  <Tab title="Balances">
    Errors for balance lookups, historical snapshots, monitors, and validation. Codes use the `BAL_*` prefix.

    | Code                    | HTTP | Meaning                                              |
    | ----------------------- | ---- | ---------------------------------------------------- |
    | `BAL_NOT_FOUND`         | 404  | Balance does not exist                               |
    | `BAL_HISTORY_NOT_FOUND` | 404  | No balance snapshot or history at the requested time |
    | `BAL_INVALID_TIMESTAMP` | 400  | Timestamp must be ISO 8601                           |
    | `BAL_VALIDATION_ERROR`  | 400  | Balance (or linked identity) validation failure      |
    | `BAL_MONITOR_NOT_FOUND` | 404  | Balance monitor does not exist                       |
  </Tab>

  <Tab title="Transactions">
    Errors when recording, validating, committing, voiding, or bulk-processing transactions. Codes use the `TXN_*` prefix.

    | Code                         | HTTP | Meaning                                                             |
    | ---------------------------- | ---- | ------------------------------------------------------------------- |
    | `TXN_NOT_FOUND`              | 404  | Transaction, refundable transaction, or queued source was not found |
    | `TXN_INSUFFICIENT_FUNDS`     | 400  | Source balance cannot cover the transaction                         |
    | `TXN_INVALID_AMOUNT`         | 400  | Transaction amount must be positive                                 |
    | `TXN_PRECISION_NOT_INTEGER`  | 400  | `precision` must be an integer value                                |
    | `TXN_INVALID_DISTRIBUTION`   | 400  | Multi-source or destination distribution is invalid                 |
    | `TXN_DUPLICATE_REFERENCE`    | 409  | The `reference` has already been used                               |
    | `TXN_NOT_INFLIGHT`           | 400  | Transaction is not in `INFLIGHT` status                             |
    | `TXN_ALREADY_COMMITTED`      | 409  | Inflight transaction was already committed                          |
    | `TXN_ALREADY_VOIDED`         | 409  | Inflight transaction was already voided                             |
    | `TXN_COMMIT_AMOUNT_EXCEEDED` | 400  | Commit exceeds the original or remaining inflight amount            |
    | `TXN_INVALID_STATUS_ACTION`  | 400  | Inflight update status must be `commit` or `void`                   |
    | `TXN_BULK_EMPTY`             | 400  | Bulk payload contains no transactions or IDs                        |
    | `TXN_BULK_LIMIT_EXCEEDED`    | 400  | Bulk payload exceeds the per-request item limit                     |
    | `TXN_VALIDATION_ERROR`       | 400  | Transaction payload failed validation                               |
  </Tab>

  <Tab title="Identities">
    Errors for identity records and field tokenization. Codes use the `IDT_*` prefix.

    | Code                          | HTTP | Meaning                                           |
    | ----------------------------- | ---- | ------------------------------------------------- |
    | `IDT_NOT_FOUND`               | 404  | Identity does not exist                           |
    | `IDT_VALIDATION_ERROR`        | 400  | Identity payload validation failure               |
    | `IDT_FIELD_NOT_TOKENIZABLE`   | 400  | Field is not in the tokenizable set               |
    | `IDT_FIELD_ALREADY_TOKENIZED` | 409  | Field is already tokenized                        |
    | `IDT_FIELD_NOT_TOKENIZED`     | 400  | Field is not tokenized and cannot be detokenized. |
    | `IDT_FIELD_NOT_FOUND`         | 400  | Field does not exist on the identity              |
    | `IDT_TOKENIZATION_DISABLED`   | 403  | Tokenization is not configured on this server     |
  </Tab>

  <Tab title="Reconciliation">
    Errors for reconciliation runs, matching rules, and external data uploads. Codes use the `RECON_*` prefix.

    | Code                             | HTTP | Meaning                                                                                             |
    | -------------------------------- | ---- | --------------------------------------------------------------------------------------------------- |
    | `RECON_NOT_FOUND`                | 404  | Reconciliation run does not exist                                                                   |
    | `RECON_RULE_NOT_FOUND`           | 404  | Matching rule does not exist                                                                        |
    | `RECON_UPLOAD_FAILED`            | 400  | External data file upload failed because of the request.                                            |
    | `RECON_UPLOAD_PROCESSING_FAILED` | 500  | Server failed to process the uploaded file                                                          |
    | `RECON_RULE_INVALID`             | 400  | Matching rule failed validation. This can include name, criteria, operator, field, or drift errors. |
    | `RECON_MATCHING_RULES_REQUIRED`  | 400  | `matching_rule_ids` is required                                                                     |
    | `RECON_EXTERNAL_TXNS_REQUIRED`   | 400  | `external_transactions` is required                                                                 |
    | `RECON_START_FAILED`             | 500  | Reconciliation could not be started                                                                 |
  </Tab>
</Tabs>

### Other operations

<Tabs>
  <Tab title="Metadata">
    Errors when updating metadata on ledgers, balances, transactions, and other entities. Codes use the `META_*` prefix.

    | Code                      | HTTP | Meaning                                   |
    | ------------------------- | ---- | ----------------------------------------- |
    | `META_ENTITY_NOT_FOUND`   | 404  | Entity for metadata update does not exist |
    | `META_UNSUPPORTED_ENTITY` | 400  | Entity type does not support metadata     |
    | `META_INVALID_ENTITY_ID`  | 400  | Entity ID is missing or malformed         |
  </Tab>

  <Tab title="Hooks">
    Errors for transaction hook registration and management. Codes use the `HOOK_*` prefix.

    | Code                    | HTTP | Meaning                                                           |
    | ----------------------- | ---- | ----------------------------------------------------------------- |
    | `HOOK_NOT_FOUND`        | 404  | Hook does not exist                                               |
    | `HOOK_INVALID`          | 400  | Hook payload validation failure                                   |
    | `HOOK_OPERATION_FAILED` | 500  | Hook registration, update, list, or delete infrastructure failure |
  </Tab>

  <Tab title="Search">
    Errors from search queries and Typesense reindex jobs. Codes use the `SRCH_*` prefix.

    | Code                       | HTTP | Meaning                                                                  |
    | -------------------------- | ---- | ------------------------------------------------------------------------ |
    | `SRCH_QUERY_INVALID`       | 400  | Search payload is invalid                                                |
    | `SRCH_FAILED`              | 500  | Search backend failure                                                   |
    | `SRCH_REINDEX_IN_PROGRESS` | 409  | A reindex is already running. `details` carries progress when available. |
    | `SRCH_REINDEX_NOT_STARTED` | 404  | No reindex has been started                                              |
  </Tab>

  <Tab title="Admin">
    Errors from admin operations such as database backup. Codes use the `ADMIN_*` prefix.

    | Code                  | HTTP | Meaning                |
    | --------------------- | ---- | ---------------------- |
    | `ADMIN_BACKUP_FAILED` | 500  | Database backup failed |
  </Tab>
</Tabs>

### Bulk commit and void

Bulk commit and void have two types of failures:

1. Request-level failures
2. Per-item failures

Request-level failures reject the whole request, i.e. they reject the whole call before any item runs. Per-item failures are returned inside the bulk result.

<Tabs>
  <Tab title="Request-level">
    Request-level failures reject the whole request, i.e. they reject the whole call before any item runs. Per-item failures are returned inside the bulk result.

    The response applies the standard transaction error shape with codes such as `TXN_BULK_EMPTY` or `TXN_BULK_LIMIT_EXCEEDED`.

    ```json Empty batch theme={"system"}
    {
      "error": "transactions cannot be empty",
      "error_detail": {
        "code": "TXN_BULK_EMPTY",
        "message": "transactions cannot be empty"
      }
    }
    ```

    Bulk commit expects a `transactions` array. Bulk void expects `transaction_ids`. Sending the wrong shape returns `TXN_BULK_EMPTY` because unrecognized fields are dropped.
  </Tab>

  <Tab title="Per-item">
    Valid requests return HTTP `200` with `succeeded`, `failed`, and `results`. Branch on `results[].code`, not the HTTP status. Per-item codes are specific to bulk processing and are not part of the `TXN_*` catalog.

    | Code                | Meaning                                                 |
    | ------------------- | ------------------------------------------------------- |
    | `NOT_FOUND`         | Transaction ID does not exist                           |
    | `ALREADY_COMMITTED` | Inflight transaction was already committed              |
    | `ALREADY_VOIDED`    | Inflight transaction was already voided                 |
    | `NOT_INFLIGHT`      | Transaction is not in `INFLIGHT` status                 |
    | `INVALID_AMOUNT`    | Commit amount is invalid                                |
    | `LOCKED`            | Concurrent operation on the same transaction            |
    | `INTERNAL_ERROR`    | Unexpected server failure for this item                 |
    | `QUEUED`            | Item was queued for processing                          |
    | `ALREADY_QUEUED`    | A commit or void is already queued for this transaction |

    ```json Mixed batch (skip_queue: true) wrap theme={"system"}
    {
      "succeeded": 1,
      "failed": 2,
      "results": [
        {
          "transaction_id": "txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
          "status": "succeeded"
        },
        {
          "transaction_id": "txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
          "status": "failed",
          "code": "NOT_FOUND",
          "message": "Transaction with ID 'txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30' not found"
        },
        {
          "transaction_id": "txn_7c91e4b2-3d8a-4f6e-b1c2-9a8d7e6f5b4a",
          "status": "failed",
          "code": "ALREADY_COMMITTED",
          "message": "Transaction is already committed"
        }
      ]
    }
    ```

    Loop through `results` and handle failed items by code. Queued items use `status: "queued"` and do not need the same error paths.

    ```js Recommended flow wrap expandable theme={"system"}
    const { results } = await response.json();

    for (const item of results) {
      if (item.status === "succeeded") {
        continue;
      }

      if (item.status === "queued") {
        // Item was queued for processing.
        // No error handling is needed unless your application treats queued items separately.
        continue;
      }

      if (item.status === "failed") {
        switch (item.code) {
          case "NOT_FOUND":
            // Confirm the transaction ID.
            break;
          case "ALREADY_COMMITTED":
          case "ALREADY_VOIDED":
            // Treat as already processed.
            break;
          case "LOCKED":
            // Retry shortly with backoff.
            break;
          default:
            // Handle NOT_INFLIGHT, INVALID_AMOUNT, INTERNAL_ERROR, and other failures.
            break;
        }
      }
    }
    ```
  </Tab>
</Tabs>

***

## Legacy codes

Blnk normalizes older generic error names to canonical `error_detail.code` values before the response leaves the server.

You will not receive legacy error names in `error_detail.code`. To handle legacy behaviour:

* Use `error_detail.code` from the catalog above.
* Prefer domain-specific codes over generic codes. For example, a missing transaction returns `TXN_NOT_FOUND`, not `GEN_NOT_FOUND`.
* Do not parse `error`, `errors`, or `error_detail.message` to infer the code.

| Legacy internal code    | Canonical `error_detail.code` |
| ----------------------- | ----------------------------- |
| `NOT_FOUND`             | `GEN_NOT_FOUND`               |
| `CONFLICT`              | `GEN_CONFLICT`                |
| `BAD_REQUEST`           | `GEN_BAD_REQUEST`             |
| `INVALID_INPUT`         | `GEN_VALIDATION_ERROR`        |
| `INTERNAL_SERVER_ERROR` | `GEN_INTERNAL`                |
| `RATE_LIMITED`          | `GEN_RATE_LIMITED`            |

***

## Need help?

We are very happy to help you make the most of Blnk, regardless of whether it is your first time or you are switching from another tool.

To ask questions or discuss issues, please [contact us](mailto:support@blnkfinance.com) or [join our Discord community](https://discord.gg/7WNv94zPpx).

<CtaCallout title="Connect your ledger to Blnk Cloud" href="https://cloud.blnkfinance.com/auth/sign-up?utm_source=blnk_docs&utm_medium=documentation&utm_campaign=need-help" buttonLabel="Open Blnk Cloud" trackingEvent="clicked_cloud_signup">
  Sign up and manage your ledger with our back-office dashboard. You can invite teammates to collaborate and manage your ledger operations directly from the dashboard.
</CtaCallout>
