REST API
The address, how a script signs in, how the organization is chosen, what errors look like, and worked examples with curl.
Everything the console and the CLI do, they do through one HTTP API. The CLI is
built on it and has no private door: whatever isogrid can do, a script with
the same credential can do with curl.
This page is a guide to using that API: the address, how a script signs in, and the calls most teams need. It does not list every endpoint. If you need one that is not shown here, the CLI covers the same ground, and the team will give you the exact call on request.
The address
Every call goes to https://api.isogrid.skyvault.pro/api/v1.
The examples below describe what the API accepts today. Check them again after a platform upgrade rather than assuming a field is still there.
Signing in from a script
Every call carries a bearer token:
Authorization: Bearer <token>
For a script, that token is a CLI credential: a long-lived token that starts
with skv_, minted by you, in your name, for one organization. You create it
with the CLI and approve it in the browser. There is no form in the console
that hands out a token without that approval.
export ISOGRID_CONFIG_HOME="$(mktemp -d)"
isogrid --api https://api.isogrid.skyvault.pro login --name my-script
jq -r '.contexts[].token' "$ISOGRID_CONFIG_HOME/config.json"
rm -rf "$ISOGRID_CONFIG_HOME"
The temporary ISOGRID_CONFIG_HOME keeps the sign-in on your own machine
untouched. Installing the CLI covers getting isogrid.
What to know about the credential:
- It is pinned to the organization shown on the approval page. Check it before you approve.
- It never exceeds what you hold. Its permissions are intersected with your own role and groups on every request, so reducing your access reduces the script's too.
- Narrow it. Add
--scopewith only what the script needs, for example--scope application:create,application:write-any. Without--scope, the approval page proposes what suits your role and you untick the rest. See Deploying from CI/CD for the scopes a deployment needs. - It lasts 90 days unless you chose a shorter life on the approval page.
- Some things it can never do, whatever it asks for: billing, appointing administrators, and minting or revoking credentials. Those need you, signed in to the console.
- Revoke it from the console's CLI page when the script is retired or the token may have leaked.
The examples below assume:
export ISOGRID_API=https://api.isogrid.skyvault.pro/api/v1
export ISOGRID_TOKEN=skv_...
Keep the token out of your shell history and out of your repository, the same way you would a password.
Choosing the organization
A person can belong to several organizations, so anything that belongs to one needs to know which. You say so with a header, giving the organization's slug or its id:
X-Organization: acme
The same value is accepted as a query parameter, ?organization=acme, for the
cases where a header is awkward.
With a CLI credential you can leave it out: the credential already belongs to
one organization and every call acts there. Naming a different one is refused
with 403, and the error says which organization the credential is bound to.
What an error looks like
Every refusal has the same shape, whatever went wrong:
{
"error": {
"code": "permission_denied",
"message": "Your role in Acme (developer) does not allow this",
"details": {
"required_permission": "application:create",
"role": "developer",
"organization": "acme"
}
}
}
message is written to be shown to a person. code is what a script should
branch on. details varies with the error and may be empty.
| Status | code |
Meaning |
|---|---|---|
401 |
unauthenticated |
No token, or one that is unknown, expired or revoked. |
402 |
insufficient_credits |
The organization cannot pay for what was asked. details says how much was needed. |
403 |
permission_denied |
You, or this credential, may not do this. |
404 |
not_found |
It does not exist, or it is not yours to see. |
409 |
conflict |
It clashes with what exists: a name already taken, or a product with no price in that region yet. |
422 |
validation_error |
The request is malformed. details.fields names each field and what is wrong with it. |
503 |
no_capacity |
The platform cannot host this right now. The request was not wrong; retry later. |
With curl, use -f (or --fail-with-body, to keep the message) so a refusal
fails your script instead of being piped into the next command.
Lists and pages
The main lists (applications, databases, database instances, networks, regions, images) return one page at a time:
{ "items": [ ... ], "total": 37, "limit": 50, "offset": 0 }
limit is 50 by default and 200 at most. offset is where the page starts. To
read everything, add limit to offset until it reaches total.
Not every list is paged this way. A few short ones (an application's builds, the instance types of a region) return a plain array. The examples below show which shape each one has.
Things that take time
Creating something returns before it is running. The answer tells you the thing
exists and gives you its id; you then read its status until it settles.
| You asked for | The call answers | Then read | Until |
|---|---|---|---|
| An application | 201, with the application |
GET /applications/{id}/status |
deployment_status is running, or failed |
| A build | 202, with a build_id |
GET /applications/{id}/builds/{build_id} |
status is succeeded, or one of failed, no_dockerfile, restricted, cancelled |
| A redeployment | 202 |
GET /applications/{id}/status |
as for an application |
| A database | 202, with the database |
GET /databases/{id} |
status is ready, or failed |
An application's deployment_status is one of none, queued, deploying,
starting, running, degraded, stopped or failed. degraded means it is
up but not wholly: fewer copies running than asked for, or copies that keep
restarting. The message beside it says why in words.
Poll every few seconds, not in a tight loop. Build output is read the same way:
GET /applications/{id}/builds/{build_id}/logs?after=0 returns the lines so far
and a last_id; pass that as after to get only what is new.
Worked examples
Who am I, and where can I deploy
curl -fsS "$ISOGRID_API/me" -H "Authorization: Bearer $ISOGRID_TOKEN"
returns your account and the organizations you belong to. The regions open to you, and the instance types a region offers with their prices:
curl -fsS "$ISOGRID_API/clusters" -H "Authorization: Bearer $ISOGRID_TOKEN" \
| jq '.items[] | {id, slug, name}'
curl -fsS "$ISOGRID_API/pricing/applications/$CLUSTER_ID" \
-H "Authorization: Bearer $ISOGRID_TOKEN" \
| jq '.[] | {id, name, vcpu, memory_mb, prices}'
In the API a region is a cluster, and an instance type is a resource_tier.
List your applications
curl -fsS "$ISOGRID_API/applications?limit=20" \
-H "Authorization: Bearer $ISOGRID_TOKEN" \
| jq '.items[] | {id, slug, deployment_status, public_url}'
Create an application from an image
curl -fsS -X POST "$ISOGRID_API/applications" \
-H "Authorization: Bearer $ISOGRID_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "hello",
"source": "registry",
"external_image": "nginx:1.27",
"port": 80,
"is_public": true
}'
The answer is the application, with its id, its slug, and the address it
will answer on in public_url. Its first deployment is already queued.
What the fields mean, and what happens when you leave one out:
| Field | Meaning | Left out |
|---|---|---|
name |
Required. | |
source |
registry for an image; github or gitlab to build from a repository. |
github |
external_image |
An image in any registry, in full: nginx:1.27, ghcr.io/acme/web:main. |
|
external_registry_id |
The stored registry whose credentials pull it. | The image must be public. |
cluster_id |
The region. | The region of the network you named, else the platform's default region. |
resource_tier_id |
The instance type. | The smallest the region offers. |
network_id |
The private network it runs on. | Your organization's own network in that region. |
port |
The port the container listens on. | 8080 for an image. |
replicas |
Number of copies, 1 to 10. | 1 |
environment |
Non-secret settings, as an object of strings. | None. |
is_public |
Whether it is reachable from the internet. | false |
An image in your organization's own registry is named with image_id instead
(GET /images lists them). To build from a repository, send
repository_full_name (acme/api) and, if it is not main, default_branch;
the repository must already be connected to your organization, as described in
Git repositories.
Secrets do not go in environment. Send them in secrets; they are stored
apart and never returned by any call.
Read its status
curl -fsS "$ISOGRID_API/applications/$APP_ID/status" \
-H "Authorization: Bearer $ISOGRID_TOKEN"
{
"application_id": "5b0c...",
"deployment_status": "running",
"desired": 1,
"running": 1,
"message": null,
"checked_at": "2026-10-07T09:12:44Z",
"listens": true,
"tasks": [ ... ]
}
desired is the number of copies asked for and running the number up now. To
wait in a script:
while :; do
STATUS=$(curl -fsS "$ISOGRID_API/applications/$APP_ID/status" \
-H "Authorization: Bearer $ISOGRID_TOKEN" | jq -r '.deployment_status')
echo "$STATUS"
case "$STATUS" in running|degraded|stopped|failed) break ;; esac
sleep 5
done
Deploy again
For an application that runs an image, redeploy it:
curl -fsS -X POST "$ISOGRID_API/applications/$APP_ID/deploy" \
-H "Authorization: Bearer $ISOGRID_TOKEN"
For one built from a repository, start a build of a branch, a tag or a commit; it is deployed when the build succeeds:
curl -fsS -X POST "$ISOGRID_API/applications/$APP_ID/builds" \
-H "Authorization: Bearer $ISOGRID_TOKEN" \
-H "Content-Type: application/json" \
-d '{"git_ref": "main"}'
List your databases
curl -fsS "$ISOGRID_API/databases" -H "Authorization: Bearer $ISOGRID_TOKEN" \
| jq '.items[] | {id, name, engine, status}'
These are the databases created on a shared server. Database servers of your
own are a separate list, GET /database-instances. A database's status is
one of pending, creating, importing, ready, failed or deleting.
Connection details, password included, are a separate call
(GET /databases/{id}/connection) with a separate permission: being able to see
that a database exists is not the same as being handed its credentials.
Edge cases worth knowing before you meet them
A 401 on a token that worked yesterday. It expired or was revoked. Mint a
new one; nothing else changes. The answer is deliberately the same for an
unknown, an expired and a revoked token.
A 403 that names another organization. You sent X-Organization for an
organization the credential is not bound to. Drop the header, or mint a
credential while the console is showing the organization you mean.
A 409 on create that mentions a price. That product has no price in that
region yet, so it is not for sale there. Choose another region or ask. See
Prices and billing.
A 402 on is_public. Your organization owes for earlier usage, and
publishing is withheld until it is settled. Create it private, or top up.
The create call succeeded and the application failed. The call only
promised that the application exists. Read /status, then the build or
deployment log. When things go wrong covers the usual
causes.
Money is in nanos. Amounts ending in _nanos are billionths of the
currency unit: 1500000000 is 1.5. The prices object beside them carries the
same figure already formatted for display.
What to read next
- Command reference for the same operations from the CLI.
- Deploying from CI/CD for a pipeline that needs no API code.
- Roles, permission groups and access for what a credential may be allowed.
- Prices and billing.