Skip to main content

API

Projects and backup tokens can be managed not only in the portal but through an HTTP API — for your own automation or through the Terraform provider. The API speaks JSON, lives at https://api.lionbackup.cloud/api/v1 and applies exactly the same permission checks as the portal.

Creating a key​

In the portal under Developer you create a service account and an API key for it. The key is shown exactly once — keep it somewhere safe.

A service account has exactly the permissions of the human who owns it. Someone who may not create projects in the portal cannot create them through the API either; deactivating the account or deleting the key closes the door immediately.

Exchanging the key for an access token​

The key is not a bearer token: you first exchange it for a short-lived access token (a JWT, valid for 15 minutes).

ACCESS_TOKEN=$(curl -s https://authentik.prod.lionbackup.cloud/application/o/token/ \
-d grant_type=client_credentials \
-d client_id=lionbackup-api \
-d client_secret="$LIONBACKUP_API_KEY" \
-d scope="profile lionbackup_api" | jq -r .access_token)

Always use the host authentik.prod.lionbackup.cloud for the exchange: the short name authentik.lionbackup.cloud answers with a redirect, and a POST does not follow redirects — the call would return no token.

Then send the token as the Authorization header:

API=https://api.lionbackup.cloud/api/v1

curl -s "$API/organizations" -H "Authorization: Bearer $ACCESS_TOKEN"
Development environment

The development environment uses https://api.dev.lionbackup.cloud/api/v1 and https://authentik.dev.lionbackup.cloud/application/o/token/. A key is valid only in the environment it was created in.

Step by step: create a project and backup tokens​

The following calls build on each other and assume ACCESS_TOKEN and API from the previous section. The identifiers in the responses are examples.

1. List the zones​

A project needs the identifier (UUID) of the zone its data will live in. Only zones with status = active can be booked.

curl -s "$API/zones" -H "Authorization: Bearer $ACCESS_TOKEN"
{
"zones": [
{
"id": "3f0c2a4e-8b1d-4c6a-9e2f-5d7b1a9c0e31",
"name": "de01-1",
"status": "active",
"provider": "Hetzner",
"location_city": "Nürnberg",
"storage_type": "managed object storage"
},
{
"id": "7a9d4b2c-1e5f-4a83-b6c0-2f8e9d1c4a57",
"name": "de01-2",
"status": "preparing",
"provider": "Hetzner",
"location_city": "Falkenstein",
"storage_type": "managed object storage"
}
]
}

The identifier of the zone you want goes straight into a variable:

ZONE_ID=$(curl -s "$API/zones" -H "Authorization: Bearer $ACCESS_TOKEN" \
| jq -r '.zones[] | select(.name == "de01-1") | .id')

2. Find your organization​

Projects belong to an organization. The list shows every organization you are a member of, together with your role — creating projects requires owner or admin.

curl -s "$API/organizations" -H "Authorization: Bearer $ACCESS_TOKEN"
{
"organizations": [
{
"id": "9b2e6c1a-4d3f-4e8b-a7c5-0f1d2e3a4b5c",
"name": "Example Ltd",
"status": "approved",
"role": "owner"
}
]
}
ORG_ID=$(curl -s "$API/organizations" -H "Authorization: Bearer $ACCESS_TOKEN" \
| jq -r '.organizations[] | select(.name == "Example Ltd") | .id')

Selecting by name is deliberate: the service account sees every organization of its owner, and the order of the list is not guaranteed.

3. Create a project​

Required fields are name (at most 100 characters) and availability_zone_id. Optional are alert_email, billing_reference, immutable_storage (default false) and — only with immutable storage — retention_days (1 to 365, default 30).

curl -s -X POST "$API/organizations/$ORG_ID/projects" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"web-servers\",\"availability_zone_id\":\"$ZONE_ID\",\"alert_email\":\"ops@example.com\"}"

Response with status 201:

{
"project": {
"id": "ea1296ad-5c7b-4f2e-8a1d-3b6c9e0f7d24",
"name": "web-servers",
"status": "active",
"organization_id": "9b2e6c1a-4d3f-4e8b-a7c5-0f1d2e3a4b5c",
"availability_zone_id": "3f0c2a4e-8b1d-4c6a-9e2f-5d7b1a9c0e31",
"alert_email": "ops@example.com",
"billing_reference": null,
"immutable_storage": false,
"retention_days": null,
"created_at": "2026-09-24 09:41:12.418273"
}
}
PROJECT_ID=ea1296ad-5c7b-4f2e-8a1d-3b6c9e0f7d24

4. Create backup tokens​

A backup token belongs to exactly one project. With type = write the client backs up, with type = read it restores. Without any body you get a write token for Linux; operating_system accepts Linux, Windows and macOS. Optionally usage_count_limit, rate_limit_per_minute and rate_limit_per_hour cap its use.

curl -s -X POST "$API/projects/$PROJECT_ID/tokens" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"write","operating_system":"Linux"}'

Response with status 201:

{
"token": {
"id": "c4d1e8f2-6a3b-4c9d-8e7f-1a2b3c4d5e6f",
"type": "write",
"secret": "9f3e7b1c…a41c"
}
}
The secret appears only in this response

secret is the actual backup token (128 hexadecimal characters). It is handed out exactly once and cannot be retrieved later — the platform stores only a hash. Put it into your secrets manager right away.

A read token for restores is created the same way:

curl -s -X POST "$API/projects/$PROJECT_ID/tokens" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"read"}'
{
"token": {
"id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"type": "read",
"secret": "d27c0a8e…5b93"
}
}

5. List tokens​

The list carries metadata only — never a secret. Revoked tokens no longer appear.

curl -s "$API/projects/$PROJECT_ID/tokens" -H "Authorization: Bearer $ACCESS_TOKEN"
{
"tokens": [
{
"id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"type": "read",
"operating_system": "Linux",
"is_active": true,
"usage_count": 0,
"usage_count_limit": null,
"created_at": "2026-09-24 09:43:05.102944",
"expire": null
},
{
"id": "c4d1e8f2-6a3b-4c9d-8e7f-1a2b3c4d5e6f",
"type": "write",
"operating_system": "Linux",
"is_active": true,
"usage_count": 0,
"usage_count_limit": null,
"created_at": "2026-09-24 09:42:37.556210",
"expire": null
}
]
}

6. Revoke a token​

TOKEN_ID=c4d1e8f2-6a3b-4c9d-8e7f-1a2b3c4d5e6f

curl -s -X DELETE "$API/projects/$PROJECT_ID/tokens/$TOKEN_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{ "deleted": true }

7. Close a project​

A project is not deleted but closed: its status changes to closing, every write token is revoked immediately, and the response reports how many. The call is idempotent — a project that is already closed answers 200 again.

curl -s -X POST "$API/projects/$PROJECT_ID/close" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"project": {
"id": "ea1296ad-5c7b-4f2e-8a1d-3b6c9e0f7d24",
"name": "web-servers",
"status": "closing",
"organization_id": "9b2e6c1a-4d3f-4e8b-a7c5-0f1d2e3a4b5c",
"availability_zone_id": "3f0c2a4e-8b1d-4c6a-9e2f-5d7b1a9c0e31",
"alert_email": "ops@example.com",
"billing_reference": null,
"immutable_storage": false,
"retention_days": null,
"created_at": "2026-09-24 09:41:12.418273"
},
"write_tokens_revoked": 1
}

What you should know​

Permissions per action​

The API checks the same roles as the portal:

ActionRole
read a project, list tokensany role in the project
update a project, create tokensowner, admin, writer
revoke a tokenowner, admin
create or close a projectowner or admin of the organization

404 instead of 403​

A resource that does not exist and one you hold no role on answer alike: 404 with {"error":"not_found"}. That way the API does not reveal which identifiers exist. You get a 403 with permission_denied only when you may see the resource but not perform the action.

Zone publication is asynchronous​

A new token is published to the storage zone after it has been created. Until that has happened the zone rejects the token with 401 — usually a matter of seconds, in case of a fault until the next reconciliation. Do not create the token again; retry later instead. Conversely, a revoked token may remain valid in the zone for a short while.

Closing a project​

Closing revokes every write token immediately and sets the project to closing. Read tokens stay valid for the retention period, and as long as a backup is still within its retention you can also create new read tokens to keep restoring stored backups. A closing project refuses new write tokens (409 project_closed); once it is closed it accepts no tokens at all. Likewise, a closed organization refuses new projects (409 organization_closed).

Rate limits​

LimitValueApplies to
Requests per IP address60/minuteevery request to the API
Requests per service account60/minuteevery authenticated request
Size of one request1 MBthe body you send

Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix time at which the current one-minute window ends). Over the limit the API answers 429 with {"error":"rate_limited"} and a Retry-After header — wait that many seconds and retry. The Terraform provider does this by itself.

The X-RateLimit-* headers can be absent: if the platform's counter cache is down, the API temporarily does not meter per service account and then announces no budget either. In your automation rely on the 429 status and Retry-After, not on the headers.

Error format​

Every error is a JSON object with exactly one field: {"error":"<code>"}. The code is stable and meant for programs; branch on it rather than on the HTTP status alone.

StatusCodeMeaning
400invalid_requestthe body is not JSON, a required field is missing or a value is invalid
400invalid_zoneavailability_zone_id is unknown or the zone is not active
400retention_out_of_rangeretention_days is outside 1 to 365
401invalid_tokenaccess token missing, invalid or expired, or service account deleted
403owner_inactivethe human who owns the service account is deactivated
403permission_deniedyour role does not allow this action
404not_foundresource unknown or not visible to you
405method_not_allowedthe path exists, the HTTP method does not
409organization_closedthe organization is closing or closed
409project_closedthe project is closed, or closing and a write token was requested
429rate_limitedrate limit — honour Retry-After
500internal_errorunexpected error on the platform
503service_unavailabletemporarily unavailable — retry later

Terraform​

For Terraform and OpenTofu there is a provider that uses the same API and manages projects and backup tokens as resources. Source, setup and a complete example are on the Terraform page.

Reference​

The full specification is available as an OpenAPI 3.1 document: openapi.yaml. The API serves the same file at /api/v1/openapi.yaml — so it always describes exactly the version that is running.

Loading the API reference…