Basker Docs

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-house or city-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

PriorityMethodHeader / SegmentExample
1 (highest)Header by IDx-basker-tenant-idx-basker-tenant-id: 6639a1c2f3b4a5d6e7f80099
2Header by slugx-basker-tenant-slugx-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 (GET list 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 POST associates the new document with the resolved tenant.
  • An UPDATE or DELETE verifies 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 /events from 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, or DELETE against another member's record returns 404, 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

ScenarioStatusResponse body
No tenant header and no path slug400{"error":"Missing tenant","message":"Tenant slug or identifier must be provided"}
Tenant ID or slug does not match any record404{"error":"Tenant not found","message":"Unable to resolve tenant from provided headers or path"}

Tradeoffs

ApproachAdvantagesDisadvantagesBest when
Tenant ID headerFastest lookup (direct by ID)Requires knowing the internal IDBuilding automated integrations where you store the tenant ID
Tenant slug headerHuman-readable, easy to discoverSlightly slower (query by slug)Interactive development and debugging
Path parameter onlySelf-documenting URLs, no extra headersMust match the URL path segmentSimple 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

MisconceptionReality
You need to add a tenant filter to your where clauseThe 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 headerHeaders take priority. The path slug is only used as a fallback.
You can never see another tenant's dataIf your tenant is in a tenant group, reads of shared collections return the whole group's data. Writes still only affect your own tenant.

Further reading

On this page