Docs
    GuidesAPI ReferenceBlog
    Sign inCreate account
    Overview

    Getting started

    Sign upAPI keysQuickstartLoad your catalog

    Integration

    Tracking EventsIdentity StitchingPersonalisation

    Production

    Errors & status codesRetries & rate limitsTypeScript SDKTroubleshooting

    Reference

    API ReferenceVersioningChangelog
    HomeDocumentationErrors & status codes
    Previous
    Personalisation
    Next
    Retries & rate limits

    Skip the ML, Ship the Revenue

    Product

    • How It Works
    • Features
    • For Startups
    • For Developers

    Developers

    • Documentation

    Company

    • Blog
    • Contact

    © 2026 Lehnz, Inc. All rights reserved.

    Reference

    Errors & status codes

    Every error response lehnz returns, the conditions that trigger it, and what to do about it.

    Response envelope

    All responses — successful and failed — share the same shape:

    success-response.json
    {
    "success": true,
    "data": { ... },
    "message": "..."
    }
    error-response.json
    {
    "success": false,
    "message": "Human-readable summary",
    "data": null,
    "error": "Optional detail or array of field errors"
    }

    Always check success, not just the HTTP status. message is safe to surface to end users; error is for logs and debugging.

    Status codes at a glance

    StatusMeaningRetry?
    200OK — synchronous request succeeded—
    202Accepted — queued for async processing—
    400Bad request — validation failure or malformed payloadNo (fix the request)
    401Unauthorized — credential missing, invalid, or expiredNo (fix auth)
    403Forbidden — wrong key type, insufficient role, or suspendedNo (use correct key/role)
    404Not found — resource does not exist or belongs to another tenantNo (verify ID)
    413Payload too large — file or batch exceeds limitsNo (split request)
    429Too many requests — rate limit exceededYes (with backoff)
    500Internal server errorYes (with backoff)

    lehnz does not use 422. Validation failures return 400.

    400 Bad Request

    The request was structurally invalid — missing field, wrong type, schema mismatch. Validation errors include a per-field error array:

    validation-error.json
    {
    "success": false,
    "message": "Validation Failed",
    "error": [
    { "path": "email", "message": "Invalid email" },
    { "path": "password", "message": "String must contain at least 8 character(s)" }
    ]
    }
    TriggerWhereFix
    Missing required fields on register/loginPOST /auth/register, /auth/loginProvide all required fields with valid types.
    Password shorter than 8 charactersPOST /auth/registerUse 8+ characters.
    Invalid email formatPOST /auth/register, /auth/loginEmail must match standard format.
    Inviting a user as OWNERPOST /organizations/:slug/membersInvite as ADMIN, DEVELOPER, or MEMBER. Ownership is exclusive to the org creator.
    API key name empty or > 50 charsPOST /organizations/:slug/api-keysProvide a name between 1 and 50 characters.
    Upsert payload not a valid object or arrayPOST /api/v1/items/upsert, /users/upsertBody must be a single record object or an array of records.
    Record missing primary keyPOST /api/v1/items/upsert, /users/upsertEvery record must include a non-empty primary key.

    401 Unauthorized

    The request was rejected because credentials are missing, invalid, or expired. Never retry a 401 with the same credentials — fetch new ones.

    TriggerMessageFix
    Wrong email or passwordInvalid credentialsVerify the password matches the registered account. Avoid retry loops — they trigger rate limits.
    Email not verifiedPlease verify your email address before logging in.Click the verification link sent at sign-up. Request a new one if expired.
    Verification token invalid or expiredInvalid or expired verification tokenTokens expire after 24 hours. Request a fresh email.
    X-API-KEY header missingMissing required X-API-KEY headerAdd the header. There is no Bearer prefix.
    Malformed API keyInvalid API Key formatConfirm the key starts with lehnz_pk_ or lehnz_sk_ and matches the format from the dashboard.
    API key revokedInvalid API KeyGenerate a new pair from the dashboard. Old keys cannot be reactivated.

    403 Forbidden

    The credential is valid but does not have permission for this resource or action.

    TriggerMessageFix
    Account suspendedYour account has been suspendedContact support to discuss reinstatement.
    Secret key on a public endpointSecret keys are prohibited on public endpoints. Use a Publishable key (pk_).Switch to your publishable key for events and recommendations.
    Publishable key on a non-public pathPublishable keys are restricted to events and recommendations.Use a secret key for bulk uploads and admin endpoints.
    Caller's role lacks permissionInsufficient permissions / ForbiddenAsk an OWNER or ADMIN to perform the action.
    Caller is not an active member of the orgForbiddenAccept any pending invitations first.

    404 Not Found

    The resource doesn't exist, or it exists in a different organization. lehnz does not leak existence — a 404 looks the same whether the ID is invalid or simply belongs to another tenant.

    TriggerFix
    API key ID does not exist or belongs to another orgVerify the ID in the dashboard.
    User is not a member of the organizationInvite the user before changing their role or removing them.
    No pending invitation foundAn invitation was never sent or was already accepted.
    Authenticated user has no organizationUser must be invited to or own an organization first.

    429 Too Many Requests

    You hit a rate limit. The response includes RateLimit-* headers (draft-7) you can use to compute the right backoff.

    ScopeLimitAffects
    Auth (per IP)10 requests / 15 minutes/auth/register, /auth/login
    Organization1 000 requests / 15 minutesAll authenticated API endpoints

    See Retries & rate limits for backoff strategies and how to design around the limits.

    500 Internal Server Error

    An unexpected server-side failure. The response is generic — internal details are scrubbed before they reach the client.

    500-response.json
    {
    "success": false,
    "message": "An unexpected error occurred",
    "data": null,
    "error": null
    }

    500s are exceptional — retry once with a short delay, then escalate. If you see them in volume, contact support with timestamps and any unique request IDs from your client logs.

    A defensive client

    A complete client should branch on success, log the error field, surface message to the user, and only retry 429 / 500.

    callLehnz.ts
    async function callLehnz(url: string, init?: RequestInit) {
    const res = await fetch(url, init);
    const body = await res.json();
    if (body.success) return body.data;
    console.error('lehnz error', { status: res.status, body });
    if (res.status === 429 || res.status >= 500) {
    throw new RetryableError(body.message);
    }
    throw new TerminalError(body.message);
    }

    What's next

    Retries & rate limits

    Backoff strategy, idempotency, batching guidance.

    TypeScript SDK

    A typed reference client that handles errors and retries for you.