Skip to Content

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.space

All 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/projects

Query Parameters:

ParameterTypeDescription
updated_sinceISO 8601Return 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:

StatusDetail
400Project with this name already exists for this owner or membership already present
403You are not allowed to create projects — the account is not a customer; see Account classes
403Active subscription required to create projects. Please choose a plan.
403Project limit reached for your '<plan>' plan (max N). Please upgrade to create more projects.
403You are not allowed to create projects for other users
404Owner user not found (only reachable with an explicit creator_user_id)
422Validation 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.

PlanMax projects
Free1
Starter5
Team15
Business50
Enterprise200
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

PlanEnvironments per project
Free1
Starter3
Team5
Business10
Enterprise25
Enterprise+Unlimited

Groups and Subgroups

Groups organize services within an environment; subgroups nest one level deeper.

EndpointDescription
GET /groups?project_id={id}&environment_id={id}List groups
POST /groupsCreate a group
PATCH /groups/{group_id}Rename or move a group
DELETE /groups/{group_id}Delete a group
GET /subgroupsList subgroups
POST /subgroupsCreate 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}.

RolePermissions
administratorFull environment access, including managing environment roles
managerCreate, update, and delete resources within the environment
viewerRead-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.

EndpointDescription
GET /environments/{environment_id}/rolesList 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:

EndpointDescription
GET/POST /projects/{project_id}/custom-rolesList 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}/assignmentsList and create assignments

Call GET /auth/me/capabilities?project_id={id} to read the caller’s effective permission keys in a project.

Next Steps