Multi-tenancy
How requests are scoped to a tenant — slugs, IDs, and the path shape
Basker is a multi-tenant CMS. Every document -- pages, events, media, and all other collections -- belongs to exactly one tenant. The Partners API resolves a single tenant for every request and scopes data to it. Writes are always confined to your own tenant. Reads are too, with one exception: when your tenant belongs to a tenant group, collections the group shares return data across the whole group (see Tenant groups & shared data).
Why this matters
Without tenant context, the API cannot determine which organisation's data you intend to access. Every request that touches collection data must include tenant identification. Omitting it results in a 400 error. Providing an identifier that does not match any tenant results in a 404 error.
You never need to filter by tenant yourself. The API merges a tenant constraint into every query automatically. For a collection that is not shared in a tenant group, a request returns only the resolved tenant's documents -- regardless of how many other tenants exist in the system. For shared collections, that constraint widens to your tenant group (see below).
What tenant context is
Tenant context is the resolved identity of the organisation for a given API request. It consists of three properties:
Key characteristics
- id: The internal identifier for the tenant (a record ID string such as
6639a1c2f3b4a5d6e7f80099). - slug: A human-readable, URL-safe string such as
royal-opera-houseorcity-arts-centre. - installedApps: An array of app handles indicating which integrations (Tessitura, Spektrix, Elevent) are active for this tenant.
How tenant resolution works
You can provide tenant identification in three ways. The API tries them in a specific priority order and uses the first one that resolves successfully.
Resolution priority
| Priority | Method | Header / Segment | Example |
|---|---|---|---|
| 1 (highest) | Header by ID | x-basker-tenant-id | x-basker-tenant-id: 6639a1c2f3b4a5d6e7f80099 |
| 2 | Header by slug | x-basker-tenant-slug | x-basker-tenant-slug: royal-opera-house |
| 3 (lowest) | Path parameter | /:tenant/ segment in URL | /partners/2026-02/royal-opera-house/pages |
If you supply both a header slug and a path slug, the header takes precedence.
Method 1: Tenant ID header
Send the x-basker-tenant-id header with the tenant's internal ID. This is the fastest resolution path because it performs a direct lookup by primary key.
curl -s \
-H "Authorization: users API-Key a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "x-basker-tenant-id: 6639a1c2f3b4a5d6e7f80099" \
"https://api.basker.app/partners/2026-02/royal-opera-house/pages"Method 2: Tenant slug header
Send the x-basker-tenant-slug header. The API looks up the tenant by slug.
curl -s \
-H "Authorization: users API-Key a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "x-basker-tenant-slug: royal-opera-house" \
"https://api.basker.app/partners/2026-02/royal-opera-house/pages"Method 3: Path parameter
Include the tenant slug as the second segment of the URL path, between the version and the collection:
/partners/{version}/{tenant}/{collection}curl -s \
-H "Authorization: users API-Key a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
"https://api.basker.app/partners/2026-02/royal-opera-house/pages"When using the path parameter alone (without headers), the path segment is used for resolution.
Automatic query filtering
Once the tenant is resolved, the API constrains every request to that tenant automatically. You do not need to include tenant in your where filters, and the tenant field is stripped from responses.
- A read (
GETlist or get-by-id) returns documents belonging to the resolved tenant. For a collection shared in your tenant group, the read also includes the rest of the group's documents (see below). - A
POSTassociates the new document with the resolved tenant. - An
UPDATEorDELETEverifies the document belongs to the resolved tenant before modifying it -- writes are never widened to the group.
Tenant groups & shared data
A tenant can belong to a tenant group -- for example a parent organisation that shares content with its venues or festivals. When a collection is shared within that group, the Partners API widens reads across the group, based on the group's collection configuration. Writes are unaffected.
Sharing is configured per collection, in one of two modes:
- Global in the group -- every member's documents are visible to every other member automatically. A
GET /eventsfrom one member returns the group's events. - Shareable in the group -- a document is visible to another member only when it has been explicitly shared with that member (the "Share with" option in the admin).
What this means for the API:
- Reads are group-aware. For a collection that is global in your group, list and get-by-id (REST and GraphQL) return records owned by any member. For a shareable collection, they return your own records plus any records other members have explicitly shared with you. Either way, a relationship or custom-object value can reference a shared record owned by another member, and it resolves normally.
- Writes stay tenant-scoped. You can read and link shared records, but you can only create, update, or delete records owned by your own tenant. A
PATCH,PUT, orDELETEagainst another member's record returns404, even though you can read it. - The group is the boundary. Shared reads never reach tenants outside your group. A tenant with no group sees only its own data, exactly as before.
- No opt-in needed. There is no parameter to set; scoping follows the group's configuration. If you expect a shared record and don't see it, that collection isn't shared in your group (or your tenant has no group).
To narrow a shared read to a single member, add that member's tenant id to your where -- it is combined with the group constraint:
curl -s -G \
-H "Authorization: users API-Key a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "x-basker-tenant-slug: royal-opera-house" \
--data-urlencode 'where[tenant][equals]=6639a1c2f3b4a5d6e7f80123' \
"https://api.basker.app/partners/2026-02/royal-opera-house/events"Error responses
| Scenario | Status | Response body |
|---|---|---|
| No tenant header and no path slug | 400 | {"error":"Missing tenant","message":"Tenant slug or identifier must be provided"} |
| Tenant ID or slug does not match any record | 404 | {"error":"Tenant not found","message":"Unable to resolve tenant from provided headers or path"} |
Tradeoffs
| Approach | Advantages | Disadvantages | Best when |
|---|---|---|---|
| Tenant ID header | Fastest lookup (direct by ID) | Requires knowing the internal ID | Building automated integrations where you store the tenant ID |
| Tenant slug header | Human-readable, easy to discover | Slightly slower (query by slug) | Interactive development and debugging |
| Path parameter only | Self-documenting URLs, no extra headers | Must match the URL path segment | Simple scripts and webhook configurations |
Recommendation: Use the tenant slug header (x-basker-tenant-slug) for most use cases. It is readable, easy to change, and does not require knowing internal IDs.
In practice
A typical integration stores the tenant slug in a configuration file or environment variable and includes it as a header on every request:
BASKER_TENANT="royal-opera-house"
BASKER_API_KEY="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
curl -s \
-H "Authorization: users API-Key ${BASKER_API_KEY}" \
-H "x-basker-tenant-slug: ${BASKER_TENANT}" \
"https://api.basker.app/partners/2026-02/${BASKER_TENANT}/events"Common misconceptions
| Misconception | Reality |
|---|---|
You need to add a tenant filter to your where clause | The API adds the tenant (or tenant-group) constraint automatically. You can still add where[tenant] to narrow a group read to one member. |
| The tenant slug in the URL path overrides the header | Headers take priority. The path slug is only used as a fallback. |
| You can never see another tenant's data | If your tenant is in a tenant group, reads of shared collections return the whole group's data. Writes still only affect your own tenant. |
Related concepts
- API key authentication -- authentication runs before tenant resolution; both are required for every request.
- Response sanitisation -- the
tenantfield is removed from responses.
Further reading
- Tutorial: Make your first API call -- hands-on first request with tenant setup
- How to authenticate with API keys -- the other required component of every request
- Error reference -- all error codes including tenant-related errors