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
    HomeDocumentationLoad your catalog
    Previous
    Quickstart
    Next
    Tracking Events

    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.

    Getting started

    Load your catalog

    Before lehnz can recommend anything, it needs to know what your items are and who your users are. Do the initial bulk load from the dashboard, then keep things in sync with the upsert API.

    Item schema

    FieldRequiredDescription
    item_idYesUnique within your org. Used as the primary key for recommendations.
    item_typeNoDefaults to 'product'. Use to distinguish content types if you have multiple (e.g., 'video', 'article').
    statusNoactive | inactive | deleted. Defaults to active. Inactive items are not recommended.
    attributesNoFree-form JSON object. Include any fields meaningful to your domain. Richer attributes improve recommendation quality.
    catalog.csv
    item_id,name,price,category,status
    PROD-001,Wireless Headphones,149.99,Electronics,active
    PROD-002,Running Shoes,89.99,Footwear,active
    PROD-003,Coffee Maker,59.99,Kitchen,active
    PROD-004,Yoga Mat,29.99,Fitness,inactive
    catalog.json
    [
    {
    "item_id": "PROD-001",
    "item_type": "product",
    "status": "active",
    "attributes": {
    "name": "Wireless Headphones",
    "price": 149.99,
    "category": "Electronics",
    "brand": "Acoustix"
    }
    },
    {
    "item_id": "PROD-002",
    "item_type": "product",
    "status": "active",
    "attributes": { "name": "Running Shoes", "price": 89.99, "category": "Footwear" }
    }
    ]

    In CSV, columns beyond item_id, item_type, and status are merged into attributes automatically.

    User schema

    FieldRequiredDescription
    user_idYesYour user identifier. Must match the user_id you send in events.
    attributesNoFree-form JSON. Email, age, geography, interests — anything that helps personalize.
    users.csv
    user_id,email,age,interests
    usr_001,alice@example.com,32,"tech,music"
    usr_002,bob@example.com,45,"sports"
    usr_003,carol@example.com,28,"books,travel"

    Bulk import from the dashboard

    Use bulk import for the initial load and any full-catalog refresh. There is no public API for file uploads — the dashboard handles auth, validation, and storage handoff for you.

    1

    Open the dashboard

    Sign in and head to Data Ingestion. Pick Items or Users depending on what you're loading.

    2

    Drop your file

    Drag a CSV or JSON file (max 50 MB) onto the upload area. The dashboard validates the schema before submission and reports any malformed rows up front.

    3

    Wait for processing

    The file is stored in our managed object storage and the recommendation engine picks it up on its next ingestion cycle (typically a few minutes). The dashboard shows the status of each import.

    Imported ≠ live

    "Done" in the dashboard means the file is queued for the recommendation engine. Recommendations reflect the new data once that cycle completes — usually within minutes.

    For very large catalogs, split into multiple files and upload them sequentially. Each file is processed independently.

    Incremental upsert

    Use upsert when individual rows change — a price drops, a new product launches, a user updates their profile. Send an array of just the changed records. Existing fields not present in the payload are preserved; attributes is shallow-merged.

    POST/api/v1/items/upsert
    POST/api/v1/users/upsert

    Both endpoints require a secret key. The body can be either a single record or an array of records:

    // single record
    { "item_id": "sku-1", "status": "active" }
    
    // or an array
    [ { "item_id": "sku-1" }, { "item_id": "sku-2" } ]
    
    terminal
    curl -X POST https://ingestion.lehnz.com/api/v1/items/upsert \
    -H "X-API-KEY: lehnz_sk_YOUR_SECRET_KEY" \
    -H "Content-Type: application/json" \
    -d '[
    { "item_id": "PROD-001", "status": "inactive" },
    { "item_id": "PROD-005", "attributes": { "name": "New Backpack", "price": 79.99 } }
    ]'

    Upserts are queued and applied within seconds. There's no upper bound on array size, but keep individual requests under 1 MB for predictable latency. For very large incremental loads (10k+ rows), prefer the dashboard bulk import above.

    Lifecycle and deletion

    Items and users carry a status field that tells the recommendation engine how to treat them:

    StatusMeaning
    active (default)Eligible for recommendations.
    inactiveHidden from new recommendations; historical events still resolve (e.g. for analytics, attribution). Use for out-of-stock SKUs, paused users, or anything you want to come back later.
    deletedSoft-deleted; excluded everywhere except event-history joins. Use for permanent removals. The recommendation engine treats deleted items/users as gone for good.

    status is optional on every upsert; if you omit it on a new record, the platform treats it as active.

    Removing an item from your catalog

    Don't delete the row — upsert it with status: "deleted":

    terminal
    curl -X POST https://ingestion.lehnz.com/api/v1/items/upsert \
    -H "X-API-KEY: lehnz_sk_..." \
    -H "X-Domain: commerce" \
    -H "Content-Type: application/json" \
    -d '{ "item_id": "sku-123", "status": "deleted" }'

    Deleting a user (GDPR / "delete my data")

    Same shape — upsert the user with status: "deleted":

    terminal
    curl -X POST https://ingestion.lehnz.com/api/v1/users/upsert \
    -H "X-API-KEY: lehnz_sk_..." \
    -H "X-Domain: commerce" \
    -H "Content-Type: application/json" \
    -d '{ "user_id": "user-42", "status": "deleted" }'

    The platform records the deletion request as a new entry in your tenant's user ledger. Downstream processing scrubs the PII from the live state while preserving the user's identity for event-history joins (so existing analytics on past behavior still resolve).

    Unknown status values are rejected

    Sending an unknown status value (e.g. "archived", "banned") returns 400 Bad Request. The accepted values are active, inactive, deleted — nothing else.

    Errors

    StatusCauseFix
    400Record missing primary key (item_id or user_id)Every record must include a non-empty primary key.
    401Missing or invalid X-API-KEYSee API keys.
    413Payload too largeSplit into smaller batches (under 1 MB recommended).
    429Rate limit exceededSee Retries & rate limits.

    For dashboard import errors, the UI surfaces the failing rows directly. See Errors & status codes for the full catalog.

    What's next

    Tracking events

    Wire up behavioral tracking once your catalog is loaded.

    Recommendations

    Fetch and display personalized item lists.