Skip to Content
API ReferenceErrors & Permissions

Errors & Permissions

Refusals in the Quazzar Space API follow one rule, and knowing it saves you from debugging the wrong thing.

404 means “not yours, or not there” — and you cannot tell which

A tenancy check answers 404 for both “no such object” and “that object is not in a project you belong to”, with the same status and the same response body.

That is deliberate. A caller who supplies ids they do not own is doing so precisely because they are guessing; answering 403 for a real id and 404 for an absent one grades each guess and turns guessing into a search. From outside the tenant the two cases are indistinguishable, which is the point.

The consequence for your code: 404 is not proof that an id does not exist. If you are certain an id is real and you get a 404, the likely cause is that the calling account is not a member of the project that owns it.

Where this is observable today:

Request404 detail
POST /impex/export, POST /impex/importProject not found
POST /invites/{invite_id}/accept (address does not match the invitation)Invitation not found
POST /invites/{invite_id}/resend, DELETE /invites/{invite_id}Invitation not found
PATCH /attachments/services/{service_id}/networksService not found
PATCH /attachments/service-floors/{floor_id}/networksServiceFloor not found
POST /integrations/cloud-accounts/{account_id}/discoverCloudAccount not found
Any project-scoped route reached through the shared tenancy helperProject not found

Several of these answered 403 until recently, which made the pair of responses a working enumeration oracle. They now answer 404, and the body for a real id belonging to someone else is byte-identical to the body for an id that does not exist.

The /integrations case is worth calling out: it answers CloudAccount not found rather than Project not found, because a caller who could compare the two bodies would learn which uuids name real cloud accounts.

The rule is not yet universal. Some endpoints still answer 403 when the caller is not a member of the project — GET /environments?project_id={id} returns 403 Not authorized to view environments for this project, and GET /impex/history returns 403 Not a member of this project. Do not rely on the status alone to decide whether an id exists; treat both 403 and 404 on a project-scoped read as “you have no access to this”.

403 means “you are inside, and you still may not do this”

A permission refusal is 403, and it runs after the tenancy check has passed. By then the caller is established to be inside the project — they can already list its contents — so telling them what they may not do leaks nothing they could not already infer.

Typical details: Missing permission: connections.manage, Project owner required, Not authorized for this environment, Not authorized to manage environment roles.

403 also covers refusals that are about the account rather than about one object:

DetailCause
Active subscription required to create projects. Please choose a plan.POST /projects with no active subscription
Project limit reached for your '<plan>' plan (max N)...Plan limit on POST /projects
You are not allowed to create projectsThe account is not a customer — see Account classes
Registration is disabled / Registration is invite-only...POST /auth/register under those policies

The rest of the status codes

StatusWhat it means here
400The request is well-formed but the operation is refused: a duplicate name, an expired or already-used token, an environment that does not belong to the project, a password that fails the policy
401No usable access token, or credentials rejected. See Authentication for the exact details a token refusal returns
403Permission or account-level refusal, after tenancy passed
404Tenancy refusal, or genuinely absent — indistinguishable, see above
409POST /invites/accept/by-token for an address that already has an account. See Invitations
422Request-schema validation. Runs before any lookup, so it is returned identically for a real id and an absent one
423Login lockout after repeated failures. The body carries locked_until
429Rate limit; see Rate Limits

422 preceding every lookup matters for one thing: it is not a disclosure. A request that fails schema validation is refused before the API looks anything up, so an over-long array or a malformed uuid tells you nothing about whether the ids in it exist.

Next Steps