Docs/Astral 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.
curl https://www.astralos.ai/api/v1/whoami \ -H "Authorization: Bearer $ASTRAL_TOKEN"Base URL and versions#
https://www.astralos.ai/api/v1- Use this exact host. The bare
astralos.aihost redirects, and the redirect drops theAuthorizationheader. - 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:
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:
{ "ok": true, "data": { } }Failure:
{ "ok": false, "error": { "type": "not_found", "message": "No tool sites.lst.", "details": { } }}The error.type values are in Errors.
Endpoints#
| Method | URL | What it does |
|---|---|---|
POST | /api/v1/auth/device/code | Starts a device login. |
POST | /api/v1/auth/device/token | Gets the token after a person approves the login. |
GET | /api/v1/whoami | Shows who the token acts for. |
GET | /api/v1/catalog | Lists the tools that the token can call. |
POST | /api/v1/tools/{app}.{tool} | Calls one tool. |
GET | /api/v1/tokens | Lists your tokens. |
POST | /api/v1/tokens | Makes 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 field | Required | Meaning |
|---|---|---|
client_id | no | A name for your app, shown to the person, for example deploy-script. The default is Astral CLI. |
scope | no | Scopes, separated by spaces, for example sites:read code:read. The default is *. |
Request:
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):
{ "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 field | Required | Meaning |
|---|---|---|
grant_type | yes | Always urn:ietf:params:oauth:grant-type:device_code. |
device_code | yes | The device_code from the first step. |
error (HTTP 400) | Meaning | What to do |
|---|---|---|
authorization_pending | Nobody has answered yet. | Poll again after interval seconds. |
slow_down | You polled too soon. | Add 5 seconds to your interval. |
access_denied | The person denied the login. | Stop. |
expired_token | The 15 minutes ended. | Start again. |
Request:
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=Zp3rVb0kT1m8yQe5Wn2aL7cHx4sJd9uFg6oRi0pKqXcResponse while nobody has answered (400):
{ "error": "authorization_pending", "error_description": "Waiting for a person to approve."}Response after the person approves (200):
{ "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:
curl https://www.astralos.ai/api/v1/whoami \ -H "Authorization: Bearer $ASTRAL_TOKEN"Response (200):
{ "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.
| Header | Meaning |
|---|---|
If-None-Match | The ETag from your last call. The answer is 304 Not Modified when nothing changed. |
| Field | Meaning |
|---|---|
name | The name to call: {app}.{tool}. |
access | read (the tool only reads) or write. |
approval | The catalog flag: never, always or conditional. The workspace's own rules decide if a call needs a person. |
input_schema | The JSON Schema of the tool's input. |
Request:
curl https://www.astralos.ai/api/v1/catalog \ -H "Authorization: Bearer $ASTRAL_TOKEN"Response (200):
{ "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 parameter | Meaning |
|---|---|
{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
idinsites.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:
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):
{ "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):
{ "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:
curl https://www.astralos.ai/api/v1/tokens \ -H "Authorization: Bearer $ASTRAL_TOKEN"Response (200):
{ "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 field | Required | Meaning |
|---|---|---|
name | yes | A name, 1 to 100 characters. |
scopes | no | A list of scopes. The default is ["*"]. |
expires_in_days | no | 1 to 365. The default is 90. |
agent_id | no | Makes 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:
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):
{ "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 parameter | Meaning |
|---|---|
id | The token id from the list. |
The answer has the full token record. This example shows three fields.
Request:
curl -X DELETE https://www.astralos.ai/api/v1/tokens/0d9b4f71-0000-4000-8000-000000000005 \ -H "Authorization: Bearer $ASTRAL_TOKEN"Response (200):
{ "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.
| Scope | Allows |
|---|---|
* | Every tool of every app. Also lets the token see and revoke your other tokens. |
*:read | Every read tool of every app. |
sites:write (or sites:*) | Every tool of the sites app. |
sites:read | Every 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#
| HTTP | error.type | When |
|---|---|---|
| 400 | invalid_input | The body or a parameter is not valid. |
| 401 | auth_required | No Authorization header. |
| 401 | auth_failed | The token is not known, revoked or expired. details.reason is token_revoked or token_expired when it is one of those. |
| 403 | permission_denied | The token's scopes or the person's rights do not allow it. |
| 403 | approval_required | The workspace's rules send this call to a person. |
| 404 | not_found | No such tool, token or object. |
| 429 | rate_limited | Too many calls. details can say when to try again. |
| 502 | upstream_error | The tool or Astral failed. You can retry. |
Related#
- 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.