Skip to main content
An organization is the tenant a sandbox, preview, or other resource belongs to. Every caller has an implicit personal organization; callers can also belong to named organizations shared with others.

Personal vs. named organizations

personal: true marks the deterministic tenant an omitted X-Bonya-Organization-ID header resolves to — see Authentication for how that header selects org context on sandbox-scoped calls. Personal organizations can’t be invited into; membership is just the one owner. Named organizations are created explicitly (POST /v1/organizations) and can have multiple members with different roles.

Organization fields

string
required
Matches ^org-[0-9a-f]{32}$.
string
required
Display name, up to 128 characters.
boolean
required
Whether this is the caller’s personal organization.
string
The requesting caller’s role, when known.
string
required
Creation timestamp.

Roles

Roles are a closed set — owner or member, defaulting to member. There are no custom roles.
An organization’s final owner can’t be demoted or removed — both return 409 final_owner. This is also why the sole owner of a personal organization can’t leave it: a personal organization always has exactly one member, who is always its owner.

Organization context in the SDKs

Every SDK reads an organization id from the environment (BONYA_ORGANIZATION_ID) or accepts an explicit override at client construction, and resends it as X-Bonya-Organization-ID on every control-plane call. All three deliberately treat an explicitly empty value as an error rather than a silent fallback to your personal organization — BONYA_ORGANIZATION_ID="", or the equivalent constructor argument set to "", raises immediately instead of quietly resolving to your personal tenant. This matters most in CI: an unset variable expanded into another one often evaluates to an empty string, and running a shared pipeline’s jobs against whichever engineer’s personal organization happens to own the credential is a worse failure mode than an immediate, loud error. Set BONYA_ORGANIZATION_ID explicitly in CI rather than relying on the personal-organization default.

Administration is JWT-only

Organization administration — creating, renaming, and reading organizations; listing, changing the role of, or removing members — requires a supabaseJwt. Personal API keys (byk_) are rejected outright on these operations. A byk_ key can still set org context via X-Bonya-Organization-ID on sandbox-scoped calls (see Authentication); it just can’t administer organizations itself. These are dashboard-facing operations, not something an automated client using an API key is expected to call directly.
The core administrative operations:

Invitations

Adding someone to a named organization goes through an invitation, not a direct membership write. This surface is dashboard-facing: four of its operations are marked internal in the API contract (x-internal: true), so treat invitations as a concept worth understanding rather than a REST flow to build automation against. Conceptually:
  • An owner creates an invitation addressed to an email with a role. The raw token is returned exactly once, and the dashboard — not the API — builds the acceptance link, since the server doesn’t know the dashboard’s origin.
  • A live invitation already addressed to the same email is a conflict; a personal organization can’t be invited into at all.
  • Acceptance lives at a separate, top-level path, not nested under the organization — because the token itself is the credential, the same pattern used elsewhere for one-time bearer tokens. The caller must be signed in as the invited address; the check is case-insensitive.
  • Accepting, once, atomically provisions membership and consumes the token. Unknown, wrong-recipient, already-consumed, revoked, and expired tokens are all a single indistinguishable 404 — a mismatched attempt doesn’t invalidate the invitation for its real recipient.
All of this is JWT-only, same as the rest of organization administration.

Authentication

API keys

Errors