--- title: Account structure description: Read and manage the entities your integration addresses — organizations, catalogs, channels, surfaces and tags — over the platform API. slug: platform/account-structure docKind: guide hub: luigisbox-ai --- 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](/concepts/domain-model/) explains what these entities *are*. This page is about working with them over the API. ```text Organization lbo_ └── Catalog lbc_ ├── Channel lbn_ └── Surface lbs__ Tag lbt_ (groups catalogs, channels or surfaces) ``` Everything here is under `/platform/v1/`, needs a token for the `https://api..luigisbox.ai/platform` audience, and a grant to read or manage kernel entities — reading, writing and deleting are granted separately. ## What can I see? 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: ```bash curl -G 'https://api.eu1.luigisbox.ai/platform/v1/me/entitlements' \ -d 'size=1000' \ -H 'Authorization: Bearer ' ``` Everything your credentials can reach, nested: ```json { "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. ## Organizations Your company. The billing entity and the root of everything else. ```text 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. ## Catalogs A catalog is one set of objects in one language, with its own settings. Separate catalogs are how you handle separate languages or regions. ```bash curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/catalogs' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "organization_id": "lbo_r2vn8c", "name": "Example — German", "language": "de_DE" }' ``` | Field | Required | Notes | |---|---|---| | `organization_id` | Yes | | | `name` | Yes | | | `language` | Yes | `xx_XX` — a lowercase language, an underscore, an uppercase region | | `settings` | No | Catalog-level settings | | `seed_surface_templates` | No | Templates 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](/api-basics/errors-and-rate-limits/#status-codes). Reading, updating and deleting follow the usual shape. :::caution Deleting a catalog deletes its channels, surfaces and content with it. ::: ## Channels A channel is a serving destination and a *view* over one catalog. Two mechanisms make one catalog serve several markets without duplicating content: | Mechanism | Field | What it does | |---|---|---| | Visibility filter | `visibility_filter` | Which objects this channel may serve at all | | Attribute aliases | `attribute_aliases` | Which of an object's destination-specific values this channel serves, and under which name | [Catalogs and channels](/concepts/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. :::hub[Config → Channels → Create Channel]{path="/channels"} Takes a name and a catalog; the list behind it has edit and delete on each channel. Its visibility conditions are single comparisons combined with AND, and its attribute projections map a catalog attribute to a channel-specific name. ::: ```bash curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/channels' \ -H 'Authorization: Bearer ' \ -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](/concepts/catalogs-and-channels/#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](/api-basics/requests-and-responses/#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. ## Surfaces 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. ```bash curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/catalogs/lbc_8w3k2p/surfaces' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "surface_kind": "search", "slug": "main" }' ``` | Field | Required | Notes | |---|---|---| | `surface_kind` | Yes | The intent: `search`, `recommender` or `collection` | | `slug` | Yes | Lowercase 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](/concepts/identifiers/#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. ```text 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} ``` ## Tags A tag is a named group, and it exists so that things which target many entities — chiefly [campaigns](/merchandising/business-rules/overview/) — can target a group instead of a list. One tag groups exactly one dimension: | `dimension` | Members are | |---|---| | `catalog` | Catalog IDs | | `channel` | Channel IDs | | `surface` | `{catalog_id, surface_id}` pairs | ```bash curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/tags' \ -H 'Authorization: Bearer ' \ -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: ```json { "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. ### Tags reflow 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=…`](/merchandising/business-rules/authoring-api/#listing) and each campaign's [effective scope](/merchandising/business-rules/targeting/#tags-make-targeting-reflow) answer that. List with `?organization_id=…`, optionally `&dimension=…`. ## Setting up a new market 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](/indexing/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`](#what-can-i-see) and one search request. Steps 2 and 4 determine most of the ranking configuration. Do both with your Luigi's Box contact. ## See also - [Domain model](/concepts/domain-model/) — what these entities mean - [Identifiers](/concepts/identifiers/) — their formats - [Business rules](/merchandising/business-rules/overview/) — the main consumer of tags - [Feed management API](/indexing/feed-management-api/) — filling a new catalog - [Authentication overview](/authentication/overview/) — credentials for a new channel