Reference

Error Codes

All errors return JSON · Machine-readable error field on every response

Every error response from the Normalyze API returns a consistent JSON object. Click any error to expand details and see an example response and fix.

Error response format

"detail": {
  "error": "error_code_slug",
  "message": "Human-readable description.",
  "docs": "https://normalyze.dev/errors"
}
2xx Success
200 OK — normalisation successful

The batch was processed successfully. Check uncategorized_count in the response — a high number may indicate your transaction strings have unusual formatting.

{ "processed": 3, "results": [...], "uncategorized_count": 0 }
201 Created — API key generated

A new API key was created successfully. The raw key is returned once and cannot be retrieved again.

{ "api_key": "nyz_live_...", "message": "Save your API key — it will not be shown again." }

Store the key immediately in a secrets manager or environment variable.

4xx Client errors
401 Unauthorized — invalid or missing API key

The x-api-key header is missing, malformed, or the key has been revoked.

{ "detail": { "error": "invalid_api_key", "message": "API key is missing, invalid, or has been revoked." } }

Ensure you're sending the header x-api-key: nyz_live_... on every request. Keys starting with anything other than nyz_live_ will always fail.

409 Conflict — API key already exists for email

An active API key already exists for the email address you provided. One key per email is allowed on the free tier.

{ "detail": "An active API key already exists for user@example.com. Revoke it first." }

Call POST /v1/keys/revoke with your email first, then generate a new key. Or use the existing key — check GET /v1/keys/usage to retrieve your prefix.

422 Unprocessable Entity — validation failure

The request body failed Pydantic validation. Common causes: missing required fields, wrong types, empty transactions array, or exceeding the 500-transaction limit per batch.

{ "detail": [{ "loc": ["body", "transactions"], "msg": "List should have at least 1 item", "type": "too_short" }] }

Check the loc field to identify which field failed. Ensure transactions is a non-empty array with at most 500 items, and each item has a non-empty raw string.

422 Unprocessable Entity — CSV missing required column

The uploaded CSV is missing a raw column. This is the only required column — id, amount, and timestamp are optional.

{ "detail": "CSV must contain a 'raw' column." }

Ensure your CSV has a header row with at least a raw column. Indian bank exports often call it Narration or Description — rename it to raw before uploading.

429 Too Many Requests — free tier limit exceeded

Your free tier allowance of 500 transactions has been exhausted. This limit is lifetime, not monthly — it applies until you upgrade to a paid plan.

{ "detail": "Free tier limit of 500 transactions exceeded (500 used). Upgrade to a paid plan to continue." }

Upgrade via Stripe Checkout (link on the pricing page) to continue with unlimited metered usage at $0.005 per transaction.

5xx Server errors
500 Internal Server Error — processing failed

An unexpected error occurred during normalisation. This is rare and typically indicates a malformed input that bypassed validation, or a transient infrastructure issue.

{ "detail": "Processing error: ..." }

Retry the request once. If the error persists, isolate the problematic transaction string and contact support@normalyze.dev with the full request body.

For AI agents

All error responses are machine-readable JSON objects. Recommended handling: