API reference

One origin, one namespace

Everything lives under https://asteroidbelt.ai/api/v1 with a Bearer token. Management is request/response; conversation is a 202 send plus one standing SSE stream.

The reference below is rendered from the machine-readable spec — the same schemas the routes validate with: /api/v1/openapi.json.

Authentication

Every request sends Authorization: Bearer ast_…. Tokens are minted in Settings → API tokens or by ast auth login — never by other tokens. Scopes are ordered read ⊂ write ⊂ admin; a token is personal (acts as you everywhere) or scoped to one universe. Pass X-Universe: <slug> to act inside a non-personal universe.

Two unauthenticated endpoints back the ast auth login browser handoff (the PKCE grant code is the authentication; the token secret is returned exactly once):

POST/api/v1/cli/token-exchange
{ "code", "verifier" } — exchanges an approved grant for the token secret.
GET/api/v1/cli/poll?request=&verifier=
Headless device-code fallback — pending until the consent page approves, then returns the secret once.

Errors

Every non-2xx body is { "error": { "code", "message" } }:

HTTPcodeWhen
400invalid_requestmalformed JSON / validation failure
401invalid_tokenmissing, unknown, revoked, or expired token
402insufficient_creditsthe spend gate blocked the turn
403insufficient_scope · universe_mismatch · forbiddenscope too low; X-Universe conflicts with a scoped token; agent has no API binding
404not_foundunknown agent/universe (also masks unauthorized slugs)
409conflictduplicate slug
422invalid_specvalid JSON, invalid domain rules (e.g. llm not allowed for type)
429rate_limited120 requests/min per token exceeded — honor Retry-After

Identity

Caller identity, resolved universe, effective scopes

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/whoami \
  -H "Authorization: Bearer ast_..."

Universes

List universes

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/universes \
  -H "Authorization: Bearer ast_..."
POST/api/v1/universeswrite, personal tokens only

Create a universe

Body · application/json

slugstringrequired

max length 40 — pattern ^[a-z0-9][a-z0-9-]*[a-z0-9]$

namestring

max length 80

Response

201Created
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X POST https://asteroidbelt.ai/api/v1/universes \
  -H "Authorization: Bearer ast_..." \
  -H "Content-Type: application/json" \
  -d '{"slug":"acme","name":"Acme"}'

Universe detail

Path parameters

slugstringrequired

The universe slug

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/universes/my-agent \
  -H "Authorization: Bearer ast_..."

Agents

List agents in the resolved universe

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/agents \
  -H "Authorization: Bearer ast_..."

Create an agent from a declarative spec

Every field optional — an empty body creates the instant default.

Body · application/json

namestring

max length 60

descriptionstring

max length 500

agentTypeenum<string>

"hermes" · "claude_code" · "codex" · "openclaw"

llmstring
promptstring

max length 100000

displayNamestring

max length 60

capabilitiesenum<string>[]

"exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "dataforseo.serp" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "fish-audio.transcribe" — default: []

instructionsobject[]
▸ Show child attributes
namestringrequired

pattern .*\.md$

contentstring

default: ""

skillsobject[]
▸ Show child attributes
namestringrequired

pattern ^[A-Za-z0-9_-]+$

urlstringrequired
sourceenum<string>required

"registry" · "git" · "package" · "archive"

versionstringrequired
configobject

default: {}

secretRefsstring[]

default: []

customInstallsobject[]
▸ Show child attributes
commandstringrequired
descriptionstring
timeoutSecondsinteger

default: 300

allowNetworkboolean

default: true

customDataobject[]
▸ Show child attributes
idstringrequired
namestringrequired

pattern ^[a-z0-9_]+$

kindenum<string>required

"spreadsheet" · "doc" · "private_api" · "other"

scopeenum<string>

"creator" · "user" — default: "creator"

descriptionstring
uristring
configobject

default: {}

vcpusinteger
memoryMbinteger

Response

201Created
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X POST https://asteroidbelt.ai/api/v1/agents \
  -H "Authorization: Bearer ast_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"research-agent","agentType":"hermes","llm":"deepseek-v4-pro","capabilities":["exa.search"]}'

Agent detail

Path parameters

slugstringrequired

The agent slug

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/agents/my-agent \
  -H "Authorization: Bearer ast_..."

Update displayName / capabilities

Path parameters

slugstringrequired

The agent slug

Body · application/json

displayNamestring

max length 60

capabilitiesenum<string>[]

"exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "dataforseo.serp" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "fish-audio.transcribe" — default: []

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X PATCH https://asteroidbelt.ai/api/v1/agents/my-agent \
  -H "Authorization: Bearer ast_..." \
  -H "Content-Type: application/json" \
  -d '{"displayName":"Research Agent"}'

Destroy the pod and delete the agent

Path parameters

slugstringrequired

The agent slug

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X DELETE https://asteroidbelt.ai/api/v1/agents/my-agent \
  -H "Authorization: Bearer ast_..."

Save + hot-reload the user prompt layer

Path parameters

slugstringrequired

The agent slug

Body · application/json

promptstringrequired

max length 100000

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X PUT https://asteroidbelt.ai/api/v1/agents/my-agent/prompt \
  -H "Authorization: Bearer ast_..." \
  -H "Content-Type: application/json" \
  -d '{"prompt":"You are a meticulous research agent…"}'

Prompt version history

Path parameters

slugstringrequired

The agent slug

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/agents/my-agent/prompt/versions \
  -H "Authorization: Bearer ast_..."

Declared secret keys + set/unset — never values

Path parameters

slugstringrequired

The agent slug

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/agents/my-agent/secrets \
  -H "Authorization: Bearer ast_..."

Set a declared secret's value (write-only)

Path parameters

slugstringrequired

The agent slug

keystringrequired

Body · application/json

valuestringrequired

max length 10000

descriptionstring

max length 500

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X PUT https://asteroidbelt.ai/api/v1/agents/my-agent/secrets/NOTION_TOKEN \
  -H "Authorization: Bearer ast_..." \
  -H "Content-Type: application/json" \
  -d '{"value":"secret_…"}'

Unset a secret

Path parameters

slugstringrequired

The agent slug

keystringrequired

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X DELETE https://asteroidbelt.ai/api/v1/agents/my-agent/secrets/NOTION_TOKEN \
  -H "Authorization: Bearer ast_..."

Conversation

Chat history (ascending row id) — the stream's catch-up surface

Path parameters

slugstringrequired

The agent slug

Query parameters

sinceinteger

Return rows with id greater than this

limitinteger

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/agents/my-agent/messages?since=&limit= \
  -H "Authorization: Bearer ast_..."

Send a turn — 202 { turnId }; output arrives on the stream

The conversation plane (edge-routed to the chat-proxy). Optional Idempotency-Key header dedupes retries.

Path parameters

slugstringrequired

The agent slug

Body · application/json

partsobject[]required
▸ Show child attributes
type: "text"object
▸ Show child attributes
indexintegerrequired
type"text"required
textstring

default: ""

type: "reasoning"object
▸ Show child attributes
indexintegerrequired
type"reasoning"required
textstring

default: ""

redactedboolean

default: false

type: "tool_call"object
▸ Show child attributes
indexintegerrequired
type"tool_call"required
call_idstringrequired
tool_namestringrequired
argsobject

default: {}

args_partialobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
type: "tool_result"object
▸ Show child attributes
indexintegerrequired
type"tool_result"required
call_idstringrequired
tool_namestringrequired
statusenum<string>

"ok" · "error" — default: "ok"

resultobject

default: {}

type: "file"object
▸ Show child attributes
indexintegerrequired
type"file"required
namestringrequired
media_typestringrequired
uriobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
dataobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
size_bytesobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
type: "image"object
▸ Show child attributes
indexintegerrequired
type"image"required
media_typestringrequired
uriobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
dataobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
altobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
widthobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
heightobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
type: "video"object
▸ Show child attributes
indexintegerrequired
type"video"required
media_typestringrequired
uriobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
dataobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
altobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
widthobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
heightobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
duration_msobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
size_bytesobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
thumbnail_uriobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
type: "status"object
▸ Show child attributes
indexintegerrequired
type"status"required
levelenum<string>required

"thinking" · "working" · "waiting" · "idle" · "done"

textobject
▸ Show child attributes
option 1object
▸ Show child attributes
option 2object
▸ Show child attributes
type: "error"object
▸ Show child attributes
indexintegerrequired
type"error"required
codestringrequired
messagestringrequired
retryableboolean

default: false

Response

202Accepted — { turnId }
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X POST https://asteroidbelt.ai/api/v1/agents/my-agent/messages \
  -H "Authorization: Bearer ast_..." \
  -H "Content-Type: application/json" \
  -d '{"parts":[{"type":"text","index":0,"text":"Summarize today's signups"}]}'
Response · 202 Accepted
{
  "turnId": "turn_8c41f0…"
}

The standing event stream (SSE)

text/event-stream, live-only, stays open across turns. Events: user.message, turn.accepted, agent.message, agent.reasoning, agent.tool_use, agent.tool_result, agent.status, turn.status_idle, turn.interrupted, error. Reconnect = reopen + list history + dedup by event id.

Path parameters

slugstringrequired

The agent slug

Response

200SSE stream
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -N https://asteroidbelt.ai/api/v1/agents/my-agent/stream \
  -H "Authorization: Bearer ast_..."

Abort the in-flight turn

Path parameters

slugstringrequired

The agent slug

Response

200{ turnId, status: "interrupting" } or { turnId: null, status: "no_turn" }
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl -X POST https://asteroidbelt.ai/api/v1/agents/my-agent/interrupt \
  -H "Authorization: Bearer ast_..."
Response · 200 OK
{
  "turnId": "turn_8c41f0…",
  "status": "interrupting"
}

Logs

Pod logs; follow=true streams as SSE

Path parameters

slugstringrequired

The agent slug

Query parameters

sincestring

e.g. "30m", "2h", "1d"

followboolean

Response

200OK
4xx{ "error": { "code", "message" } } — see the error table above.
Request
curl https://asteroidbelt.ai/api/v1/agents/my-agent/logs?since=&follow= \
  -H "Authorization: Bearer ast_..."

Stream events

Send and listen are decoupled: POST …/messages, then read …/stream until turn.status_idle — there is no non-streaming reply mode. Frames are id: + event: + one data: JSON envelope { type, turnId?, agent, at, …payload }:

eventpayloadmeaning
user.messageparts, text, channelinput echo — a turn arrived, from this API or any channel
turn.acceptedturn validated, queued to the agent
agent.messageparts, texta user-facing agent post; a turn may carry several
agent.reasoningpartraw engine event: thinking
agent.tool_usepartraw engine event: a tool invocation
agent.tool_resultpartraw engine event: the tool's outcome
agent.statuspartraw engine event: working / waiting / …
turn.status_idleturn complete; the stream stays open
turn.interruptedbyan interrupt took effect
errorcode, message, retryablethe turn failed; the stream stays open
One turn on the standing stream
event: user.message
data: {"type":"user.message","turnId":"turn_8c41f0…","agent":"nemesis-ee87","at":"…","channel":"api","text":"Summarize today's signups.","parts":[]}

event: turn.accepted
data: {"type":"turn.accepted","turnId":"turn_8c41f0…","agent":"nemesis-ee87","at":"…"}

event: agent.message
data: {"type":"agent.message","turnId":"turn_8c41f0…","agent":"nemesis-ee87","at":"…","text":"Signups today: 42, up 12%.","parts":[]}

event: turn.status_idle
data: {"type":"turn.status_idle","turnId":"turn_8c41f0…","agent":"nemesis-ee87","at":"…"}