Troubleshooting
What each status code actually means on the admin surface, the four different 429s, the calls that return 200 while doing something else, and the full permission table.
The status codes on this surface do not always mean what they mean elsewhere. This page decodes them, then lists every operation with the access it needs and the exact failure you get without it.
404 is the ordinary authorization failure
Section titled “404 is the ordinary authorization failure”Nearly every organization-scoped call denies with 404, not 403:
404 Organization <organization_id> not foundThe organization exists. You are not an admin of it. The API masks existence deliberately, so that probing ids cannot be used to enumerate organizations — which means a 404 is ambiguous by design between “wrong id” and “no access”.
If you are certain the id is right, you lack write access to the organization. Check, in order:
- Is the key user-scoped? A project-scoped key cannot reach the organization surface at all. The read-only probe is in the recipe.
- Is the key from the right region? See Wrong-region keys.
- Are you an organization admin — not a project admin? Reading
Adminoff a member’srolesarray is not the check you think it is; see The roles array is not an admin check.
Three exceptions to the 404 rule:
organizations.listhas no denial at all. It is membership-scoped. An organization you have no grant in is simply absent from the page — never 403, never 404. An empty page and a permissions problem look identical.- Read access is enough for the read calls.
organizations.get,organizations.get_usage,users.list_members, andusers.list_projectsneed only read access to the organization, or any project grant inside it. A viewer can read the organization and still 404 on every write. - Project routes are different.
projects.getdenies with 404Project <id> is not found.projects.updateandprojects.deletereturn 404 if you cannot read the project and 403 if you can read it but not write it — and they carry no organization check at all, so a project admin who is not an organization admin can delete a project while 404ing on everything else.
429 means one of four things
Section titled “429 means one of four things”All four are plain 429s. Only detail distinguishes them, and only one of them
is a rate limit you should back off from.
detail starts with | Raised by | What it is |
|---|---|---|
Rate limit exceeded for user invitations. | users.add | A real rate limit: 10 requests per 60 seconds per organization. It counts requests, not members. Wait and retry. |
You've already reached the maximum number of users | users.add | The plan’s seat cap. Pending invites count toward it. Retrying will not help. |
You've already reached the maximum number of projects | projects.create | The plan’s project cap. Retrying will not help. |
You've already created the maximum number of organizations | organizations.create | The per-account organization cap. The same gate returns 403 User does not have access to create organizations. when your account may not create organizations at all. |
Read the live numbers behind the two plan caps with
organizations.get_usage(organization_id) — plan.limits.max_users and
plan.limits.max_projects (null means unlimited) against usage.total_users.
On a BYOC or self-hosted deployment the plan caps do not apply at all; only the
invite rate limit remains.
409 has exactly two causes
Section titled “409 has exactly two causes”projects.createandprojects.update—A project with name X already exists in organization Y. Project names are unique within an organization, and a deleted project still holds its name. So the conflicting project may not appear inprojects.list; if a lookup after the 409 comes back empty, that is why. Pick a different name.projects.delete—An organization must keep at least one project.The organization’s last project cannot be deleted.
No other operation on this surface returns 409.
Calls that succeed but do the wrong thing
Section titled “Calls that succeed but do the wrong thing”These return 2xx. Nothing in the response tells you what happened.
add_to_project without project_id targets the default project
Section titled “add_to_project without project_id targets the default project”project_id is optional in the signature. Omit it and the call grants access to
the organization’s default project — creating a default project if the
organization has none — and returns 200. The response does not name the project
it touched.
# Wrong: 200, and the grant lands on the default project.client.organizations.users.add_to_project(user_id, organization_id=ORG_ID)
# Right:client.organizations.users.add_to_project( user_id, organization_id=ORG_ID, project_id=PROJECT_ID)// Wrong: 200, and the grant lands on the default project.await client.organizations.users.addToProject(userId, { organization_id: ORG_ID });
// Right:await client.organizations.users.addToProject(userId, { organization_id: ORG_ID, project_id: PROJECT_ID,});A multi-member users.add batch cross-assigns project ids
Section titled “A multi-member users.add batch cross-assigns project ids”Every entry’s project_ids is applied to every member in the batch, so each
person ends up with the union of everyone’s project grants. Send one member per
call.
# Wrong: both people get both projects.client.organizations.users.add( ORG_ID, body=[ {"email": "a@example.com", "project_ids": [PROJECT_A], "role_id": VIEWER_V2}, {"email": "b@example.com", "project_ids": [PROJECT_B], "role_id": VIEWER_V2}, ],)
# Right: one call each.for email, project_id in [("a@example.com", PROJECT_A), ("b@example.com", PROJECT_B)]: client.organizations.users.add( ORG_ID, body=[{"email": email, "project_ids": [project_id], "role_id": VIEWER_V2}], )// Wrong: both people get both projects.await client.organizations.users.add(ORG_ID, { body: [ { email: "a@example.com", project_ids: [PROJECT_A], role_id: VIEWER_V2 }, { email: "b@example.com", project_ids: [PROJECT_B], role_id: VIEWER_V2 }, ],});
// Right: one call each.for (const [email, projectId] of [ ["a@example.com", PROJECT_A], ["b@example.com", PROJECT_B],]) { await client.organizations.users.add(ORG_ID, { body: [{ email, project_ids: [projectId], role_id: VIEWER_V2 }], });}assign_role adds a role, it never replaces one
Section titled “assign_role adds a role, it never replaces one”Grants are additive. Assigning a second role leaves the first one live, and the
more permissive of the two wins. There is no downgrade and no revoke through
assign_role: after granting Admin on a project and then ViewerV2 across
the organization, the member holds both.
The only calls that remove access are users.remove_from_project, which drops
one project grant, and users.delete, which removes the person from the
organization.
# Check what they actually hold before and after — a second assign_role adds a row.member = next(m for m in client.organizations.users.list_members(ORG_ID) if m.user_id == user_id)print([(r.role.name, r.project_ids) for r in member.roles])
# To take a project grant away, remove it explicitly:client.organizations.users.remove_from_project( PROJECT_ID, organization_id=ORG_ID, user_id=user_id)// Check what they actually hold before and after — a second assignRole adds a row.const roster = await client.organizations.users.listMembers(ORG_ID);const member = roster.find((m) => m.user_id === userId);console.log(member?.roles.map((r) => [r.role.name, r.project_ids]));
// To take a project grant away, remove it explicitly:await client.organizations.users.removeFromProject(PROJECT_ID, { organization_id: ORG_ID, user_id: userId,});Re-adding an existing member re-sends the email and clears a flag
Section titled “Re-adding an existing member re-sends the email and clears a flag”users.add against someone who is already a member re-applies the membership
from your request. Your request does not carry their default-organization
setting, so that setting is cleared — and the invitation email is sent again.
List the members and skip the ones already present rather than adding
idempotently.
One more, not silent but easy to miss
Section titled “One more, not silent but easy to miss”At organization scope, assign_role collapses anything that is not Admin
or ViewerV2 down to a plain viewer grant. Granting AgentViewer
organization-wide does not produce an AgentViewer organization grant, and
nothing in the response flags the substitution — read role.name back from
list_members if it matters.
The roles array is not an admin check
Section titled “The roles array is not an admin check”users.list_members returns each member’s roles, and that array mixes
organization-scope and project-scope grants. A project-level admin grant is
labelled Admin exactly like an organization-wide one. Checking the name alone
reports someone as an organization admin who will 404 on every organization
write — and you can manufacture that false positive yourself by granting a
colleague Admin on a single project.
The discriminator is project_ids. It is populated for a project grant and
empty only for an organization-wide one.
# Wrong — a project admin passes this.is_admin = any(r.role.name == "Admin" for r in member.roles)
# Right.is_admin = any(r.role.name == "Admin" and not r.project_ids for r in member.roles)// Wrong — a project admin passes this.const isAdmin = member.roles.some((r) => r.role.name === "Admin");
// Right.const isOrgAdmin = member.roles.some( (r) => r.role.name === "Admin" && !r.project_ids?.length,);A wrong-region key looks like a bad key
Section titled “A wrong-region key looks like a bad key”A key issued in one region is rejected by the other with:
401 Invalid API Key. Please check your region https://developers.llamaindex.ai/python/cloud/general/regions.It is indistinguishable from an expired or mistyped key, and regenerating the key does not help. Check the base URL first:
| Region | API base URL |
|---|---|
| North America | https://api.cloud.llamaindex.ai (the client default) |
| Europe | https://api.cloud.eu.llamaindex.ai |
Both clients read LLAMA_CLOUD_ADMIN_BASE_URL for the override.
401 has two other causes on this surface: an expired or revoked key, and a
project-scoped key naming a different project — Project ID specified in the URL does not match the project ID associated with the API key.
Reading an error
Section titled “Reading an error”The two clients expose the response body differently. Use each one’s own idiom;
porting the other’s will silently read undefined.
from llama_cloud_admin import APIStatusError
try: client.projects.create(organization_id=ORG_ID, name="demo")except APIStatusError as err: # There is no err.error in Python. The body is typed `object | None`, # so guard it before subscripting. detail = err.body.get("detail") if isinstance(err.body, dict) else None request_id = err.response.headers.get("x-request-id") print(err.status_code, detail or err.message, request_id)Exception classes, all exported from the package root: BadRequestError (400),
AuthenticationError (401), PermissionDeniedError (403), NotFoundError
(404), ConflictError (409), UnprocessableEntityError (422),
RateLimitError (429), InternalServerError (5xx). They all subclass
APIStatusError, which subclasses APIError. Transport failures raise
APIConnectionError / APITimeoutError, which carry no status and no body.
import { APIError } from "@llamaindex/llama-cloud-admin";
try { await client.projects.create({ organization_id: ORG_ID, name: "demo" });} catch (err) { if (!(err instanceof APIError)) throw err; // There is no err.body in TypeScript. `err.error` is the parsed JSON body; // cast it to read the API's `detail` field. const detail = (err.error as { detail?: string } | undefined)?.detail; const requestId = err.headers?.get("x-request-id"); console.log(err.status, detail ?? err.message, requestId);}Exception classes, all exported from the package root: BadRequestError (400),
AuthenticationError (401), PermissionDeniedError (403), NotFoundError
(404), ConflictError (409), UnprocessableEntityError (422),
RateLimitError (429), InternalServerError (5xx). They all subclass
APIError. Transport failures raise APIConnectionError /
APIConnectionTimeoutError, which carry no status and no body.
Both clients retry 5xx responses and connection failures twice by default before
raising, so an InternalServerError that reaches you has already been retried.
Other status codes
Section titled “Other status codes”| Status | On this surface it means |
|---|---|
| 400 | organization_id is required when not using a project-scoped API key on projects.list (this is the key-scope probe); User cannot assign role to themselves; an unrecognized role id. |
| 401 | Expired or revoked key; a key from the wrong region; a project-scoped key naming a different project. |
| 403 | A project-scoped key on projects.create; projects.update / projects.delete when you can read the project but not write it; invites.accept on an invite addressed to someone else; api_keys.delete on someone else’s user-scoped key; api_keys.create against a disabled project; the rare assign_role / users.add case described below the table. |
| 404 | The ordinary authorization denial. See the top of this page. |
| 409 | Duplicate project name; deleting the organization’s last project. |
| 422 | Body shape: project_ids omitted from a users.add entry, an entry with neither email nor user_id, or organization_id missing from the assign_role body. |
| 429 | One of four limits. Read detail. |
Permission table
Section titled “Permission table”Every operation on the organization-administration tier, the access it needs, and what you get without it.
| Operation | Route | Requires | Denial |
|---|---|---|---|
organizations.list | GET /api/v2/organizations | A valid key. Results are limited to organizations you belong to. | None. An organization you have no grant in is absent from the page — never 403, never 404. The name filter is a case-insensitive partial match. |
organizations.get | GET /api/v2/organizations/{organization_id} | Read access to the organization, or any project grant inside it. | 404 Organization <id> not found. Read access is enough here; the writes below need more. |
organizations.get_usage / getUsage | GET /api/v1/organizations/{organization_id}/usage | Read access to the organization, or any project grant inside it. | 404 Organization <id> not found. Read-only and safe to call first — this is where the plan caps come from. |
organizations.roles.list | GET /api/v1/organizations/{organization_id}/roles | A valid key. The organization is not checked. | None. Succeeding here is not evidence that you administer that organization; the id only filters which roles are visible. |
organizations.users.list_members / listMembers | GET /api/v1/organizations/{organization_id}/users | Read access to the organization, or any project grant inside it. | 404 Organization <id> not found. Each member’s roles mixes organization-scope and project-scope grants. |
organizations.users.list_projects / listProjects | GET /api/v1/organizations/{organization_id}/users/{user_id}/projects | Read access to the organization, or any project grant inside it. | 404 Organization <id> not found. Returns an empty list rather than an error for an empty user id. |
projects.list | GET /api/v2/projects | A valid key. organization_id is required unless the key is project-scoped. | 400 organization_id is required when not using a project-scoped API key — the key-scope probe. A project-scoped key gets 200 and its one project. The name filter is an exact match. |
projects.get | GET /api/v2/projects/{project_id} | Read access to the project. Organization admins inherit it. | 404 Project <id> is not found — not 403; the read check runs first. A project-scoped key naming a different project gets 401. |
projects.create | POST /api/v2/projects | Write access to the organization — an organization admin. | 404 Organization <id> not found. Also 403 API key is restricted to a single project for a project-scoped key; 429 for the plan’s project cap; 409 for a duplicate name. |
projects.update | PUT /api/v2/projects/{project_id} | Read and write access to the project. No organization check. | 404 if you cannot read it; 403 if you can read but not write it; 409 on a duplicate name. |
projects.delete | DELETE /api/v2/projects/{project_id} | Read and write access to the project. No organization check — a project admin who is not an organization admin can delete it. | 404 if you cannot read it; 403 if you can read but not write it; 409 An organization must keep at least one project. |
organizations.users.add | PUT /api/v1/organizations/{organization_id}/users | Write access to the organization; the organization-wide shape additionally requires that you are an organization admin. | 404 Organization <id> not found is the ordinary denial. Then, in order: 429 invite rate limit; 429 seat cap; 422 body shape; 403 User is not an admin and cannot assign roles and 400 User cannot assign role to themselves, unless the entry pairs non-empty project_ids with Admin/ViewerV2/AgentViewer. 400 for an unrecognized role id. |
organizations.users.assign_role / assignRole | PUT /api/v1/organizations/{organization_id}/users/roles | Write access to the organization, plus an organization-wide Admin role. | 404 Organization <id> not found for anyone without write access — the ordinary denial. 403 User is not an admin and cannot assign roles only in the rare case below. 400 User cannot assign role to themselves. 400 for an unrecognized role id. 422 when organization_id is missing from the body. |
organizations.users.add_to_project / addToProject | PUT /api/v1/organizations/{organization_id}/users/{user_id}/projects | Write access to the organization and read access to the named project. | 404 Organization <id> not found first, then 404 Project <id> is not found. Silent hazard: omit project_id and it targets the organization’s default project, creating one if absent — 200, wrong project. |
organizations.users.remove_from_project / removeFromProject | DELETE /api/v1/organizations/{organization_id}/users/{user_id}/projects/{project_id} | Write access to the organization. | 404 Organization <id> not found. Removing a grant the member did not have is not reported as an error. |
organizations.users.delete | DELETE /api/v1/organizations/{organization_id}/users/{member_user_id} | Write access to the organization. | 404 Organization <id> not found; 404 User not removed from Organization. when nothing was removed. The path segment is read as an email if it contains @, otherwise as a user id. There is no last-admin guard and no self-removal guard. |
invites.list_mine / listMine | GET /api/v2/invites | A valid key. Scoped to your own address. | None. Lists organization-scope invites only, so someone added with project_ids sees nothing here. |
invites.accept | POST /api/v2/invites/{invite_id}/accept | A valid key. The invite must be addressed to you. | 404 Invite <id> not found or already redeemed; 403 Invite <id> is not addressed to user <id>. |
invites.decline | DELETE /api/v2/invites/{invite_id} | A valid key. The invite must be addressed to you. | 404 Invite <id> not found or already redeemed; 403 Invite <id> is not addressed to user <id>. Declining deletes the pending grants for that address across the organization and its projects, so a later re-invite issues fresh ones. |
api_keys.create / apiKeys.create | POST /api/v1/beta/api-keys | Read access to the project named in project_id. Omit it and there is nothing to check — unless you present a project-scoped key, which substitutes its own project and is then checked against that. | 404 Project <id> is not found — this one goes through the same resolver as projects.get, so the wording differs from the row below. A project-scoped key naming a different project gets 401 first, before any permission check. |
api_keys.list / apiKeys.list | GET /api/v1/beta/api-keys | Read access to the project, when you pass project_id. Called bare it returns your own keys and checks nothing. | 404 Project <id> not found or access denied. Secrets are masked on every row; no read returns one. |
api_keys.delete / apiKeys.delete | DELETE /api/v1/beta/api-keys/{api_key_id} | Key-management permission on the key’s project — more than read, because project keys are shared. A key with no project only needs to be yours. | 404 Project <id> not found or access denied without the permission, which is the usual case for a shared project key; 404 for a key id that does not exist; 403 Access denied to this API key for someone else’s user-scoped key. |
The rare 403 instead of a 404
Section titled “The rare 403 instead of a 404”assign_role, and the organization-wide shape of users.add, are the only
operations here that can answer 403 User is not an admin and cannot assign roles. If you administer the organization you will never see it — 404 is the
denial you get. Treat a 403 from either call exactly as you would the 404: the
key you are holding does not administer the organization you named.
On users.add, that 403 and the self-target 400 User cannot assign role to themselves are both reachable unless the entry pairs a non-empty project_ids
with Admin, ViewerV2, or AgentViewer — that combination takes a different
path and raises neither. project_ids: null, or project ids paired with
Viewer, can still raise both.