Services API
Services represent applications, microservices, or software components running on your infrastructure. This reference covers service CRUD, servers (service floors), connections, networks and their attachments, import/export, and auto-discovery.
Base URL
https://app.quazzar.spaceAll requests require an Authorization: Bearer <token> header. See the Authentication guide.
Every collection endpoint here returns a plain JSON array, not a paginated envelope, and every one is scoped by required project_id and environment_id query parameters.
List Services
GET /services
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://app.quazzar.space/services?project_id=PROJECT_UUID&environment_id=ENV_UUID"Query Parameters:
| Parameter | Type | Description |
|---|---|---|
project_id | uuid | Required |
environment_id | uuid | Required |
name | string | Case-insensitive substring match on the service name |
tags | string (repeatable) | Match any of these tags |
language | string | Programming language |
location | string | Case-insensitive contains across provider, region, and zone |
limit | integer | Default 200, between 1 and 1000 |
offset | integer | Default 0 |
updated_since | ISO 8601 | Only services modified since; includes soft-deleted ones |
Paging is limit/offset, not page/per_page, and the parameter for a name search is name, not search. There is no group_id filter.
Response (200 OK):
[
{
"id": "svc-uuid-1",
"name": "auth-service",
"owner_user_id": "user-uuid",
"project_id": "project-uuid",
"environment_id": "env-uuid",
"service_floor_id": "floor-uuid",
"group_id": "group-uuid",
"subgroup_id": null,
"group_name": "Backend",
"subgroup_name": null,
"type": "vm",
"category": "api",
"replicas": 3,
"provider": "aws",
"provider_region": "us-east-1",
"provider_zone": "us-east-1a",
"jira_project_key": "AUTH",
"port": 8080,
"ip_address": "10.0.1.20",
"agent_id": null,
"responsible_project_user_id": null,
"responsible_user_id": null,
"responsible_name": null,
"programming_language": "python",
"programming_language_version": "3.12",
"repository_url": "https://github.com/org/auth-service",
"version": "3.2.0",
"links": [],
"tags": ["critical", "auth"],
"repository_deps": [],
"updated_at": "2026-09-01T12:00:00Z",
"deleted_at": null
}
]A few field names differ from what you might expect: the language is programming_language (not language), there is a single port rather than a ports array, and there is no description field. type is derived from the linked service floor and reads serverless when there is none. group_name and subgroup_name are resolved server-side so a client never has to join.
If environment_id does not belong to project_id, the response is 400 Environment not found or does not belong to project.
Create Service
POST /services
curl -X POST https://app.quazzar.space/services \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "auth-service",
"project_id": "project-uuid",
"environment_id": "env-uuid",
"group_id": "group-uuid",
"service_floor_id": "floor-uuid",
"category": "api",
"replicas": 3,
"provider": "aws",
"provider_region": "us-east-1",
"programming_language": "python",
"programming_language_version": "3.12",
"repository_url": "https://github.com/org/auth-service",
"version": "3.2.0",
"jira_project_key": "AUTH",
"port": 8080,
"tags": ["critical", "auth"]
}'name, project_id, and environment_id are required; everything else is optional. Response 201 Created with the service object.
Validation worth knowing before you send a request:
repository_urlmust start withhttps://orgit://.- Every entry in
linksmust start withhttp://orhttps://. categoryis a free-form label of at most 64 characters.providermust be one of the supported providers.- The service owner is always the calling account;
owner_user_idin the body is ignored.
Refusals:
| Status | Detail |
|---|---|
400 | Environment not found or does not belong to project |
403 | Insufficient permissions to create service in this environment — requires project owner or an administrator/manager environment role |
403 | Service limit reached for your '<plan>' plan (max N per project including add-ons)... |
404 | Project not found |
422 | Validation error |
Service limits
The plan’s service allowance is enforced twice on the same number: once as a per-project cap and once as a per-environment cap. Quota add-ons on max_services raise both.
| Plan | Max services |
|---|---|
| Free | 5 |
| Starter | 25 |
| Team | 100 |
| Business | 500 |
| Enterprise | 2000 |
| Enterprise+ | Unlimited |
Get, Update, Delete a Service
| Endpoint | Notes |
|---|---|
GET /services/{service_id} | Returns the full service object |
PATCH /services/{service_id} | Every field optional; project_id and environment_id cannot be changed |
DELETE /services/{service_id} | Removes the service and its connections and network attachments |
GET /services/{service_id}/blast-radius | What else is affected if this service fails |
GET /services/{service_id}/repository-deps | Declared repository dependencies |
Service Connections
A connection joins two entities in the same environment. Either end may be a service or a service floor, which is why the ends are named a and b rather than source and target.
POST /service-connections
curl -X POST https://app.quazzar.space/service-connections \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "project-uuid",
"environment_id": "env-uuid",
"a_type": "service",
"a_id": "auth-service-uuid",
"b_type": "service",
"b_id": "user-database-uuid",
"direction": "a_to_b",
"name": "reads user data"
}'| Field | Values |
|---|---|
a_type, b_type | service or floor |
direction | none (default), a_to_b, b_to_a, both |
chain_id | Optional; groups connections that form one chain or hub |
name | Optional label for the pair |
There is no connection_type, port, or protocol on a connection — a port belongs to the service. Connecting an entity to itself is rejected at validation.
| Endpoint | Description |
|---|---|
GET /service-connections | List connections in a scope |
POST /service-connections/batch | Create many at once; the batch shares one generated chain_id and a group_kind of chain or hub, while each item keeps its own direction |
PATCH /service-connections/{connection_id} | Change direction or name |
DELETE /service-connections/{connection_id} | Delete one connection |
PATCH/DELETE /service-connections/chains/{chain_id} | Operate on a whole chain or hub |
Service links
/service-links is the simpler service-to-service edge list used by the dependency map.
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://app.quazzar.space/service-links?project_id=PROJ_UUID&environment_id=ENV_UUID"POST /service-links takes {project_id, environment_id, from_service_id, to_service_id}. Both ends must be services, and they must differ.
Servers (Service Floors)
Servers represent compute — virtual machines, containers, or bare metal. The path is /service-floors with a hyphen.
POST /service-floors
curl -X POST https://app.quazzar.space/service-floors \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "project-uuid",
"environment_id": "env-uuid",
"type": "vm",
"hostname": "api-server-1",
"provider": "aws",
"provider_region": "us-east-1",
"provider_zone": "us-east-1a",
"provider_instance_id": "i-0123456789abcdef0",
"ip_address": "10.0.1.10",
"ip_addresses": ["10.0.1.10", "203.0.113.10"],
"cpu": 4,
"memory_mb": 16384,
"storage_gb": 100,
"instances": 1,
"tags": ["api"]
}'project_id, environment_id, and type are required. A floor is identified by hostname, not by a name, and its specification is flat — cpu, memory_mb, storage_gb — not a nested specs object. Memory is in megabytes. There is no monthly_cost field; cost is estimated from the specification, see GET /projects/{project_id}/costs/estimate.
Server types:
| Type | Description |
|---|---|
vm | Virtual machine |
container | Container instance |
baremetal | Physical dedicated server |
other | Anything else |
The bare-metal value is baremetal, one word.
Supported providers
aws, gcp, azure, hetzner, ovh, digitalocean, interserver, on-premise.
Any other value is rejected at validation on both services and floors.
GET /service-floors
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://app.quazzar.space/service-floors?project_id=PROJ_UUID&environment_id=ENV_UUID&type=vm"project_id and environment_id are required. Filters: hostname (substring), tags, type, location (across provider/region/zone), zone, updated_since.
| Endpoint | Description |
|---|---|
PATCH /service-floors/{floor_id} | Update a floor |
DELETE /service-floors/{floor_id} | Delete a floor |
GET /service-floors/{floor_id}/blast-radius | What depends on this server |
Networks
POST /networks
curl -X POST https://app.quazzar.space/networks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "project-uuid",
"environment_id": "env-uuid",
"name": "production-vpc",
"cidr": "10.0.0.0/16",
"gateway": "10.0.0.1",
"vlan_id": 100,
"is_private": true,
"description": "Primary production network"
}'project_id, environment_id, name, and cidr are required. is_private defaults to true. There is no dns_servers field. Network names are unique within a (project, environment) pair.
GET /networks
project_id and environment_id are required. Filters: q (searches name, cidr, gateway, vlan, mask), vlan_id, mask (prefix bits, 0—32), range (CIDR range prefix, e.g. 10.0.0.0), updated_since.
PATCH /networks/{network_id} and DELETE /networks/{network_id} complete the set.
Network Attachments
Attaching networks to a service or a server is one endpoint family, /attachments. Both halves are GET to read and PATCH to replace the whole set.
| Endpoint | Description |
|---|---|
GET /attachments/services/{service_id}/networks | Networks attached to a service |
PATCH /attachments/services/{service_id}/networks | Replace them |
GET /attachments/service-floors/{floor_id}/networks | Networks attached to a server |
PATCH /attachments/service-floors/{floor_id}/networks | Replace them |
curl -X PATCH https://app.quazzar.space/attachments/services/SVC_UUID/networks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"network_ids": ["net-uuid-1", "net-uuid-2"]}'The body is the complete list — the previous attachments are replaced, so send everything you want to keep. At most 10 networks per service or per floor; a longer list is refused at 422 by request validation, before any lookup happens.
GET returns {service_id, networks: [...]} or {service_floor_id, networks: [...]} with full network objects.
A service or floor that does not exist, and one in a project the caller does not belong to, both answer the same 404 (Service not found / ServiceFloor not found) — see Errors & Permissions. Permission to change the attachments is checked after that and refuses at 403.
The web app used to have separate service-networks and floor-networks screens; those routes now redirect to the unified attachments screen. The API has always addressed both under /attachments.
Import / Export
Both endpoints are multipart/form-data, not JSON, both are asynchronous, and both answer 202 Accepted.
POST /impex/export
curl -X POST https://app.quazzar.space/impex/export \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "project_id=PROJECT_UUID" \
-F "environment_id=ENV_UUID"Response (202 Accepted):
{
"status": "queued",
"job_id": "job-uuid",
"record_id": "record-uuid"
}The export runs in the background. Poll GET /impex/history?project_id={id} for its state; a finished record carries a signed download link, which GET /impex/download/{record_id} serves. That endpoint requires the sig and exp query parameters from the link and answers 410 Link expired past exp, so construct it from the record rather than by hand.
POST /impex/import
curl -X POST https://app.quazzar.space/impex/import \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "project_id=TARGET_PROJECT_UUID" \
-F "environment_id=TARGET_ENV_UUID" \
-F "[email protected]"The configuration arrives as an uploaded file, not as a JSON body. The document contains the environment’s groups, subgroups, services, service floors, networks, service links, and network attachments; GET /impex/example returns a specimen of the expected shape.
Resources are matched by name — an existing resource with the same name is updated, a new one is created — and the import re-checks the plan’s per-environment service limit as it goes, so a document that would exceed it stops there.
Authorization for both: the caller must be a member of the project (a non-member gets 404 Project not found, the same answer as a project that does not exist), the environment must belong to that project (400 otherwise), and the caller must then be the project owner or hold an environment role (403 Not authorized for this environment).
| Endpoint | Description |
|---|---|
GET /impex/history?project_id={id} | Recent import/export records; optional environment_id, limit (1—200, default 50) |
GET /impex/download/{record_id}?sig=...&exp=... | Download a completed export via its signed link |
GET /impex/example | An example document |
Auto-Discovery
Discovery imports existing infrastructure from a connected cloud account. It is scoped to the cloud account, not to a provider name in a body.
POST /integrations/cloud-accounts/{account_id}/discover
curl -X POST https://app.quazzar.space/integrations/cloud-accounts/ACCOUNT_UUID/discover \
-H "Authorization: Bearer YOUR_TOKEN"Response: {"status": "queued"}. There is no request body — the project and credentials come from the stored cloud account. Discovery runs as a background job and creates service floors for the resources it finds.
An account that does not exist and an account in someone else’s project both answer 404 CloudAccount not found — with the same detail deliberately, so the bodies cannot be compared to learn which uuids name real cloud accounts. Beyond that, the caller needs the connections.manage permission, because discovery decrypts and uses the credentials, and the account’s plan must include an integrations feature.
Connect an account first with POST /integrations/cloud-accounts, or through the per-provider endpoints (POST /integrations/cloud/{provider}/connect, GET /integrations/cloud/{provider}/status).
Next Steps
- Errors & Permissions — status codes and the tenancy rule
- Projects API — projects, environments, and groups
- Billing API — plans and limits
- Infrastructure Guide — best practices for organizing services