Platform automation and diagnostics
This page is the machine contract for scripts, CI pipelines and AI agents that create and debug DanubeData resources without a human watching.
Every managed resource — VPS, databases, cache, queues, object storage, static sites, managed apps, uptime checks, metric alerts, block volumes — reports state through the same status_details model and answers the same diagnostic questions. Learn it once and it applies to every product.
Field names and value domains here change additively, never in place.
Rapids containers use this same model, plus a few surfaces unique to them (preflight, probe, revisions). See Rapids automation.
Authentication and project scope
All requests use a bearer token against https://danubedata.ro:
curl -H "Authorization: Bearer $DANUBE_TOKEN" \
-H "X-Team-Id: 42" \
https://danubedata.ro/api/v1/cache
X-Team-Id selects the project. Omit it and the account default is used, which is rarely what automation wants. With the CLI:
danube --project 42 cache get my-redis --json
danube project select --project 42 # persists the default, no prompt
Token abilities
Abilities follow one pattern across products: {product}:read, {product}:write, {product}:delete, and — where a product exposes workload output or cluster internals — {product}:diagnostics.
| Product | Ability prefix |
|---|---|
| VPS instances and block volumes | vps: |
| Databases | database: |
| Cache | cache: |
| Queues | queue: |
| Object storage | storage: |
| Static sites | static-site: |
| Managed apps | app: |
| Uptime checks | uptime: |
| Metric alerts | metric-alert: |
| Container registry | registry: |
| Rapids containers | serverless: |
:diagnostics is deliberately separate from :read. A token that may list your databases should not automatically be able to read what those databases print to their logs. Grant it only to automation that needs to debug.
The status model
Every resource response carries status_details. Read this rather than the legacy status string — it is the same object the dashboard uses, so they cannot disagree with you.
{
"summary": "failed",
"health": "unhealthy",
"observed_at": "2026-08-05T18:59:00+00:00",
"stale": false,
"operation": { "state": "failed", "terminal": true },
"error": {
"code": "cache.oom_killed",
"source": "kubernetes",
"resource": { "kind": "Pod", "name": "redis-my-cache-0" },
"reason": "OOMKilling",
"message": "The container exceeded its memory limit and was killed.",
"retryable": false,
"observed_at": "2026-08-05T18:59:00+00:00"
}
}
| Field | Meaning |
|---|---|
summary | pending, in_progress, ready, degraded, failed, stopped, deleting, unknown |
health | healthy, degraded, unhealthy, unknown — what is currently serving |
operation.state | queued, running, succeeded, failed, cancelled |
operation.terminal | The stop condition for polling |
observed_at | When the platform last checked — not when the row was last written |
stale | The platform could not confirm the state; the summary is the last known one |
error | Present only while summary is failed or degraded; null otherwise |
Three dimensions, because one string cannot answer three different questions: what is happening (summary), is my service working right now (health), and may I stop waiting (operation.terminal).
Polling correctly
Poll until operation.terminal is true. Never infer terminality from the status string, and never treat health: "unknown" as a failure — during a create or a rollout the platform genuinely does not yet know, and saying so is more honest than guessing.
until [ "$(danube database get my-pg --json | jq -r '.status_details.operation.terminal')" = "true" ]; do
sleep 5
done
A status string you do not recognise must be treated as still running, not as finished. New states are added additively; terminal is what protects you.
stale: true
The state shown is the last one confirmed, and an in-flight operation has gone unconfirmed for 15 minutes. It is a statement about the platform's confidence, not about your resource.
A settled state never goes stale — stale: false alongside an old observed_at is correct and expected. An error established an hour ago is still an error.
degraded is terminal but not an outage
For resources with replicas, degraded usually means a replica is unhealthy while the primary keeps serving. Automation that treats degraded as "down" will page you for a working system.
Capabilities: what to call before calling it
Every resource reports which diagnostic surfaces it can serve, so an agent can branch without probing and eating a 404 or 403:
"capabilities": { "logs": true, "events": true, "diagnose": true }
These are derived, not hardcoded. A resource whose events are genuinely unavailable reports events: false rather than advertising an endpoint that will fail. Static sites additionally report build_logs.
| Product | status_details | /logs | /events | /diagnose | /metrics |
|---|---|---|---|---|---|
| Cache | ✅ | ✅ | ✅ | ✅ | ✅ |
| Databases | ✅ | ✅ | ✅ | ✅ | ✅ |
| Queues | ✅ | ✅ | ✅ | ✅ | ✅ |
| Managed apps | ✅ | ✅ | ✅ | ✅ | ✅ |
| VPS | ✅ | — | ✅ | ✅ | ✅ |
| VPS block volumes | ✅ | — | ✅ | ✅ | — |
| Object storage | ✅ | — | — | ✅ | ✅ |
| Static sites | ✅ | build logs | — | ✅ | — |
| Uptime checks | ✅ | — | — | ✅ | — |
| Metric alerts | ✅ | — | — | — | — |
| Rapids containers | ✅ | ✅ | ✅ | ✅ | — |
| Container registry | — | — | ✅ | — | — |
VPS has no /logs by design. Guest operating system output is yours, not ours; we do not read inside your VM. Platform-side events are exposed instead.
Metric alerts have no /diagnose. An alert's history is its diagnosis — /metric-alerts/{id}/history returns each evaluation with the query behind it.
The registry has no status model. Repositories exist because something was pushed, so there is nothing to poll. /registry/preflight answers the question diagnose would.
The response envelope
Diagnostic endpoints return a consistent envelope:
{
"success": true,
"data": { },
"error": null,
"meta": { }
}
success reflects transport and authorisation only. A diagnose call that finds a fatal problem is still success: true — it succeeded at diagnosing. The verdict lives in data.
On failure, error carries a stable code and a retryable flag:
{
"success": false,
"data": null,
"error": {
"code": "cache.logs_unavailable",
"message": "The log backend did not respond.",
"retryable": true
},
"meta": {}
}
retryable is the field to branch on. true means the identical request may succeed later. false means it will not, and retrying only consumes your rate limit. An upstream that is unreachable returns HTTP 503 with retryable: true; a permission problem returns 403 with retryable: false.
Diagnostic responses are sent with Cache-Control: no-store, private. They can contain your log contents and must not be held by a shared cache.
Diagnose
GET /api/v1/{product}/{id}/diagnose correlates status, cluster events and log availability into a ranked list of findings:
{
"verdict": "failed",
"findings": [
{
"code": "cache.oom_killed",
"severity": "fatal",
"summary": "The container exceeded its memory limit and was killed.",
"remediation": "Increase the memory profile, or reduce the working set.",
"retryable": false
}
],
"status": { },
"sections": {
"events": { "available": true, "count": 12, "truncated": false },
"logs": { "available": true, "sampled_lines": 200 }
}
}
Branch on finding.code, never on summary or remediation. Codes are a stable interface. The prose is written for humans and may be reworded at any time.
findings[0] is the most severe finding. Severities, in order:
| Severity | Meaning |
|---|---|
fatal | Broken and will not recover on its own |
action_required | Working, but needs a decision from you |
transient_recovered | Failed and already recovered — not a current problem |
informational | Context, not a problem |
transient_recovered exists because Kubernetes emits warnings during perfectly normal reconciliation races. Reporting those as failures teaches operators to ignore diagnostics.
Sections degrade independently
If cluster events are unreachable, sections.events.available is false and diagnose still returns everything else. An empty section and an unavailable one are different answers, and the API never conflates them: available: true, count: 0 means "we looked and there was nothing".
Full code list: Failure codes.
Logs
GET /api/v1/{product}/{id}/logs requires {product}:diagnostics.
| Parameter | Meaning |
|---|---|
since | Relative (15m, 2h, 7d) or ISO-8601 |
level | Filter by severity where the workload emits one |
limit | Lines to return |
cursor | Opaque; pass the previous response's cursor to continue |
Retention is bounded — a since beyond the retention window returns what exists rather than failing.
Events
GET /api/v1/{product}/{id}/events returns platform-side Kubernetes events for the objects backing your resource, newest first, capped at 500 with a truncated flag. Events are the raw material /diagnose classifies; read them when you want to see what diagnose was looking at.
Rate limits
Diagnostic endpoints share a budget separate from the general API limit: 60 requests per minute per token and 180 per minute per project. An agent debugging many resources at once should diagnose serially rather than fanning out.
CLI exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Command failed, or a diagnose found a fatal problem |
2 | Usage error |
3 | Authentication or authorisation failure |
--json prints the envelope verbatim on stdout, so a script can read the same structure the API returned.
Rules for agents
- Poll
operation.terminal. Never the status string. - Treat unknown enum values as "keep waiting". Domains grow additively.
- Branch on
code, never on prose.finding.codeanderror.codeare stable;messageandsummaryare not. - Respect
retryable: false. Retrying wastes your rate limit and changes nothing. - Read
capabilitiesbefore calling a sub-resource. It exists so you do not have to probe. health: "unknown"during an operation is not a failure.degradedis not necessarily an outage. Check what is serving.stale: trueis about our confidence, not your resource.- Distinguish empty from unavailable.
available: falsemeans we could not look. - Send
X-Team-Idexplicitly. Never rely on the account default.