Skip to content
Console

REST API

SdV Cloud exposes a JSON:API-compliant REST API for managing organizations, projects, service containers, and deployments, scoped to a single organization per token.

All endpoints are relative to:

http://localhost/api/v1

Requests are authenticated with a personal access token, generated from Settings → API Tokens — the same token type used by the MCP server.

Authorization: Bearer <your-api-token>

Every token is created with:

  • One organization — encoded as an internal org:{organization_id} ability at creation time. All requests made with the token operate within that organization only, and this cannot be changed later. To act within a different organization, revoke the token and generate a new one scoped to it.
  • One or more abilities, which gate which endpoints the token can call. These are the same abilities used by the MCP server:
Ability Grants
projects.read GET /projects, GET /projects/{project}
projects.write POST /projects, PATCH /projects/{project}, DELETE /projects/{project}
service-containers.read GET /service-containers, GET /service-containers/{serviceContainer}
service-containers.write POST /service-containers, PATCH /service-containers/{serviceContainer}, DELETE /service-containers/{serviceContainer}
deployments.read GET /deployments, GET /deployments/{deployment}
deployments.write POST /service-containers/{serviceContainer}/deployments, POST /deployments/{deployment}/cancel

A single token works for both the REST API and the MCP server. Calling an endpoint without the required ability returns a 403 Forbidden (see Errors) rather than a generic authentication failure. Beyond token abilities, some actions are further gated by your role in the organization or project (for example, only organization Owners can create projects).

GET /organization requires no specific ability — access is governed entirely by the token’s organization scope.

Every request must send:

Authorization: Bearer <your-api-token>
Accept: application/vnd.api+json

POST and PATCH requests that include a body must also send:

Content-Type: application/json

All responses — including errors — are returned as application/vnd.api+json.

Responses follow the JSON:API document structure: every resource is wrapped in a top-level data object (or array, for collections) with id, type, and attributes. Attribute keys are camelCase.

{
"data": {
"id": "01991a00-0000-7000-8000-000000000001",
"type": "projects",
"attributes": {
"name": "astro-blog",
"description": "Marketing blog"
}
}
}

Resources that belong to another resource (a service container’s project, a deployment’s service container and stages) expose that relationship as a reference — an id and type, without the related resource’s attributes:

"relationships": {
"project": { "data": { "id": "01991a00-0000-7000-8000-000000000001", "type": "projects" } }
}

Pass ?include=<relationship> to also embed the full related resource(s) in a top-level included array. For example, GET /deployments/{deployment}?include=stages embeds each deployment stage.

List endpoints (GET /projects, GET /service-containers, GET /deployments) are paginated, 15 items per page by default:

GET /projects?page=2
{
"data": [ ... ],
"links": {
"first": "http://localhost/api/v1/projects?page=1",
"last": "http://localhost/api/v1/projects?page=3",
"prev": "http://localhost/api/v1/projects?page=1",
"next": "http://localhost/api/v1/projects?page=3"
},
"meta": {
"current_page": 2,
"from": 16,
"last_page": 3,
"per_page": 15,
"to": 30,
"total": 42
}
}

List endpoints accept filter[...] query parameters and a sort parameter. Prefix a sort field with - for descending order (e.g. sort=-createdAt). Supported fields are documented per endpoint below.

Errors follow the JSON:API error object format — a top-level errors array, even when there’s a single error:

{
"errors": [
{
"status": "404",
"title": "Not Found",
"detail": "The requested resource could not be found."
}
]
}
Status When Notes
401 Unauthenticated Missing or invalid bearer token
403 Forbidden Token lacks the required ability, or your role doesn’t permit the action detail: "Invalid ability provided." for a missing ability
404 Not Found The resource doesn’t exist, or belongs to a different organization A project (or any resource) outside your token’s organization always 404s, never 403s — organizations can’t detect each other’s resources
409 Conflict The action can’t be performed in the resource’s current state e.g. deleting a service container with a deployment in progress, or starting a deployment while one is already running
422 Unprocessable Entity Request body failed validation One error object per failing field/rule, each with a source.pointer (e.g. /data/attributes/name)
429 Too Many Requests Rate limit exceeded See Rate limits

Validation errors report every failing field:

{
"errors": [
{
"status": "422",
"title": "Unprocessable Entity",
"detail": "The name field is required.",
"source": { "pointer": "/data/attributes/name" }
}
]
}

Requests are limited to 60 per minute, keyed per authenticated user (shared across all of that user’s tokens). Exceeding the limit returns 429 Too Many Requests.

GET /organization

Returns the organization your token is scoped to.

Terminal window
curl http://localhost/api/v1/organization \
-H "Authorization: Bearer <your-api-token>" \
-H "Accept: application/vnd.api+json"
{
"data": {
"id": "01991a00-0000-7000-8000-0000000000f0",
"type": "organizations",
"attributes": { "name": "Acme Inc.", "mfaRequired": false }
}
}
GET /projects

Requires projects.read. Returns the projects you’re a member of (organization Owners see every project).

Query parameter Description
filter[name] Filter to projects whose name contains this value
sort name or createdAt, prefix with - for descending
page Page number
Terminal window
curl "http://localhost/api/v1/projects?filter[name]=blog&sort=-createdAt" \
-H "Authorization: Bearer <your-api-token>" \
-H "Accept: application/vnd.api+json"

Returns an array of project resources (see the response shape below), paginated.

POST /projects

Requires projects.write and the organization Owner role.

Field Type Required Description
name string Yes Project name (max 255 characters), unique within the organization
description string No Project description (max 1000 characters)
Terminal window
curl -X POST http://localhost/api/v1/projects \
-H "Authorization: Bearer <your-api-token>" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/json" \
-d '{"name": "astro-blog", "description": "Marketing blog"}'

Returns 201 Created with a Location header pointing at the new project.

{
"data": {
"id": "01991a00-0000-7000-8000-000000000001",
"type": "projects",
"attributes": { "name": "astro-blog", "description": "Marketing blog" }
}
}
GET /projects/{project}

Requires projects.read. Same response shape as Create a project.

PATCH /projects/{project}

Requires projects.write.

Field Type Required Description
name string Yes Project name (max 255 characters), unique within the organization
description string No Project description (max 1000 characters)

Returns the updated project, same shape as Create a project.

DELETE /projects/{project}

Requires projects.write. Returns 204 No Content.

A service container’s gitProvider and gitRepositoryName are fixed at creation and can’t be changed afterwards.

GET /service-containers

Requires service-containers.read.

Query parameter Description
filter[projectId] Only service containers belonging to this project
filter[name] Filter to service containers whose name contains this value
sort name or createdAt, prefix with - for descending
page Page number

Returns an array of service container resources, paginated.

POST /service-containers

Requires service-containers.write and the organization Owner or project Developer role. Only gitProvider: gitlab currently provisions a deploy token automatically.

Field Type Required Description
projectId string Yes UUID of the project to create the service container in
name string Yes Service container name (max 255 characters), unique within the project
containerType string Yes One of http, worker, scheduled
gitProvider string Yes One of gitlab, github
gitRepositoryId string Yes Provider-specific repository id
gitRepositoryName string Yes Repository path, e.g. my-group/my-repo
Terminal window
curl -X POST http://localhost/api/v1/service-containers \
-H "Authorization: Bearer <your-api-token>" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/json" \
-d '{
"projectId": "01991a00-0000-7000-8000-000000000001",
"name": "api",
"containerType": "http",
"gitProvider": "gitlab",
"gitRepositoryId": "123456",
"gitRepositoryName": "my-group/api"
}'

Returns 201 Created with a Location header. The new service container isn’t deployable yet — configure branchToDeploy and port with Update a service container, then trigger a deployment.

{
"data": {
"id": "01991a00-0000-7000-8000-000000000002",
"type": "service-containers",
"attributes": {
"name": "api",
"containerType": "http",
"gitProvider": "gitlab",
"gitRepositoryName": "my-group/api",
"branchToDeploy": null,
"buildMode": "automatic",
"dockerfilePath": null,
"port": null,
"memoryLimit": 512,
"cpuLimit": 0.5,
"environmentVariables": [],
"autoscalingEnabled": false,
"minReplicas": null,
"maxReplicas": null,
"cpuThreshold": null,
"hibernationEnabled": false,
"hibernationSchedules": null,
"ipAllowlistEnabled": false,
"ipAllowlist": null
},
"relationships": {
"project": {
"data": {
"id": "01991a00-0000-7000-8000-000000000001",
"type": "projects"
}
}
}
}
}
GET /service-containers/{serviceContainer}

Requires service-containers.read. Pass ?include=project to embed the parent project. Unlike the MCP server, the REST API returns environment variable values, not just keys — treat responses accordingly.

PATCH /service-containers/{serviceContainer}

Requires service-containers.write. All fields are optional and only replace what’s provided; array fields (environmentVariables, hibernationSchedules, ipAllowlist) are replaced wholesale when passed.

Field Type Description
name string Service container name (max 255 characters), unique within the project
branchToDeploy string Git branch to deploy
buildMode string One of automatic, dockerfile
dockerfilePath string Path to the Dockerfile, relative to the repository root (required with buildMode: dockerfile)
port integer Exposed port (1-65535)
memoryLimit integer Memory limit in MB (250-8000)
cpuLimit number CPU limit in cores (0.25-4)
environmentVariables array of {key, value} Replaces all environment variables
autoscalingEnabled boolean Enable CPU-based autoscaling
minReplicas integer Minimum replica count (1-10)
maxReplicas integer Maximum replica count (1-10)
cpuThreshold integer CPU percentage that triggers scaling (10-90)
hibernationEnabled boolean Enable scheduled hibernation
hibernationSchedules array of {days, startTime, endTime} Replaces all hibernation schedules. days is an array of mon-sun; startTime/endTime are HH:MM (24-hour)
ipAllowlistEnabled boolean Restrict access to an IP allowlist
ipAllowlist array of {ip, description} Replaces the IP allowlist

Returns the updated service container, same shape as Create a service container.

DELETE /service-containers/{serviceContainer}

Requires service-containers.write. Returns 204 No Content, or 409 Conflict if a deployment is in progress.

POST /service-containers/{serviceContainer}/deployments

Requires deployments.write. Starts a new deployment asynchronously and returns immediately with the created deployment in pending status — poll Get a deployment to follow its progress through the build/deploy stages.

Terminal window
curl -X POST http://localhost/api/v1/service-containers/01991a00-0000-7000-8000-000000000002/deployments \
-H "Authorization: Bearer <your-api-token>" \
-H "Accept: application/vnd.api+json"

Returns 202 Accepted with a Location header pointing at the deployment. Fails with 409 Conflict if the service container isn’t fully configured yet, or already has a deployment in progress.

{
"data": {
"id": "01991a00-0000-7000-8000-000000000003",
"type": "deployments",
"attributes": {
"status": "pending",
"triggeredVia": "api",
"triggeredByUserId": null,
"commitSha": null,
"commitMessage": null,
"branch": "main",
"imageTag": null,
"errorMessage": null,
"startedAt": null,
"finishedAt": null,
"createdAt": "2026-07-24T09:00:00.000000Z"
},
"relationships": {
"serviceContainer": {
"data": {
"id": "01991a00-0000-7000-8000-000000000002",
"type": "service-containers"
}
},
"stages": { "data": [] }
}
}
}
GET /deployments

Requires deployments.read.

Query parameter Description
filter[serviceContainerId] Only deployments for this service container
filter[status] One of pending, building, deploying, succeeded, failed, cancelled
sort createdAt, prefix with - for descending
page Page number

Returns an array of deployment resources (same shape as Trigger a deployment), paginated.

GET /deployments/{deployment}

Requires deployments.read. Pass ?include=stages to embed each build/deploy stage in included:

{
"data": {
"id": "01991a00-0000-7000-8000-000000000003",
"type": "deployments",
"attributes": {
"status": "succeeded",
"triggeredVia": "api",
"...": "..."
},
"relationships": {
"stages": {
"data": [
{
"id": "01991a00-0000-7000-8000-000000000004",
"type": "deployment-stages"
}
]
}
}
},
"included": [
{
"id": "01991a00-0000-7000-8000-000000000004",
"type": "deployment-stages",
"attributes": {
"name": "build",
"status": "success",
"duration": 42
}
}
]
}
POST /deployments/{deployment}/cancel

Requires deployments.write. Only deployments in pending, building, or deploying status can be cancelled — otherwise returns 409 Conflict. Returns 204 No Content.