API & Automation
The panel is a client of this API. So is anything you build on it.
# ids come from GET /app-templates and /pod-subscriptions$ curl -X POST https://api.portal.calibernode.com/api/v1/apps \-H "Authorization: ApiKey cp_3f9a…" \-H "Content-Type: application/json" \-d '{"name": "blog","subscriptionId": "cms692u1a…","appTemplateId": "cms692u14…","cpuCores": 1,"ramMb": 1024,"diskGb": 10}'201 Created{"id": "cm7q1x…","name": "blog","status": "DEPLOYING","url": null}# Poll until it is RUNNING — no fixed sleep required$ curl -H "Authorization: ApiKey cp_3f9a…" \https://api.portal.calibernode.com/api/v1/apps/cm7q1x…
An app in one call
Post a subscription, a template and the slice you want, and get an id back straight away. Deploying is real work — images are pulled, a database is created, secrets are generated, a certificate is issued — so the call returns a status rather than holding a connection open and calling itself instant.
Poll the resource until it reports RUNNING. No fixed sleep, no guessing how long an image pull takes on a busy host.
Failures you can program against
Every error returns the same envelope with a stable, machine-readable code. Switch on the code; show the message to a person. You should never have to string-match an error message, and you should never have to guess from a bare status what actually happened.
Validation failures carry a details object naming the field, so a form can point at the input that was wrong instead of showing a banner and making the user hunt.
400 Bad Request{"error": {"code": "BAD_REQUEST","message": "The requested resources exceed yoursubscription headroom.","details": { "field": "ramMb" }}}# code is stable and switchable on. message is for humans.# NOT_FOUND · FORBIDDEN · UNAUTHORIZED · BAD_REQUEST
What it reaches
Everything, because the panel uses it too
There is no second internal API quietly doing the interesting parts.
Apps, end to end
Deploy, resize, redeploy, restart and destroy. Manage domains, environment variables, snapshots and subscriptions. Everything the panel does to an app, the API does — because the panel is calling these same endpoints.
Keys scoped per resource
Scopes across apps, subscriptions, billing and tickets, each read or write. A monitoring script gets read access and nothing else. Keys are shown once, stored hashed, and revocable individually.
One error shape, always
Every failure returns the same envelope with a stable machine-readable code. You switch on the code and show the message. You never string-match prose, and you never infer what went wrong from a bare status.
Asynchronous, and honest about it
Deploying an app returns immediately with a status you can poll. Deployment is genuine work — image pull, database, secrets, certificate — so the API reports state rather than holding a connection open and pretending it is instant.
Audited like everything else
Calls made with an API key land in the same audit log as panel actions, with the action, the target and the source address. You can see what a script did, and when it did it.
Idempotent where it matters
Payment fulfilment runs through a single funnel with a conditional state transition, so an invoice settles once no matter how many times a gateway redelivers. Wallet credits take a lock on the payment reference for the same reason.
Same API, no tiers
No enterprise plan unlocks it and there is no per-call pricing. The smallest plan on the site has exactly the access the largest one does.
Keys, scopes and revocation are covered on the networking & security page.
Questions
Frequently asked questions
Build against it
Create an account, mint a scoped key, and deploy your first app from a script.