Three ways the organization is decided
It’s never guessed. Exactly one of these applies to any request: From the path./v1/organizations/{orgId}/… — the id is right there, and your membership of it is checked.
From the project. /v1/projects/{projectId}/…, which is most of the API. The organization is the project’s — derived, not asserted — and membership of that is what grants access.
From an explicit query parameter. Routes with neither in the path — live SERP lookups, keyword research, content generation, trends. These are billable, so with a user token the organization must be named as org_id:
BAD_REQUEST: “org_id is required. This call is billed to an organization, so it cannot be inferred.”
That third case exists because of a bug in the previous implementation. Routes with no organization in the path resolved it as “the user’s first membership”, from an unordered query — so for users belonging to two organizations, lookups were billed to whichever row came back first, and the plan limit checked was that organization’s too. Guessing is no longer available.
Membership, and what “not a member” looks like
Two checks run:- Are you a member, and have you accepted? An invitation you haven’t accepted is not membership — the row exists to hold the invitation, not to grant access.
- Does the resource actually belong to that organization?
NOT_FOUND, not FORBIDDEN. A 403 would confirm the organization or project exists, which is the fact being protected.
So a 404 doesn’t prove deletion. Check your ids, and check the invitation was accepted, before concluding anything.
Roles are hierarchical
owner ⊃ admin ⊃ member. A check for admin passes for an owner.
Failing a role check is a genuine FORBIDDEN (403), with a message naming what’s required — “This action requires the owner role.” The distinction from 404 is deliberate: you can already see the organization, so hiding it now would only confuse. What you can’t do is this action.
Creating a project requires admin because a project counts against the plan.
An organization always keeps one owner
The last owner can’t be demoted or removed. Attempting it fails rather than leaving an organization nobody can administer. Owner can be granted to someone else — unlike the previous implementation, it’s a normal role change. What can’t happen is the count reaching zero.Working across organizations
There’s no cross-organization endpoint.- With an API key — one key per organization, selected per request in your client.
- With a user token —
GET /v1/organizationslists your accepted memberships; loop and issue scoped requests for each.
organization argument.