Projects API
Projects are the top-level organizational unit in Quazzar Space. Each project contains environments, groups, services, and members.
Base URL
https://app.quazzar.spaceAll requests require an Authorization: Bearer <token> header. See the Authentication guide.
List Projects
GET /projects
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://app.quazzar.space/projectsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
updated_since | ISO 8601 | Return only projects modified after this timestamp, including soft-deleted ones. Sets an X-Sync-Timestamp response header |
There is no pagination and no search parameter. The response is a plain JSON array, ordered by role then name:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"identifier": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "my-saas-product",
"is_owner": true,
"cost_calc_enabled": true,
"updated_at": "2026-09-01T12:00:00Z",
"deleted_at": null
}
]There is no description field on a project, and no embedded stats object — counts come from GET /projects/stats.
Without updated_since, soft-deleted projects are excluded. With it, they are included and carry a non-null deleted_at, which is how a client reconciles deletions.
Create Project
POST /projects
curl -X POST https://app.quazzar.space/projects \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-saas-product"}'The body is name, plus an optional creator_user_id that only staff may set to another account. There is no description.
Response (201 Created): a project object as above, with is_owner: true. The caller is written in as the project owner in the same transaction.
Refusals:
| Status | Detail |
|---|---|
400 | Project with this name already exists for this owner or membership already present |
403 | You are not allowed to create projects — the account is not a customer; see Account classes |
403 | Active subscription required to create projects. Please choose a plan. |
403 | Project limit reached for your '<plan>' plan (max N). Please upgrade to create more projects. |
403 | You are not allowed to create projects for other users |
404 | Owner user not found (only reachable with an explicit creator_user_id) |
422 | Validation error |
An active subscription is required, not merely a plan on file. An account with no active subscription cannot create a project at all — the Free plan assigned at registration satisfies this.
The limit counts projects the account owns (owner memberships on non-deleted projects), not projects it can see.
Project limits by plan
Counts come from the plan record; see the Billing API for the full table.
| Plan | Max projects |
|---|---|
| Free | 1 |
| Starter | 5 |
| Team | 15 |
| Business | 50 |
| Enterprise | 200 |
| Enterprise+ | Unlimited |
Project Settings
PATCH /projects/{project_id}/settings
curl -X PATCH https://app.quazzar.space/projects/PROJECT_UUID/settings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"cost_calc_enabled": false}'cost_calc_enabled is the only writable field, and it is required. Returns the updated project object.
There is no GET /projects/{project_id} and no PATCH /projects/{project_id}. A single project is read out of the GET /projects array; renaming a project is not exposed by the API.
Project Plan
GET /projects/{project_id}/plan
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://app.quazzar.space/projects/PROJECT_UUID/plan{
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"plan_name": "Team",
"plan_tier": "team",
"is_owner": true
}Delete Project
DELETE /projects/{project_id}
Requires the project owner role.
curl -X DELETE https://app.quazzar.space/projects/PROJECT_UUID \
-H "Authorization: Bearer YOUR_TOKEN"Response: 204 No Content.
This is a soft delete. The project’s deleted_at is set; nothing is removed. The project disappears from GET /projects but is still returned by a GET /projects?updated_since=... sync call with deleted_at populated.
Project Statistics
GET /projects/stats
Aggregate counts for every project the caller can reach, in one call. Takes no parameters.
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://app.quazzar.space/projects/stats[
{
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"environments": 3,
"services": 48,
"servers": 32,
"groups": 12
}
]The key is servers (service floors). There is no per-project /statistics endpoint, and cost is not part of this response — see GET /projects/{project_id}/costs/estimate.
Environments
Environments represent deployment stages within a project.
GET /environments?project_id={id}
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://app.quazzar.space/environments?project_id=PROJECT_UUID"project_id is required. updated_since behaves as it does on GET /projects. Archived environments are excluded.
[
{
"id": "env-uuid-1",
"name": "production",
"project_id": "project-uuid",
"updated_at": "2026-09-01T12:00:00Z",
"deleted_at": null
}
]An environment has no description and no embedded stats. A caller who is not a member of the project gets 403 Not authorized to view environments for this project.
There is no GET /projects/{project_id}/environments.
POST /environments
curl -X POST https://app.quazzar.space/environments \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "staging", "project_id": "project-uuid"}'Body is name and project_id only. Returns 201 with the environment object.
PATCH /environments/{environment_id}
curl -X PATCH https://app.quazzar.space/environments/ENV_UUID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "staging-v2"}'name is the only field, and it is required.
DELETE /environments/{environment_id}
curl -X DELETE https://app.quazzar.space/environments/ENV_UUID \
-H "Authorization: Bearer YOUR_TOKEN"GET /environments/stats?project_id={id}
Aggregate counts per environment within one project.
Environment limits by plan
| Plan | Environments per project |
|---|---|
| Free | 1 |
| Starter | 3 |
| Team | 5 |
| Business | 10 |
| Enterprise | 25 |
| Enterprise+ | Unlimited |
Groups and Subgroups
Groups organize services within an environment; subgroups nest one level deeper.
| Endpoint | Description |
|---|---|
GET /groups?project_id={id}&environment_id={id} | List groups |
POST /groups | Create a group |
PATCH /groups/{group_id} | Rename or move a group |
DELETE /groups/{group_id} | Delete a group |
GET /subgroups | List subgroups |
POST /subgroups | Create a subgroup |
PATCH /subgroups/{subgroup_id} | Update a subgroup |
DELETE /subgroups/{subgroup_id} | Delete a subgroup |
Both /groups and /subgroups remain first-class API resources. (In the web app, the separate subgroups screen has been folded into the groups screen and its route now redirects — that is a UI change and does not affect these endpoints.)
Group and network names are unique per (project, environment) pair. A subgroup name is unique only within its parent group — the same subgroup name (Core, say) may be reused under different groups in the same environment.
Project Members
GET /projects/members/{project_id}
Note the path shape: the project id comes after members.
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://app.quazzar.space/projects/members/PROJECT_UUID[
{ "user_id": "user-uuid", "email": "[email protected]" }
]Any project member may call it. The response carries user_id and email only — it exists to populate filter dropdowns, not to describe roles. There is no POST /projects/{project_id}/members: people are added to a project by invitation, see the Invitations API.
Project roles are owner and member, held on the membership row. Ownership is the membership with role = "owner"; there is no owner column on the project itself, and a project may have more than one owner membership.
For the administrative view of project-member accounts — who they are, what they reach, and blocking them — the admin console exposes /admin/project-members. That surface requires staff credentials and is not part of the customer API.
Environment Roles
Per-environment roles give finer-grained access than project membership.
POST /environments/{environment_id}/roles
curl -X POST https://app.quazzar.space/environments/ENV_UUID/roles \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id": "user-uuid", "role": "manager"}'Upserts the role and returns 201 with {id, environment_id, user_id, role}.
| Role | Permissions |
|---|---|
administrator | Full environment access, including managing environment roles |
manager | Create, update, and delete resources within the environment |
viewer | Read-only access to the environment |
Callers allowed to assign: the project owner, an administrator on that environment, or a custom role holding env.roles.manage. Anyone else gets 403 Not authorized to manage environment roles; an unknown environment gets 404 Environment not found.
Only a project owner implicitly grants project membership. When an owner assigns an environment role, the target also gets a project-level member record. An environment administrator assigning a role does not create one — they cannot escalate somebody to project membership without the owner.
| Endpoint | Description |
|---|---|
GET /environments/{environment_id}/roles | List role assignments |
DELETE /environments/{environment_id}/roles/{user_id} | Remove an assignment |
Custom roles
For permission sets beyond the three above, each project has its own custom roles:
| Endpoint | Description |
|---|---|
GET/POST /projects/{project_id}/custom-roles | List and create custom roles |
PATCH/DELETE /projects/{project_id}/custom-roles/{role_id} | Update and delete |
GET/POST /projects/{project_id}/custom-roles/{role_id}/assignments | List and create assignments |
Call GET /auth/me/capabilities?project_id={id} to read the caller’s effective permission keys in a project.
Next Steps
- Errors & Permissions — why an id you do not own answers
404 - Invitations — adding people to a project
- Services API — manage services within your projects
- Billing API — plans and limits