Skip to content

    Pre-launch API. The concepts described here are settled, but the API shape is not: request and response fields, parameters and defaults can still change. Build against it, and talk to your Luigi's Box contact before you put an integration into production.

    Account structure

    View source

    Every call you make names at least one of these entities. Most accounts are set up once with Luigi’s Box and rarely change shape afterwards, but reading them — and adding a market or a placement — is something you can do yourself.

    The domain model explains what these entities are. This page is about working with them over the API.

    Organization lbo_
    └── Catalog lbc_
    ├── Channel lbn_
    └── Surface lbs_<intent>_<slug>
    Tag lbt_ (groups catalogs, channels or surfaces)

    Everything here is under /platform/v1/, needs a token for the https://api.<region>.luigisbox.ai/platform audience, and a grant to read or manage kernel entities — reading, writing and deleting are granted separately.

    One call returns the whole tree your credentials can reach. It is paginated by organization and defaults to 20, so ask for a page large enough to hold it:

    Terminal window
    curl -G 'https://api.eu1.luigisbox.ai/platform/v1/me/entitlements' \
    -d 'size=1000' \
    -H 'Authorization: Bearer <token>'

    Everything your credentials can reach, nested:

    {
    "items": [
    {
    "id": "lbo_r2vn8c",
    "name": "Example Retail",
    "catalogs": [
    {
    "id": "lbc_8w3k2p",
    "name": "Example — EN",
    "language": "en_GB",
    "channels": [{ "id": "lbn_4hj9tv", "name": "example.com" }],
    "surfaces": [
    { "id": "lbs_search_main", "surface_kind": "search", "slug": "main" }
    ]
    }
    ]
    }
    ],
    "page": 1,
    "size": 1000,
    "total": 1
    }

    The response is scoped to your own credentials — it lists exactly what you are entitled to and nothing else — so it answers which catalog to index into and which channel to search on.

    Paginated by organization with page and size, in the same envelope every list on the API uses: one items entry is one organization, with its whole subtree nested inside, and total counts organizations. size defaults to 20 and accepts up to 1000 here — higher than the usual limit of 100, so a token entitled to many organizations can read the whole tree in one call.

    Your company. The billing entity and the root of everything else.

    GET /platform/v1/organizations
    POST /platform/v1/organizations
    GET /platform/v1/organizations/{organization_id}
    PATCH /platform/v1/organizations/{organization_id}
    DELETE /platform/v1/organizations/{organization_id}

    An organization has a name and nothing else to configure. Most integrations only ever read this.

    A catalog is one set of objects in one language, with its own settings. Separate catalogs are how you handle separate languages or regions.

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/catalogs' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{
    "organization_id": "lbo_r2vn8c",
    "name": "Example — German",
    "language": "de_DE"
    }'
    FieldRequiredNotes
    organization_idYes
    nameYes
    languageYesxx_XX — a lowercase language, an underscore, an uppercase region
    settingsNoCatalog-level settings
    seed_surface_templatesNoTemplates to seed the catalog’s first surfaces from

    The language decides how text is analyzed: de_DE gets German stemming and compound handling, so a search for “Schuhe” finds Wanderschuhe.

    List with ?organization_id=… — that parameter is also the authorization scope, see Status codes. Reading, updating and deleting follow the usual shape.

    A channel is a serving destination and a view over one catalog. Two mechanisms make one catalog serve several markets without duplicating content:

    MechanismFieldWhat it does
    Visibility filtervisibility_filterWhich objects this channel may serve at all
    Attribute aliasesattribute_aliasesWhich of an object’s destination-specific values this channel serves, and under which name

    Catalogs and channels covers both in full, including which of an aliased field’s two names a response is keyed by. What follows is the API surface.

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/channels' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{
    "catalog_id": "lbc_8w3k2p",
    "name": "example.de",
    "attribute_aliases": { "price": "price_de", "availability": "availability_de" }
    }'

    Each product carries several destination-specific values — price_de, price_at — and the channel decides which one answers to the canonical name price. Your storefront asks for price and gets the right one, with no per-request context and no duplicated products.

    Both fields are validated against the catalog when you write them, so a name the catalog does not carry — or an operator its type does not allow — is a 422. The check needs a catalog with content, so neither field can be set before the catalog’s first ingest: create the channel without them and set them once it has been fed. When the check cannot run at all, the write fails with a retryable 503 instead of saving unvalidated. See Attribute aliases for the full list of what is rejected and why.

    visibility_filter is the one clearable field on a channel: send null to remove it. Everything else, omit to leave unchanged. See Partial updates.

    List with ?catalog_id=… for one catalog’s channels, or ?organization_id=… for every channel in an organization. Sending both narrows to the intersection.

    A surface is a configured discovery unit, scoped to a catalog. Every channel bound to that catalog can call any of its surfaces — there is no per-channel duplication.

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/catalogs/lbc_8w3k2p/surfaces' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{ "surface_kind": "search", "slug": "main" }'
    FieldRequiredNotes
    surface_kindYesThe intent: search, recommender or collection
    slugYesLowercase letters, digits and underscores, starting with a letter, 1–48 characters

    The two combine into the surface’s ID — lbs_search_main — and slugs are unique per catalog and intent. See Surface identifiers.

    A surface’s response reports its default_version. Ranking configuration lives on versions rather than on the surface itself, which is what lets an A/B test run two versions of one surface without duplicating it. Version authoring is done with Luigi’s Box; the surface endpoints here create, read, list and delete the surface itself.

    GET /platform/v1/catalogs/{catalog_id}/surfaces
    POST /platform/v1/catalogs/{catalog_id}/surfaces
    GET /platform/v1/catalogs/{catalog_id}/surfaces/{surface_id}
    DELETE /platform/v1/catalogs/{catalog_id}/surfaces/{surface_id}

    A tag is a named group, and it exists so that things which target many entities — chiefly campaigns — can target a group instead of a list.

    One tag groups exactly one dimension:

    dimensionMembers are
    catalogCatalog IDs
    channelChannel IDs
    surface{catalog_id, surface_id} pairs
    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/tags' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{
    "organization_id": "lbo_r2vn8c",
    "dimension": "catalog",
    "name": "EU markets",
    "members": ["lbc_8w3k2p", "lbc_4hj9tv"]
    }'

    Surface tags name the catalog alongside the surface, because a surface ID is only unique within its catalog:

    {
    "organization_id": "lbo_r2vn8c",
    "dimension": "surface",
    "name": "All search surfaces",
    "members": [
    { "catalog_id": "lbc_8w3k2p", "surface_id": "lbs_search_main" },
    { "catalog_id": "lbc_4hj9tv", "surface_id": "lbs_search_main" }
    ]
    }

    At least one member is required. dimension is immutable — changing it would orphan the members — and a members list on a PATCH replaces the whole set.

    A campaign targeting EU markets resolves that tag to its members each time the campaign is saved. Add a catalog to the tag, save each campaign targeting it, and every one of them covers the new market with no targeting edited.

    Editing a tag therefore changes what every campaign using it serves. Before changing membership, check which campaigns point at it — GET /platform/v1/campaigns?intent=… and each campaign’s effective scope answer that.

    List with ?organization_id=…, optionally &dimension=….

    The usual sequence, when an organization already exists:

    1. Create the catalog for the language — POST /platform/v1/catalogs.
    2. Register its feeds and mappings — Feed management API.
    3. Create the channel for the storefront, with its visibility filter and attribute projection.
    4. Create the surfaces the storefront will call.
    5. Ask Luigi’s Box for credentials — a publishable key bound to the new channel, or an OAuth client for your backend.
    6. Verify with /me/entitlements and one search request.

    Steps 2 and 4 determine most of the ranking configuration. Do both with your Luigi’s Box contact.