DocsAstral API

Astral API

The live HTTP API under the CLI: device login, whoami, the tool catalog, tool calls and access tokens, with a sample request and response for each endpoint.

Call every Astral tool over HTTP: sites, code, domains and more. Send an access token, read the tool catalog, and call a tool by name. The Astral CLI and astral mcp use this same API.

Status: live. Every endpoint on this page works today.

bash
curl https://www.astralos.ai/api/v1/whoami \  -H "Authorization: Bearer $ASTRAL_TOKEN"

Base URL and versions#

text
https://www.astralos.ai/api/v1
  • Use this exact host. The bare astralos.ai host redirects, and the redirect drops the Authorization header.
  • Every response has the header Astral-Api-Version: 1.
  • Version 1 changes only by added fields, added endpoints and added error types. Your code must ignore fields that it does not know.

Authentication#

Send an access token as a bearer token:

text
Authorization: Bearer astral_3pQx...
  • A token is astral_ followed by 43 characters. Astral shows it one time and stores only a hash.
  • A token acts for one person in one workspace, or for one agent of that workspace. It never has more rights than that person or agent.
  • Scopes narrow a token. See Scopes.
  • Get a token in one of three ways: astral login (a browser approves it), astral token create, or POST /api/v1/tokens with a token that you already have.

Response format#

Every endpoint except the two device login endpoints answers in one envelope.

Success:

json
{ "ok": true, "data": { } }

Failure:

json
{  "ok": false,  "error": {    "type": "not_found",    "message": "No tool sites.lst.",    "details": { }  }}

The error.type values are in Errors.

Endpoints#

MethodURLWhat it does
POST/api/v1/auth/device/codeStarts a device login.
POST/api/v1/auth/device/tokenGets the token after a person approves the login.
GET/api/v1/whoamiShows who the token acts for.
GET/api/v1/catalogLists the tools that the token can call.
POST/api/v1/tools/{app}.{tool}Calls one tool.
GET/api/v1/tokensLists your tokens.
POST/api/v1/tokensMakes a token.
DELETE/api/v1/tokens/{id}Revokes a token.

Device login#

A script or an agent gets a token without a password: it shows a short code, and a person approves the code in the browser. This is the OAuth device flow (RFC 8628). These two endpoints take a form-encoded body (JSON also works) and answer with flat OAuth JSON, not the envelope.

POST/api/v1/auth/device/code#

Starts a device login. Show the user_code to the person, and open verification_uri_complete for them. Then poll POST /api/v1/auth/device/token.

No token is needed.

Body fieldRequiredMeaning
client_idnoA name for your app, shown to the person, for example deploy-script. The default is Astral CLI.
scopenoScopes, separated by spaces, for example sites:read code:read. The default is *.

Request:

bash
curl -X POST https://www.astralos.ai/api/v1/auth/device/code \  -d client_id=deploy-script \  -d scope="sites:read code:read"

Response (200):

json
{  "device_code": "Zp3rVb0kT1m8yQe5Wn2aL7cHx4sJd9uFg6oRi0pKqXc",  "user_code": "WDJB-MJHT",  "verification_uri": "https://www.astralos.ai/device",  "verification_uri_complete": "https://www.astralos.ai/device?code=WDJB-MJHT",  "expires_in": 900,  "interval": 5}

POST/api/v1/auth/device/token#

Gets the token. Poll it every interval seconds until the person approves or denies the login, or until expires_in ends.

No token is needed. The device_code is the secret: keep it private.

Body fieldRequiredMeaning
grant_typeyesAlways urn:ietf:params:oauth:grant-type:device_code.
device_codeyesThe device_code from the first step.
error (HTTP 400)MeaningWhat to do
authorization_pendingNobody has answered yet.Poll again after interval seconds.
slow_downYou polled too soon.Add 5 seconds to your interval.
access_deniedThe person denied the login.Stop.
expired_tokenThe 15 minutes ended.Start again.

Request:

bash
curl -X POST https://www.astralos.ai/api/v1/auth/device/token \  -d grant_type=urn:ietf:params:oauth:grant-type:device_code \  -d device_code=Zp3rVb0kT1m8yQe5Wn2aL7cHx4sJd9uFg6oRi0pKqXc

Response while nobody has answered (400):

json
{  "error": "authorization_pending",  "error_description": "Waiting for a person to approve."}

Response after the person approves (200):

json
{  "access_token": "astral_3pQxW8...",  "token_type": "Bearer",  "expires_in": 7776000,  "scope": "code:read sites:read"}

Account#

GET/api/v1/whoami#

Shows who the token acts for, its workspace and its scopes. Use it to test a token.

principal.type is agent when the token acts for an agent.

Request:

bash
curl https://www.astralos.ai/api/v1/whoami \  -H "Authorization: Bearer $ASTRAL_TOKEN"

Response (200):

json
{  "ok": true,  "data": {    "principal": {      "type": "user",      "user_id": "b4478648-0000-4000-8000-000000000001",      "agent_id": null    },    "workspace_id": "89e83749-0000-4000-8000-000000000002",    "auth": "token",    "token": {      "id": "6a1f0c2e-0000-4000-8000-000000000003",      "name": "deploy-script",      "scopes": ["code:read", "sites:read"],      "expires_at": "2027-01-24T10:00:00.000Z"    }  }}

Tools#

GET/api/v1/catalog#

Lists every tool that this token can call, with its input schema. Scopes narrow the list.

HeaderMeaning
If-None-MatchThe ETag from your last call. The answer is 304 Not Modified when nothing changed.
FieldMeaning
nameThe name to call: {app}.{tool}.
accessread (the tool only reads) or write.
approvalThe catalog flag: never, always or conditional. The workspace's own rules decide if a call needs a person.
input_schemaThe JSON Schema of the tool's input.

Request:

bash
curl https://www.astralos.ai/api/v1/catalog \  -H "Authorization: Bearer $ASTRAL_TOKEN"

Response (200):

json
{  "ok": true,  "data": {    "version": "1",    "tools": [      {        "name": "sites.list",        "app": "sites",        "tool": "list",        "description": "List the sites you can access...",        "access": "read",        "approval": "never",        "input_schema": {          "type": "object",          "properties": {            "kind": { "type": "string", "enum": ["site", "app", "api"] }          }        }      }    ]  }}

POST/api/v1/tools/{app}.{tool}#

Calls one tool. The body is the tool's input, as a JSON object. The answer's data is the tool's own output.

Path parameterMeaning
{app}.{tool}A tool name from the catalog, for example sites.list.
  • Send Content-Type: application/json. An empty body is {}.
  • Input fields that the tool takes in its path (such as id in sites.get) go in the body like every other field.
  • The HTTP status is the tool's own status.

Make that call from a chat with your agent instead, where a person can approve it.

Request:

bash
curl -X POST https://www.astralos.ai/api/v1/tools/sites.list \  -H "Authorization: Bearer $ASTRAL_TOKEN" \  -H "Content-Type: application/json" \  -d '{"kind": "site"}'

Response (200):

json
{  "ok": true,  "data": {    "deployments": [      {        "id": "3c0a8e47-0000-4000-8000-000000000004",        "kind": "site",        "name": "clinic-web",        "slug": "clinic-web",        "status": "live",        "provider": "vercel",        "production_url": "https://clinic-web.astralos.app",        "latest_deployment": { "id": "7d2e...", "status": "ready", "error": null },        "database_id": null,        "source": {          "repo_id": "2d549525-0000-4000-8000-000000000006",          "repo_name": "clinic-web",          "repo_kind": "astral",          "branch": "main",          "root_dir": "",          "html_url": null        }      }    ]  }}

A call that the workspace's rules send to a person fails (403):

json
{  "ok": false,  "error": {    "type": "approval_required",    "message": "This call needs a person to approve it in a chat. /api/v1 can not take that approval.",    "details": { "reason": "chat_approval_required" }  }}

Tokens#

GET/api/v1/tokens#

Lists your tokens in the token's workspace, newest first (at most 200). A token without the * scope sees only itself. The list never shows a secret.

source is device (device login), manual (made with this endpoint or astral token create) or computer.

Request:

bash
curl https://www.astralos.ai/api/v1/tokens \  -H "Authorization: Bearer $ASTRAL_TOKEN"

Response (200):

json
{  "ok": true,  "data": {    "tokens": [      {        "id": "6a1f0c2e-0000-4000-8000-000000000003",        "name": "deploy-script",        "prefix": "astral_3pQx",        "workspace_id": "89e83749-0000-4000-8000-000000000002",        "agent_id": null,        "source": "device",        "scopes": ["code:read", "sites:read"],        "expires_at": "2027-01-24T10:00:00.000Z",        "last_used_at": "2026-10-26T09:12:40.000Z",        "revoked_at": null,        "created_at": "2026-10-26T09:00:00.000Z"      }    ]  }}

POST/api/v1/tokens#

Makes a token. The secret is in the answer one time only.

Body fieldRequiredMeaning
nameyesA name, 1 to 100 characters.
scopesnoA list of scopes. The default is ["*"].
expires_in_daysno1 to 365. The default is 90.
agent_idnoMakes the token act for this agent of the workspace.
  • A token can make a token only in its own workspace, with scopes inside its own, and with an expiry no later than its own.
  • A token for one command on an Astral computer can not make tokens.

Request:

bash
curl -X POST https://www.astralos.ai/api/v1/tokens \  -H "Authorization: Bearer $ASTRAL_TOKEN" \  -H "Content-Type: application/json" \  -d '{"name": "ci-deploys", "scopes": ["sites:write"], "expires_in_days": 30}'

Response (201):

json
{  "ok": true,  "data": {    "token": "astral_Hk2vR9...",    "record": {      "id": "0d9b4f71-0000-4000-8000-000000000005",      "name": "ci-deploys",      "prefix": "astral_Hk2v",      "workspace_id": "89e83749-0000-4000-8000-000000000002",      "agent_id": null,      "source": "manual",      "scopes": ["sites:write"],      "expires_at": "2026-11-25T09:30:00.000Z",      "last_used_at": null,      "revoked_at": null,      "created_at": "2026-10-26T09:30:00.000Z"    }  }}

DELETE/api/v1/tokens/{id}#

Revokes a token at once. A token without the * scope can revoke only itself. Revoking a token that is already revoked returns it again.

Path parameterMeaning
idThe token id from the list.

The answer has the full token record. This example shows three fields.

Request:

bash
curl -X DELETE https://www.astralos.ai/api/v1/tokens/0d9b4f71-0000-4000-8000-000000000005 \  -H "Authorization: Bearer $ASTRAL_TOKEN"

Response (200):

json
{  "ok": true,  "data": {    "token": {      "id": "0d9b4f71-0000-4000-8000-000000000005",      "name": "ci-deploys",      "revoked_at": "2026-10-26T10:00:00.000Z"    }  }}

Scopes#

A scope names an app and an access level. read allows the tools whose HTTP method is GET. write allows every tool of the app, so it includes read.

ScopeAllows
*Every tool of every app. Also lets the token see and revoke your other tokens.
*:readEvery read tool of every app.
sites:write (or sites:*)Every tool of the sites app.
sites:readEvery read tool of the sites app.

A token can have at most 32 scopes. A scope never adds a right that the person or agent of the token does not have.

Errors#

HTTPerror.typeWhen
400invalid_inputThe body or a parameter is not valid.
401auth_requiredNo Authorization header.
401auth_failedThe token is not known, revoked or expired. details.reason is token_revoked or token_expired when it is one of those.
403permission_deniedThe token's scopes or the person's rights do not allow it.
403approval_requiredThe workspace's rules send this call to a person.
404not_foundNo such tool, token or object.
429rate_limitedToo many calls. details can say when to try again.
502upstream_errorThe tool or Astral failed. You can retry.
  • Astral CLI: the same API from a terminal, and as an MCP server for Claude Code, Codex and Cursor.
  • Web Chat API: put your agent on your own website or app (preview).
  • Tools reference: every tool in the catalog.