BanklyzeBanklyze/Developer Docs
Sign In

Error Handling

The Banklyze API uses standard HTTP status codes and returns a consistent JSON error envelope on every failure. This guide covers the error format, status codes, error codes, retry strategies, and how to handle validation and rate limit errors.

Error Response Format

All error responses return a JSON body with three fields:

NameTypeRequiredDescription
detailstringRequiredHuman-readable description of what went wrong.
codestringRequiredMachine-readable error code for programmatic handling (e.g. DEAL_NOT_FOUND).
errorsarrayRequiredArray of field-level validation errors. Empty array for non-validation errors.
Error response — 404 Not Found
{
  "detail": "Deal not found",
  "code": "DEAL_NOT_FOUND",
  "errors": []
}

HTTP Status Codes

The API returns the following HTTP status codes. Successful responses use 2xx; client errors use 4xx; server errors use 5xx.

NameTypeRequiredDescription
200 OKsuccessOptionalThe request succeeded. Returned for GET, PATCH, and most POST requests.
201 CreatedsuccessOptionalA new resource was created. Returned for POST /deals, POST /deals/{id}/documents, and similar creation endpoints.
204 No ContentsuccessOptionalThe request succeeded with no response body. Returned for DELETE requests.
400 Bad Requestclient errorOptionalThe request was malformed or contained invalid JSON. Check your request body and Content-Type header.
401 Unauthorizedclient errorOptionalMissing or invalid API key. Include a valid X-API-Key header or Authorization: Bearer token.
403 Forbiddenclient errorOptionalYour API key does not have permission to perform this action. Check your key scope or contact your organization admin.
404 Not Foundclient errorOptionalThe requested resource does not exist or is not accessible with your API key's organization scope.
409 Conflictclient errorOptionalThe request conflicts with the current state of a resource (e.g. a duplicate external reference).
422 Unprocessable Entityclient errorOptionalThe request body failed schema validation or a business rule (e.g. ruleset weights do not sum to 1.0).
429 Too Many Requestsclient errorOptionalYou have exceeded your rate limit. Check the X-RateLimit-Reset header and retry after that timestamp.
500 Internal Server Errorserver errorOptionalAn unexpected error occurred on the Banklyze side. These are safe to retry with exponential backoff.

Error Codes

The code field in every error response contains a machine-readable string you can use in conditional logic. Error codes are stable across API versions.

NameTypeRequiredDescription
VALIDATION_ERROR400 / 422OptionalOne or more request fields failed schema validation. Check the errors array for field-level details.
AUTH_REQUIRED401OptionalNo authentication credential was provided. Include the X-API-Key header.
INVALID_API_KEY401OptionalThe provided API key is invalid, expired, or has been revoked.
FORBIDDEN403OptionalThe authenticated key does not have permission to perform this operation.
NOT_FOUND404OptionalGeneric not-found code when no more specific code applies.
DEAL_NOT_FOUND404OptionalNo deal with the given ID exists within your organization.
DOCUMENT_NOT_FOUND404OptionalNo document (statement) with the given ID exists for this deal.
DUPLICATE_RESOURCE409OptionalA resource with the same unique identifier already exists (e.g. duplicate external_ref in /ingest).
RATE_LIMIT_EXCEEDED429OptionalRequest rate limit hit. Wait until X-RateLimit-Reset before retrying.
QUOTA_EXCEEDED429OptionalMonthly API call or document quota exhausted. Upgrade your plan or wait for the monthly reset.
PROCESSING_FAILED422OptionalDocument processing failed in the analysis pipeline. Common causes: corrupted PDF, no extractable text, or blank pages.
WEIGHT_SUM_INVALID422OptionalRuleset factor weights do not sum to exactly 1.0. Adjust weights so they total 100%.
INVALID_DECISION422OptionalThe decision value provided to POST /deals/{id}/decision is not one of: approve, decline, fund, review.

Retry Strategy

Use exponential backoff. When retrying 429 or 5xx responses, wait before retrying and double the wait time on each subsequent failure. This prevents overwhelming the API during transient issues. A good starting point is 1s, 2s, 4s, 8s with a maximum of 4 retries.

Only 429 Too Many Requests and 5xx server errors are safe to retry automatically. Never retry 4xx errors other than 429 — they indicate a problem with the request itself that will not resolve on retry.

Python — exponential backoff retry
import time
import requests
from requests.exceptions import HTTPError

BANKLYZE_API_KEY = "bk_your_api_key"
BASE_URL = "https://api.banklyze.com/v1"


def api_request_with_retry(method: str, path: str, **kwargs) -> dict:
    """Make a Banklyze API request with exponential backoff retry."""
    url = f"{BASE_URL}{path}"
    headers = {"X-API-Key": BANKLYZE_API_KEY, **kwargs.pop("headers", {})}

    max_retries = 4
    backoff = 1  # seconds

    for attempt in range(max_retries):
        response = requests.request(method, url, headers=headers, **kwargs)

        if response.status_code == 429:
            # Respect Retry-After if present, otherwise use exponential backoff
            retry_after = response.headers.get("Retry-After")
            wait = int(retry_after) if retry_after else backoff * (2 ** attempt)
            print(f"Rate limited. Retrying in {wait}s (attempt {attempt + 1})")
            time.sleep(wait)
            continue

        if response.status_code >= 500:
            if attempt < max_retries - 1:
                wait = backoff * (2 ** attempt)
                print(f"Server error {response.status_code}. Retrying in {wait}s")
                time.sleep(wait)
                continue

        response.raise_for_status()
        return response.json()

    raise RuntimeError("Max retries exceeded")


# Usage
deals = api_request_with_retry("GET", "/deals")
bash — curl with exponential backoff
#!/usr/bin/env bash
# Retry a curl request up to 4 times with exponential backoff

API_KEY="bk_your_api_key"
MAX_RETRIES=4
BACKOFF=1

for attempt in $(seq 1 $MAX_RETRIES); do
  HTTP_STATUS=$(curl -s -o /tmp/response.json -w "%{http_code}" \
    -H "X-API-Key: $API_KEY" \
    "https://api.banklyze.com/v1/deals")

  if [ "$HTTP_STATUS" -eq 200 ]; then
    cat /tmp/response.json
    exit 0
  elif [ "$HTTP_STATUS" -eq 429 ]; then
    echo "Rate limited. Waiting ${BACKOFF}s before retry $attempt/$MAX_RETRIES..."
    sleep $BACKOFF
    BACKOFF=$((BACKOFF * 2))
  elif [ "$HTTP_STATUS" -ge 500 ]; then
    echo "Server error $HTTP_STATUS. Waiting ${BACKOFF}s..."
    sleep $BACKOFF
    BACKOFF=$((BACKOFF * 2))
  else
    echo "Non-retryable error: $HTTP_STATUS"
    cat /tmp/response.json
    exit 1
  fi
done

echo "Max retries exceeded"
exit 1

Validation Errors

When the request body fails Pydantic schema validation, the API returns 422 Unprocessable Entity with a populated errors array. Each entry in the array describes one invalid field:

NameTypeRequiredDescription
locstring[]OptionalPath to the invalid field. The first element is the source ("body", "query", "path") and subsequent elements are the field name(s).
msgstringOptionalHuman-readable description of the validation failure.
typestringOptionalPydantic error type code (e.g. "missing", "string_too_short", "float_parsing").
Response — 422 Unprocessable Entity (validation)
{
  "detail": "Validation failed",
  "code": "VALIDATION_ERROR",
  "errors": [
    {
      "loc": ["body", "business_name"],
      "msg": "Field required",
      "type": "missing"
    },
    {
      "loc": ["body", "funding_amount_requested"],
      "msg": "Input should be a valid number",
      "type": "float_parsing"
    }
  ]
}

Iterate over errors to surface field-level messages in your application UI. The loc path lets you map each error back to the input field that caused it.

Rate Limiting

Every API response includes three rate limit headers. Monitor these headers to avoid hitting the limit:

NameTypeRequiredDescription
X-RateLimit-LimitheaderOptionalMaximum number of requests allowed per minute for your API key.
X-RateLimit-RemainingheaderOptionalNumber of requests remaining in the current 60-second window.
X-RateLimit-ResetheaderOptionalUnix timestamp (seconds) when the current rate limit window resets and the remaining count returns to the limit.
429 response with rate limit headers
HTTP/2 429
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1741132800
Content-Type: application/json

{
  "detail": "Rate limit exceeded. Try again in 23 seconds.",
  "code": "RATE_LIMIT_EXCEEDED",
  "errors": []
}
Python — reading rate limit headers
import os
import time
import requests

def check_rate_limit_headers(response: requests.Response) -> None:
    """Log rate limit status from response headers."""
    limit = response.headers.get("X-RateLimit-Limit")
    remaining = response.headers.get("X-RateLimit-Remaining")
    reset_ts = response.headers.get("X-RateLimit-Reset")

    if limit and remaining and reset_ts:
        reset_dt = time.strftime("%H:%M:%S", time.localtime(int(reset_ts)))
        print(f"Rate limit: {remaining}/{limit} remaining, resets at {reset_dt}")

        # Warn when running low
        if int(remaining) < 10:
            print("Warning: fewer than 10 requests remaining in this window")

resp = requests.get(
    "https://api.banklyze.com/v1/deals",
    headers={"X-API-Key": os.environ["BANKLYZE_API_KEY"]},
)
check_rate_limit_headers(resp)
Rate limits vary by plan: Free (30 rpm), Starter (120 rpm), Pro (600 rpm), Enterprise (custom). The X-RateLimit-Limit header always reflects your current plan limit. Check your plan in the Banklyze dashboard under Settings → Billing.