> ## Documentation Index
> Fetch the complete documentation index at: https://orgo.space/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tenancy

> How the API resolves the current tenant and why subdomain choice matters

Orgo is multi-tenant. Every organization that uses Orgo gets its own isolated workspace — its own users, events, payments, contracts, files. Two organizations cannot see each other's data, ever, even if a query is misconfigured.

This page explains how the API knows which tenant a request is for, and what to do when the answer is wrong.

***

## Tenant resolution: the `Host` header

The tenant is resolved from the HTTP `Host` header of the incoming request. Each organization has either a subdomain on `orgo.space` or a fully-custom domain:

```
https://acme.orgo.space/api/v1/users        →  tenant = "acme"
https://members.your-org.com/api/v1/users   →  tenant = "your-org"
```

There is **no header, query parameter, or JWT claim that overrides this**. The host *is* the tenant selector.

```bash theme={null}
# ✓ Correct — host is the tenant
curl https://acme.orgo.space/api/v1/users \
  -H "Api-Token: $TOKEN"

# ✗ Wrong — calling the bare app.orgo.space domain returns 404
# for tenant-scoped endpoints because no tenant resolves
curl https://app.orgo.space/api/v1/users \
  -H "Api-Token: $TOKEN"
```

<Note>
  The OpenAPI spec lists `https://app.orgo.space` as the base URL only because the API playground needs *some* URL. For real requests, swap that for your tenant's host.
</Note>

***

## Why this matters for tokens

Tokens are tenant-scoped at the moment they are issued:

* **Api-Token**: tied to the user who created it; that user belongs to one tenant; the token only works against that tenant's host.
* **JWT** (from `/login-check` or `/verify-login-otp`): contains a `session_id` claim bound to the tenant of the host the login was performed against.
* **OAuth access tokens**: bound to the tenant where the OAuth application was registered.

A token from tenant A cannot be used against tenant B. Trying produces `401 Unauthorized`.

***

## Cross-tenant access is blocked at the persistence layer

Inside Orgo's backend, Doctrine extensions automatically add `WHERE entity.tenant = current_tenant` to every read. Even if a developer wrote `SELECT * FROM users` without a tenant filter, the extension would inject the filter before the query runs. This is enforced at the ORM level so no API endpoint can leak across tenants.

The one exception is OTP-issued JWTs used by the event-app surface (members opening an event landing page from a different tenant they were invited to). Those JWTs carry an `auth_strength: OTP` claim and a `verified_email` instead of a `session_id`, and they can cross tenants but only for the event-attendance endpoints.

***

## Resolving your tenant host

Every tenant admin can find their host in the admin UI under **Settings → Organisation → Domains**. Common shapes:

| Shape              | Example            | Notes                                            |
| ------------------ | ------------------ | ------------------------------------------------ |
| Default subdomain  | `acme.orgo.space`  | Always available, even after custom-domain setup |
| Custom subdomain   | `members.acme.org` | Configured by the admin                          |
| Apex custom domain | `acme.org`         | Less common; requires DNS at the apex            |

The default subdomain always works, even when a custom domain is also configured. Use it for integrations to avoid breakage if the admin changes the custom-domain setup.

***

## What happens when the tenant is missing or wrong

| Scenario                                                                               | Response                                                                                   |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Host doesn't match any tenant                                                          | `404 Not Found` from the load balancer before Symfony sees the request                     |
| Host matches a tenant, but token is from another tenant                                | `401 Unauthorized`                                                                         |
| Host matches, token is valid, but the requested resource belongs to a different tenant | `404 Not Found` (not `403` — leaking "exists but forbidden" would itself be a tenant leak) |

The 404 in the last row is intentional. From outside, "not found" and "exists in another tenant" are indistinguishable. This is the same pattern GitHub uses for private repos.

***

## Tenant context in webhook deliveries

Webhook payloads carry a top-level `tenant_id` field so a single receiver subscribed to multiple tenants can route events. See [Webhooks](/docs/api-reference/concepts/webhooks).

```json theme={null}
{
  "id": "wh_evt_67aa128c2f4a",
  "event": "user.created",
  "tenant_id": 142,
  "created": 1704067200,
  "object": { ... }
}
```

***

## Common scenarios

<AccordionGroup>
  <Accordion title="I'm getting 401 errors with a valid-looking token">
    Check that the host you are calling matches the tenant the token was issued for. The same token against the wrong host produces 401, not 403.
  </Accordion>

  <Accordion title="I want one integration to serve many tenants">
    Generate one Api-Token per tenant (each from an admin user in that tenant) and route by hostname server-side. There is no "super-token" that crosses tenants.
  </Accordion>

  <Accordion title="The tenant's admin changed the custom domain — my integration broke">
    Switch to the `*.orgo.space` default subdomain. It is permanent and never changes.
  </Accordion>

  <Accordion title="Can I look up a tenant's host programmatically?">
    Yes — `GET /api/v1/tenant-host/{slug}` returns the current canonical host for a tenant slug. This endpoint is public (no auth needed).
  </Accordion>
</AccordionGroup>

***

## Related

* [Authentication](/docs/api-reference/concepts/authentication) — how tokens carry tenant scope
* [Errors](/docs/api-reference/concepts/errors) — what each tenant-mismatch error looks like
