# Choose your path AgentSky has six distinct integration surfaces. Each solves a different problem — pick the one that matches what you want to build, and you’ll spend zero time on the others. | I want to… | Use | Start here | Not this | |---|---|---|---| | Use an agent from my terminal or shell scripts | **CLI / terminal** | [Quickstart](https://agentsky.dev/docs) | Agent API (overkill for local use) | | Embed agents in my product — sessions, streaming, history | **Agent API** | [Agent API guide](https://agentsky.dev/docs/agent-api) | Gateway (no sessions) | | Use one AgentSky key instead of separate Anthropic/OpenAI keys | **API Gateway** | [Gateway guide](https://agentsky.dev/docs/gateway) | Agent API (not for plain LLM calls) | | Use agents as tools inside Claude Desktop, Cursor, or any MCP client | **MCP server** | [MCP guide](https://agentsky.dev/docs/mcp) | Agent API (MCP client manages the session) | | Send agents messages from Slack, Telegram, WhatsApp, Discord | **Channels** | [Channels guide](https://agentsky.dev/docs/channels) | Agent API (channels handle the webhook routing) | | Trigger agents on a schedule | **Routines** | [Routines guide](https://agentsky.dev/docs/routines) | Agent API (routines handle the cron loop) | ## The two APIs that look similar but aren’t The most common source of confusion: **Agent API vs. API Gateway**. They share a key format and base URL but solve different problems. **API Gateway** — you have existing code that calls Anthropic or OpenAI directly and you want a single billing account, model routing, or failover. Swap your base URL. No agents, no sessions, no state. One call, one response. **Agent API** — you want a conversation that persists, an agent with its own instructions and tool access, and a stream your UI can subscribe to. This is the integration surface for product features. It requires creating an agent and a session. If you’re not sure which to use: if your code calls `POST /v1/messages` (Anthropic) or `POST /v1/chat/completions` (OpenAI) today, that’s **Gateway**. If you want users to have a conversation history and you need to stream responses into a UI, that’s **Agent API**. ## What all six share - One base URL: `https://api.agentsky.dev/v1` - One key format: `ast_…` (minted in Developer → API keys or `sky auth login`) - Scopes: `read ⊂ write ⊂ admin` — most integrations need `write` - Workspaces: pass `X-Universe: ` to act in an isolated universe See [Authentication](https://agentsky.dev/docs/authentication) for the full token reference and [Concepts](https://agentsky.dev/docs/concepts) for agents, sessions, events, and billing explained. --- # Agents, from your terminal AgentSky agents are persistent — they keep state, memory, and history for life. The `sky` CLI launches one in two commands; the API is a 202 send plus one standing event stream. New to AgentSky? [See what you can build with the API and its included browser UI](https://agentsky.dev/docs/overview.md), then launch your first agent below. ## 1. Install and log in ```bash npm i -g @agentsky/sky sky auth login # opens the browser once; CI: set SKY_API_TOKEN ``` Login mints an API token scoped to you (revocable anytime under Developer → API keys). Headless machines use `sky auth login --no-browser`. ## 2. Launch an agent ```bash cd my-project sky launch # scaffolds ./agent.toml, creates the agent, prints its address ``` [agent.toml](https://agentsky.dev/docs/agent-toml.md) is optional — `sky launch` with no file gives you the one-click default (pi). An agent is a reusable spec; `launch` also creates its first **session** (the pod-backed runtime you talk to), which boots on your first message (`--start` boots it now). Edit the file and re-apply with `sky deploy`. ## 3. Talk to it ```bash sky agent message agent_cmtb2f0a8m9k7x51q4ez6r0d "Summarize the signups from today" # default session; streams until idle sky agent chat agent_cmtb2f0a8m9k7x51q4ez6r0d # interactive REPL ``` Or over HTTP — one origin, one namespace (`https://api.agentsky.dev/v1`). Chat is session-scoped; find the session id with `sky session list agent_…` (or `defaultSessionId` on `GET /v1/agents/agent_…`): ```bash # send (echoes the event; output never rides this response) curl -X POST https://api.agentsky.dev/v1/sessions/SESSION_ID/events \ -H "Authorization: Bearer ast_..." -H "Content-Type: application/json" \ -d '{"events":[{"type":"user.message","content":[{"type":"text","text":"Summarize the signups from today"}]}]}' # listen (standing SSE — every turn, every channel, agent-initiated posts) curl -N https://api.agentsky.dev/v1/sessions/SESSION_ID/stream \ -H "Authorization: Bearer ast_..." ``` One Q&A = POST the message, read the stream until `session.status_idle` (check its `stop_reason`). Missed events replay from `GET /v1/sessions/SESSION_ID/events` — the list returns the same event objects as the stream; dedupe by event `id`. ## Go deeper - [API reference](https://agentsky.dev/docs/api.md): every /v1 endpoint, the error contract, and the full stream event catalog. - [CLI reference](https://agentsky.dev/docs/cli.md): every sky command: auth, launch/deploy, agents and sessions, billing, keys, memory, routines, webhooks, and workspaces. - [agent.toml](https://agentsky.dev/docs/agent-toml.md): the declarative agent file — everything the create wizard can set. - [Skills](https://agentsky.dev/docs/skills.md): reusable instruction + script bundles — upload once, attach by reference, pin revisions, or load from a GitHub repo. - [Routines](https://agentsky.dev/docs/routines.md): run agents on a schedule — cron-fired sessions or turns into a long-lived session, per-run budgets, runs ledger, webhooks. --- # Authentication Every request to the AgentSky API must include a bearer token. Tokens are scoped — the scope you need depends on what the call does. ## Getting a token **From the browser:** Developer → API keys → New key. Copy the token immediately; it is shown only once. **From the CLI:** ```bash sky auth login # opens a browser tab; pastes the token automatically sky auth login --no-browser # prints a URL; paste the code manually (CI) ``` Successful login stores a credential in `~/.config/sky/credentials.json` and sets the `SKY_API_TOKEN` environment variable for the current shell. Use `sky auth status` to inspect the current session. **In CI / server environments:** Set the `SKY_API_TOKEN` environment variable or pass the token in every request. ## Using a token Pass the token as a bearer in the `Authorization` header: ```bash curl https://api.agentsky.dev/v1/agents \ -H "Authorization: Bearer ast_..." ``` The AgentSky SDK picks up `SKY_API_TOKEN` automatically if the header is not set explicitly. ## Scopes Tokens have one of three scopes. Higher scopes include all permissions of lower scopes. | Scope | Grants | |---|---| | `read` | GET any resource you own | | `write` | read + create and update agents, sessions, routines, webhooks, policies, files | | `admin` | write + manage workspace members, billing, and universe-level settings | `read ⊂ write ⊂ admin`. Most integration scenarios need only `write`. Use `read` for monitoring dashboards or tooling that only fetches data. Never generate an `admin` token for a service that doesn't need billing access. ## Personal vs. scoped tokens **Personal tokens** act on all universes (workspaces) your account belongs to. This is the default. **Universe-scoped tokens** are tied to a single universe and cannot act outside it — safer for multi-tenant setups where each customer has its own universe. Create a scoped token by selecting a universe when you create the key in Developer → API keys. ## The X-Universe header When you hold a personal token and want to act inside a specific universe, pass the universe's slug in `X-Universe`: ```bash curl -X POST https://api.agentsky.dev/v1/sessions \ -H "Authorization: Bearer ast_..." \ -H "X-Universe: acme" \ -H "Content-Type: application/json" \ -d '{ "agent": "agent_cmtb2f0a8m9k7x51q4ez6r0d" }' ``` Every resource created in that call belongs to the `acme` universe. Without the header, the call acts on your personal workspace. Universe-scoped tokens imply a universe; you do not need to repeat the header on every call. ## Revoking a token Browser: Developer → API keys → revoke. CLI: `sky auth logout` (revokes the stored token and clears the local credential). ## See also - [Concepts](https://agentsky.dev/docs/concepts.md) — agents, sessions, universes - [Agent API guide](https://agentsky.dev/docs/agent-api.md) — end-to-end integration with auth examples - [API reference](https://agentsky.dev/docs/api.md) — full error catalog, including 401/403 shapes --- # Concepts AgentSky is built around four primitives: **agents**, **sessions**, **events**, and **universes**. Understanding these makes every API response predictable. ## Agents An **agent** is a reusable specification — not a running process. It stores: - The **harness** (Claude Code, Codex, Hermes, or a custom harness): determines what model orchestration the agent can do. - **Instructions**: the system prompt and personality. - **Capabilities**: which built-in tools the agent may call (web search, code execution, browser automation, …). - **Skills**: versioned instruction bundles loaded on demand. - **agent.toml**: the declarative file that configures all of the above and is committed to your repo. Creating an agent does not start it running. You interact with it through sessions. ``` Agent ├── id: agent_… ├── harness: hermes ├── displayName: Research Agent ├── defaultSessionId: sess_… ← created automatically on first launch └── capabilities: [exa.search] ``` ## Sessions A **session** is one running conversation with an agent. It holds: - The **event log**: every message, response, tool call, and status change, in order. - The **context window**: the live slice of the event log the agent sees on its next turn. - The **status**: `idle`, `running`, `error`, `terminated`. A session is the unit you stream, continue, and replay. Every agent gets a `defaultSessionId` at launch — good for one-off use. For multi-user applications, create a session per user conversation. Sessions live forever (until you delete them). An idle session can be continued days later; it picks up context from the event log. ``` Session ├── id: sess_… ├── agent: agent_… ├── status: idle ├── llm: claude-sonnet-5 └── events: […] ``` ## Events Events are the messages-in and messages-out of a session. The session's event log is append-only. **Events you send:** | Type | When to use | |---|---| | `user.message` | A new message from a user or your application | | `user.interrupt` | Stop the agent mid-turn cleanly | | `system.message` | Inject a system-level message (hidden from user but visible to agent) | **Events the agent produces:** | Type | What it carries | |---|---| | `agent.message` | A completed text response | | `agent.message.delta` | A streaming chunk of a text response | | `agent.tool_call` | An outbound tool invocation | | `agent.tool_result` | The result of a tool call | | `session.status_idle` | The agent finished its turn (includes `stop_reason`) | | `session.error` | An unrecoverable error (includes code and message) | The **stream** (`GET /v1/sessions/{id}/stream`) delivers events as Server-Sent Events in real time. The **event log** (`GET /v1/sessions/{id}/events`) returns the same objects in order after the fact. Both use the same `id` field — deduplicate by event id when replaying a partial stream. ### The session loop Every turn follows the same shape: ``` POST /sessions/{id}/events → 202 (queued) GET /sessions/{id}/stream → SSE: agent.message.delta … session.status_idle POST /sessions/{id}/events → next turn ``` The stream is standing — open it once and it delivers every future turn. `session.status_idle` marks the end of a turn; `stop_reason` says why the agent stopped (`end_turn`, `tool_use`, `budget_reached`, `interrupted`, `error`). ## Universes (workspaces) A **universe** is an isolated workspace — its own agents, sessions, members, billing, and API keys. Use one per customer tenant, per deployment environment, or per team. ```bash # Create curl -X POST https://api.agentsky.dev/v1/universes \ -H "Authorization: Bearer ast_..." \ -d '{ "slug": "acme", "name": "Acme" }' # Act inside it curl ... -H "X-Universe: acme" ``` Resources (agents, sessions, files) belong to exactly one universe. Members of that universe can see its resources. See [Authentication](https://agentsky.dev/docs/authentication.md) for how personal vs. scoped tokens interact with universes. ## Billing AgentSky bills at three levels: **Token usage:** every LLM call records input and output tokens. The model rate card is at `GET /v1/generation?id=…` and in the billing dashboard. **Capabilities:** built-in tool calls (web search, browser automation) are charged per invocation at rates listed in the [Capabilities reference](https://agentsky.dev/docs/capabilities.md). **Sessions:** session compute (hosting the pod between turns) is charged per active session-hour. A **spend policy** lets you cap a session's total spend before it was created or at any time: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_.../policies \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Spend cap", "kind": "SPEND_CAP", "config": { "hardCapUsdMicros": 5000000, "warnUsdMicros": 4000000 } }' ``` When the hard cap is hit, the session stops with `stop_reason.type: "budget_reached"`. Cumulative spend is on `GET /v1/sessions/{id}`. ## See also - [Choose your path](https://agentsky.dev/docs/overview.md) — which integration to use for your situation - [Authentication](https://agentsky.dev/docs/authentication.md) — tokens, scopes, X-Universe - [Agent API guide](https://agentsky.dev/docs/agent-api.md) — end-to-end usage with code samples - [API reference](https://agentsky.dev/docs/api.md) — full schema for every resource --- # Agent API > **Use this when:** you want to embed long-lived agents inside your own product — create agents, start sessions, send messages, and stream responses back to your users. > > **Don't use this when:** you only want to call an LLM with one key (use the [API Gateway](https://agentsky.dev/docs/gateway.md) instead — no sessions, no agents, same key). Or if you want to use agents from an MCP client (see [MCP server](https://agentsky.dev/docs/mcp.md)). Everything lives under `https://api.agentsky.dev/v1`. Authenticate with `Authorization: Bearer ast_…` (an API key from **Developer → API keys** or `sky auth login`). Management is request/response; conversation is a 202-send plus one standing SSE stream. ## The model in 30 seconds ``` Agent ← reusable spec (harness, instructions, capabilities) └─ Session ← one conversation thread; holds context and history └─ Events ← messages in, responses out, as a stream ``` An **agent** is what you configure once. A **session** is what runs. Your application sends `user.message` events and reads the stream until `session.status_idle` — the same loop every time. ## Basic: end to end in three API calls ### 1. Create an agent ```bash curl -X POST https://api.agentsky.dev/v1/agents \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "displayName": "Research Agent", "agentType": "hermes", "llm": "claude-sonnet-5" }' # → { "id": "agent_…", "defaultSessionId": "sess_…", … } ``` ### 2. Start a session and send the first message ```bash curl -X POST https://api.agentsky.dev/v1/sessions \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "agent": "agent_…", "initial_events": [ { "type": "user.message", "content": [{ "type": "text", "text": "Summarize the signups from today" }] } ] }' # → { "id": "sess_…", "status": "running", … } ``` ### 3. Stream the response ```bash curl -N https://api.agentsky.dev/v1/sessions/sess_.../stream \ -H "Authorization: Bearer ast_..." # SSE frames until session.status_idle # id: msg-77e0c4… # event: agent.message # data: {"id":"msg-…","type":"agent.message","text":"Signups today: 42…"} # … # event: session.status_idle # data: {"stop_reason":{"type":"end_turn"}} ``` Read the stream until you see `session.status_idle`. The stream stays open — subsequent turns post another event and wait again. ### Continue the conversation ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_.../events \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "requireIdle": true, "events": [ { "type": "user.message", "content": [{ "type": "text", "text": "Which cohort drove the increase?" }] } ] }' ``` `requireIdle: true` rejects the POST if the session is already running — use it to prevent double-sends. ## Advanced topics ### Idempotency Wrap every mutating call in an `Idempotency-Key` header (a UUID you generate once per logical operation). Replaying the same key returns the first response without side effects. Network timeouts are safe to retry with the same key. ```bash curl -X POST https://api.agentsky.dev/v1/sessions \ -H "Authorization: Bearer ast_..." \ -H "Idempotency-Key: 40a9ae2a-4ea5-4a50-987e-d8b3c99c96f0" \ ... ``` ### Budgets and spending limits Cap what a session may spend with a spend policy: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_.../policies \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Spend cap", "kind": "SPEND_CAP", "config": { "hardCapUsdMicros": 5000000, "warnUsdMicros": 4000000 } }' ``` The session stops with `stop_reason.type: "budget_reached"` when the hard cap is reached. `GET /v1/sessions/{id}` includes cumulative spend. ### Interrupts Stop a running session gracefully: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_.../events \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "events": [{ "type": "user.interrupt" }] }' ``` Send `"mode": "save"` to ask the agent to save what it has and end the turn cleanly. ### Reconnect and replay Missed stream events are kept in the event log forever. Replay from any event ID: ```bash # All events in order (same objects as the live stream; dedupe by event id) curl "https://api.agentsky.dev/v1/sessions/sess_.../events" \ -H "Authorization: Bearer ast_..." # Resume stream from a known event id curl -N "https://api.agentsky.dev/v1/sessions/sess_.../stream?after=msg-77e0c4…" \ -H "Authorization: Bearer ast_..." ``` ### Files and workspace Upload files and mount them into a session: ```bash # Upload curl -X POST https://api.agentsky.dev/v1/files \ -H "Authorization: Bearer ast_..." \ -F "file=@report.pdf" # → { "id": "file_…", … } # Mount at session create or POST /sessions/{id}/resources -d '{ "agent": "agent_…", "resources": [ { "type": "file", "file_id": "file_…", "mount_path": "/data/report.pdf" } ] }' ``` GitHub repositories mount the same way (`"type": "github_repository"`); memory stores mount as `"type": "memory_store"`. ### Vaults and secrets Store per-agent secrets (OAuth tokens, webhook signing keys) in the agent's vault: ```bash curl -X PUT https://api.agentsky.dev/v1/agents/agent_.../secrets/MY_TOKEN \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "value": "secret_…" }' ``` The agent sees the value as an environment variable; it never appears in API responses. ### Webhooks Subscribe to session events server-to-server: ```bash curl -X POST https://api.agentsky.dev/v1/webhooks \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/agentsky", "events": ["session.status_idle", "session.error"] }' ``` The `routine_run.failed` and `routine.paused` events are also available. Wire hook rules to call a URL or require human approval on specific tool uses (`POST /hook_rules`). ### Environments (universes) Create an isolated workspace for a customer, team, or environment: ```bash curl -X POST https://api.agentsky.dev/v1/universes \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "slug": "acme", "name": "Acme" }' # All subsequent calls scoped to that workspace: curl ... -H "X-Universe: acme" ``` Invite members (`POST /universes/{slug}/invites`), set policies, and pin agent versions (`PUT /universes/{slug}/agents/{handle}`). ### Skills and agent.toml Attach reusable instruction bundles to any agent with `sky launch` and an [agent.toml](https://agentsky.dev/docs/agent-toml.md) file, or via `PUT /agents/{id}/prompt`. [Skills](https://agentsky.dev/docs/skills.md) are versioned bundles you upload once and attach by reference. ### Agent identity fields (D-391) Three optional fields let you give an agent a persistent identity within a workspace. **`handle`** — workspace-scoped @-handle (lower-case, 2–40 chars: `[a-z0-9_]`). Unique within the workspace. Setting it gives the agent a stable email address and lets you start sessions with `"agent": "@handle"` instead of the agent id. ```bash # Set a handle at create time curl -X POST https://api.agentsky.dev/v1/agents \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "displayName": "Support Bot", "handle": "support_bot" }' # → { "id": "agent_…", "handle": "support_bot", # "email": "support_bot@agents.agentsky.dev", … } # Or set/update it via PATCH curl -X PATCH https://api.agentsky.dev/v1/agents/agent_... \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "handle": "support_bot" }' # Start a session by handle curl -X POST https://api.agentsky.dev/v1/sessions \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{ "agent": "@support_bot", "initial_events": [] }' ``` Pass `"handle": null` in PATCH to clear the handle. **`email`** — read-only. Present in agent responses when a handle is set; value is `{handle}@agents.agentsky.dev`. The address is reserved, not live: `agents.agentsky.dev` has no mail receiving yet, so mail sent to it bounces, and agents have no tool to send from it. **`channel_settings`** — Slack/Lark persona settings. Controls whether the agent is invitable to channels and carries an optional description. ```json { "invitable": true, "description": "Handles tier-1 support tickets" } ``` Pass `"channel_settings": null` to reset to defaults. **`runtime`** — BYO-code runtime config. Two shapes: - `{ "kind": "image", "image": "ghcr.io/your-org/agent@sha256:<64 hex>" }` — run your own container image. The reference must be digest-pinned and readable anonymously from its registry (checked on create/PATCH, `422 invalid_runtime` otherwise). The agent becomes a `workspace_acp` agent on a hosted harness built from the image; sessions answer `409 harness_not_ready` until the image's template is built. The image must run the hosted-brain contract. - `{ "kind": "endpoint", "protocol": "a2a", "url": "https://…" }` — AgentSky sends each user turn to your A2A agent as `message/send` (no sandbox, nothing metered) and shows its reply as the agent's turn. Set the agent secret `A2A_AUTH_TOKEN` to have it sent as `Authorization: Bearer …`. `acp` and `mcp` are refused with `422 runtime_protocol_not_supported`. > **Note:** BYO-code runtimes are enabled per workspace. Where they are not, session create answers `422 runtime_not_available`. Pass `"runtime": null` to clear (agent runs in the standard sandbox). ## Reference - [Full API reference](https://agentsky.dev/docs/api.md) — every /v1 endpoint, schemas, the error contract, and the complete stream event catalog - [Authentication](https://agentsky.dev/docs/authentication.md) — tokens, scopes, X-Universe header - [Concepts](https://agentsky.dev/docs/concepts.md) — agents, sessions, events, and billing explained - [CLI](https://agentsky.dev/docs/cli.md) — `sky launch`, `sky agent`, `sky session` and every other command - [agent.toml](https://agentsky.dev/docs/agent-toml.md) — declarative agent configuration --- # API Gateway > **Use this when:** you want one key and one billing account in front of Anthropic, OpenAI, and the other providers AgentSky supports — no sessions, no agents, no state. > > **Don't use this when:** you need a persistent conversation with history and streaming (use the [Agent API](https://agentsky.dev/docs/agent-api.md) instead). Or if you want to use your agents from an MCP client (see [MCP server](https://agentsky.dev/docs/mcp.md)). https://gateway.agentsky.dev serves the Anthropic Messages API, the OpenAI Chat Completions, Responses and Embeddings APIs, and the decisions API behind one AgentSky key, plus APIs for search, maps, email, speech, transcription, SEO data and image generation (the run API). Every completed model call is billed; the charge appears inline on most responses and is always retrievable via `GET /v1/generation`. ## Base URLs | Client | Base URL | |---|---| | Anthropic SDK, Claude Code (`ANTHROPIC_BASE_URL`) | `https://gateway.agentsky.dev` | | OpenAI SDK, Codex (`OPENAI_BASE_URL`) | `https://gateway.agentsky.dev/v1` | | Decisions API | `https://gateway.agentsky.dev` | Authenticate with an AgentSky API key (`ast_…`, created under Developer → API keys) as `x-api-key` or `Authorization: Bearer`. Model calls and runs need the `write` scope; catalog and account reads need `read`. ## Routes | Method | Path | Notes | |---|---|---| | POST | /v1/messages | Anthropic Messages API, streamed or not | | POST | /v1/messages/count_tokens | unmetered; requires a model route that supports token counting | | POST | /v1/chat/completions, /v1/responses, /v1/embeddings | OpenAI APIs | | POST | /api/alpha/decisions | decision models: `{model, state, questions}` → an answer per question | | GET | /v1/models, /v1/models/{vendor}/{model} | public catalog with per-token prices, context, and modalities | | GET | /v1/endpoints, /v1/endpoints/{provider}/{endpoint} | public run catalog, and one API with its input fields and limits | | GET | /v1/providers | API provider list | | POST | /v1/run | `{provider, endpoint, input}` → run object | | GET | /v1/runs/by-idempotency | read a saved run using its original `Idempotency-Key` and API token | | GET | /v1/runs/{runId} | retrieve a saved run owned by your account | | GET | /v1/credits | balance and total used | | GET | /v1/generation?id= | one call's record: tokens and cost | ## Model ids Use the catalog id (`anthropic/claude-sonnet-5`, `qwen/qwen-max`), an accepted alias (`glm/glm-5.2`, `kimi/k3`), or the bare id (`claude-sonnet-5`). A vendor prefix that matches nothing is a 404. ## Anthropic compatibility Messages supports JSON and streaming responses. Token counting is available for active Anthropic resources whose IDs end in `-anthropic-api`; it requires `read` scope and does not charge the wallet. Other resource routes may have different endpoint support. Send `anthropic-version` on `GET /v1/models` to receive the Anthropic SDK list format (`data`, `has_more`, `first_id`, `last_id`), with `limit`, `before_id`, and `after_id` pagination. Retrieve a model with `GET /v1/models/{id}` and the same `anthropic-version` header; URL-encode the full model ID when it contains a slash. Without this header, the endpoint returns the AgentSky catalog with prices. Unknown model capability metadata is returned as null. On Anthropic responses, `request-id` preserves the upstream request identifier when one is supplied and valid. Gateway-generated errors and Models responses use a gateway identifier instead. `x-agentsky-request-id` remains the separate AgentSky support reference; include both identifiers when reporting a problem. A request ID or thinking signature alone does not establish a direct connection to Anthropic. For Messages requests to `anthropic/claude-fable-5-1-anthropic-api`, use `thinking: {"type": "adaptive"}` and `output_config.effort` to control reasoning. Explicit `thinking.type` values of `disabled` or `enabled` are not supported on this resource. Message Batches (`/v1/messages/batches`) is not currently supported. Batch pricing and any batch discount are not offered by this gateway at this time. ## Decision models A decision model answers typed questions about supplied state instead of generating text, so it is served on `/api/alpha/decisions` rather than the chat paths — the catalog marks it `"type": "decision"` with `"wires": ["decisions"]`. Send `{model, state, questions}`, where each named question is `{type: "noul" | "choice" | "score", instructions, criteria?}`; the reply carries an answer with a probability per question. A chat model on this path is a 404, as is a decision model on the chat paths. The wire does not stream: `stream: true` is a 400. Errors use `{"error":{"code","message"}}`. ## Failover A model may be served from more than one place. The gateway tries them in its own order and fails over to the next when one is unreachable, times out before the first byte, or answers 408, 429, 401, 403 or 5xx; any other reply is returned as-is, and nothing is retried once response bytes have been sent. Which upstream serves a call is platform configuration, not a request option: OpenRouter's `provider` body field and Vercel's `providerOptions.gateway` are accepted and dropped before forwarding. ## Cost in the response Model calls: `usage.cost` (USD) on the final usage object — the usage-only last chunk of an OpenAI Chat Completions stream, `response.completed` on the Responses API, `message_delta` on the Anthropic wire, or the JSON body. Responses API streams ending in `response.incomplete` or `response.failed` are billed when they carry usage, but those terminal events are relayed without a `usage.cost` field; use `GET /v1/generation?id=` to retrieve the charge. Runs: `price.amount.value` is the customer charge in `price.amount.currency` (USD). The public `billing` cost fields have null values; they do not expose supplier costs. A `RUNNING` run may report zero before settlement; this is not a final price or a free generation. Prices are per token or per unit as listed in the catalog; `GET /v1/generation?id=` returns the record once it has landed. ## Runs ``` POST /v1/run { "provider": "exa", "endpoint": "search", "input": { "query": "agent gateways", "numResults": 5 } } 200 { "runId": "run_…", "status": "COMPLETED", "provider": "exa", "endpoint": "search", "output": { … vendor response … }, "price": { "type": "PER_CALL", "amount": { "value": 0.014, "currency": "USD" } }, "providerResponse": { "httpStatus": 200 }, … } ``` A request can return a completed run, or a saved run with `status: RUNNING`. Image and video generation can outlast the HTTP connection. A network timeout, HTML error page, or other non-JSON response does not tell you whether generation succeeded. Read `GET /v1/runs/{runId}` until the status is `COMPLETED`, `FAILED`, or `TIMED_OUT`; use a key with both `read` and `write` scopes. Send an `Idempotency-Key` on the first POST. Save it, the exact request body, and any returned `runId` before retrying. If you lost the run ID, send `GET /v1/runs/by-idempotency` with the **same API token** and original `Idempotency-Key`; it requires `read` scope and never starts another supplier request. A 404 means no saved run is visible yet, so keep the original key and retry the read; a missing or malformed key is 400. The response is private and not cached. You can also repeat the POST with the **same API key, idempotency key, and body** to retrieve the existing run; that path may create a run if the first POST never arrived. Reusing the key with a different body returns `409 idempotency_conflict`. Use a new key only for an intentionally new generation. Do not rotate keys or automatically submit a fresh paid request after a timeout. ### Node.js example: submit, recover, and poll Save this as `run.mjs` and run it with Node.js 22 or later. Set `AGENTSKY_API_KEY` and `AGENTSKY_IDEMPOTENCY_KEY` in your environment. Choose a unique idempotency key for this generation and keep it unchanged when restarting this script. Set `RUN_ID` to resume a known run directly. This example stops waiting after 30 minutes without cancelling the server-side job. ```js const apiKey = process.env.AGENTSKY_API_KEY; const idempotencyKey = process.env.AGENTSKY_IDEMPOTENCY_KEY; if (!apiKey || !idempotencyKey) { throw new Error("Set AGENTSKY_API_KEY and a saved AGENTSKY_IDEMPOTENCY_KEY"); } const baseUrl = "https://gateway.agentsky.dev"; const body = JSON.stringify({ provider: "openai", endpoint: "gpt-image-2", input: { prompt: "A red bicycle on a white background", quality: "low" }, }); let runId = process.env.RUN_ID; const deadline = Date.now() + 30 * 60 * 1000; let finished = false; while (Date.now() < deadline) { let response; let run; try { response = await fetch(runId ? baseUrl + "/v1/runs/" + encodeURIComponent(runId) : baseUrl + "/v1/run", { method: runId ? "GET" : "POST", headers: { Authorization: "Bearer " + apiKey, "Content-Type": "application/json", ...(runId ? {} : { "Idempotency-Key": idempotencyKey }), }, ...(runId ? {} : { body }), signal: AbortSignal.timeout(Math.min(180_000, deadline - Date.now())), }); run = await response.json(); } catch { if (response && response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429) { throw new Error("HTTP " + response.status + "; keep the saved request and key for investigation"); } await new Promise(resolve => setTimeout(resolve, 5000)); continue; } if (run.runId) { runId = run.runId; console.log("Save runId:", runId); if (run.status === "COMPLETED") { console.log("Output:", run.output); console.log("Customer charge:", run.price.amount); finished = true; break; } if (run.status === "FAILED" || run.status === "TIMED_OUT") { throw new Error(JSON.stringify({ runId, status: run.status, error: run.error, price: run.price })); } } else if (!response.ok && response.status !== 408 && response.status !== 429 && response.status < 500) { throw new Error(JSON.stringify(run.error ?? run)); } else if (response.ok) { throw new Error("Unexpected response; retain the same request and idempotency key"); } await new Promise(resolve => setTimeout(resolve, 5000)); } if (!finished) { throw new Error("Still unresolved. Resume with the same saved key/body or RUN_ID=" + (runId ?? "unknown")); } ``` A terminal failure includes `error: { code, message }`. An HTTP failure or connection timeout does not establish the run outcome or charge: a pending generation can still complete and settle. Retrieve the saved run and check its terminal status and `price.amount`; keep the run ID for reconciliation. Do not treat a pending zero amount as final or start a new generation merely because the connection failed. ### Reading image and video outputs The `output` object follows the endpoint's response shape; there is no universal binary wrapper. Check the endpoint reference and select the appropriate field after `COMPLETED`: | Endpoint family | Media location in the run | |---|---| | GPT Image | `run.output.data[].b64_json` (base64 image) | | Nano / Gemini image | `run.output.candidates[].content.parts[].inlineData.data`, with `inlineData.mimeType`; other parts can contain text | | Wan image | `run.output.output.choices[].message.content[].image` (URL) | | Video | H3, Seedance, Wan and HappyHorse: `run.output.video_url` (URL). | | Binary audio endpoints | `run.output.data` (base64), with `mediaType` and `bytes` | For a GPT image, save `Buffer.from(run.output.data[0].b64_json, "base64")` using Node's `writeFile`; use the returned format or MIME type for the file extension. For URL outputs, download the returned URL promptly and save the response bytes. Gateway media URLs contain a signed token valid for one hour: keep them private. Reading the run again issues a fresh link while the saved run and underlying media remain available; this does not guarantee permanent storage of the provider's media. ## Errors Errors take the shape of the wire: Anthropic `{ "type": "error", "error": { "type", "message" } }`, OpenAI `{ "error": { "message", "type", "param", "code" } }`, and `{ "error": { "code", "message" } }` on run and account routes. Codes you will see: `model_not_found`, `endpoint_not_found`, `pricing_unavailable` (503, the SKU has no rate yet), `upstream_unavailable`, `insufficient_credits` / `insufficient_quota` (spend gate), `rate_limited`. ## Limits Request bodies up to 10 MB on the model wires and 1 MB on most APIs (35 MB for audio uploads). Media outputs up to 20 MB inline. Per-key rate limits apply. --- # MCP server > **Use this when:** you want to call your AgentSky agents from a Model Context Protocol client — Claude Desktop, Cursor, Zed, or any MCP-compatible host. > > **Don't use this when:** you are building an application that calls agents directly via HTTP (use the [Agent API](https://agentsky.dev/docs/agent-api.md) instead). MCP is for connecting an agent to another agent or tool host, not for building your own product on top of agents. AgentSky exposes your agents as tools over the Model Context Protocol. Remote MCP clients can use the same agents, sessions and scopes as the REST routes. - **Server URL:** `https://api.agentsky.dev/v1/mcp` - **Transport:** Streamable HTTP. Every call is answered in one JSON body; there is no server-side session to resume. - **Auth:** `Authorization: Bearer ast_…` — an API key from Developer → API keys, or `sky auth login` - **Scopes:** `read` lists and reads; `write` also starts sessions and sends messages ## Tools | Tool | What it does | Scope | |---|---|---| | `list_workspaces` | All workspaces you belong to: slug, name, role, is_personal, is_current. Pass a slug as `workspace` to any list or create tool | read | | `describe_workspace` | What the workspace has and what you may do there, read live: agents, company skills, routines and their triggers, installed connectors and their tools, policies, remaining budget, your role and route groups, each with a one-line usage example. Optional `workspace` | read | | `help` | Help generated from the server's own API document: without `topic`, the topics; with one (`routines`, `sessions`, `connectors/`, `workflow`, …), its operations, required fields, a skeleton body and a usage line. Optional `workspace` | read | | `list_agents` | Your agents in the workspace: id, name, harness, model, session count. Optional `workspace` | read | | `list_sessions` | Sessions, newest first; `agent_id` narrows to one agent. Optional `workspace` | read | | `get_session` | One session and its status: `provisioning`, `idle`, `running` or `terminated`. Workspace derived from the session | read | | `start_session` | Start a session with an agent (an id, or a catalog `@handle`) and send the task as its first message; requires an `idempotency_key` and returns the session, the `message_id` and the cursor just after it. `task_id` joins an existing task (workspace derived from it; omit `workspace`); `budget_cents` caps what the session may spend; `model` runs another model than the agent's own. Optional `workspace` | write | | `send_message` | Send the next message into an idle session; requires an `idempotency_key`. Workspace derived from the session | write | | `read_session` | That message's replies after the cursor, one page at a time; with `wait_seconds` (up to 50) waits until the turn's idle mark. Workspace derived from the session | read | | `search_context` | The one search over everything you can open in the workspace (chats, traces, memory and notes, artifacts, files, meeting notes, skills, agents, help). `query` is plain words, `"exact phrase"` or `/regex/` (every matching line, with its `path` and `line`); `scope` narrows to a folder such as `skills/review/`; a page at a time (`nextCursor` → `cursor`). Optional `workspace` | read | | `read_context` | Read a `search_context` hit in full: pass its `ref` unchanged; `nextOffset` → `offset` continues a long message | read | | `collect_result` | What a session produced: its last reply, the task files it wrote, and the `task_id` they live in. Workspace derived from the session | read | | `switch_model` | Change a session's `model`, `thinking` level or both; its next turn uses them and the conversation and files stay. Workspace derived from the session | write | | `hand_off_session` | Continue a session with another agent (`agent_id`, an id or `@handle`), optionally on a chosen `model` and `thinking` level: a new session starts with the conversation and the workspace files, and the original stays. Requires an `idempotency_key`; returns the handoff | write | | `get_handoff` | Where a handoff stands; once `completed`, `target_session` is the session to continue with | read | | `get_cloud_copy` | What is kept in the cloud for a chat synced from your own computer: its working folder and native session, so continuing it in the cloud needs no computer | read | | `stop_session` | Archive a session you are done with; its transcript, files and artifacts stay. Workspace derived from the session | write | | `search_catalog` | Every agent you can start, by `@handle`, with what it does, its categories, pricing and requirements. Optional `workspace` | read | | `list_tasks` / `get_task` | Your tasks and what each is waiting on; one task's sessions, open requests and cost. `list_tasks` accepts optional `workspace`; `get_task` derives workspace from the task | read | | `list_task_files` / `read_task_file` / `write_task_file` | A task's shared folder, as text up to 256 KiB per file. Workspace derived from the task | read / write | | `list_artifacts` / `publish_artifact` / `share_artifact` | Published results from a task, and their public link. `list_artifacts` accepts optional `workspace` (or `task_id`); the others derive workspace from the task or artifact | read / write | | `list_browser` / `share_browser` / `stop_sharing_browser` | Your saved browser (yours in every workspace) with its sites and whom you shared it with, plus the browsers shared with you here; share it with one member (`user_id`) or everyone here, for every site or only `sites`; stop a share by its id. Never returns cookies, passwords or stored state. Optional `workspace`; not with a workspace key | read / write | | `read_artifact_file` | One text file of a published artifact. `list_artifacts` with `kind: "design_system"` lists the workspace's design systems; read one's `project/README.md` before designing with it | read | | `import_design_system` / `save_design_tokens` | Import a design system's file table into a task and publish it; save its whole `tokens.json` as the next version against the `base_version` you read | write | | `save_design_system_file` | Save one of a design system's `editable_files` (its brand book `project/README.md`, guidelines, tokens) with new content, or restore it from an earlier version, as the next version against the `base_version` you read | write | | `delete_workspace` | Permanently delete a workspace you own and everything in it; `confirm` must repeat the slug. Sessions are stopped and the wallet's credit goes back to you. Not your default workspace; not with a workspace key | write | | `answer_request` | Answer a question, approval or connection a task is waiting on. Workspace derived from the task | write | | `list_reflection_proposals` / `decide_reflection_proposal` | Skill and lesson changes suggested after finished tasks (default: pending), and approving (`decision: "approve"`, writes it for the workspace) or rejecting one. Owner or admin decides. Optional `workspace` | read / write | | `get_goal` / `set_goal` / `clear_goal` | A session's standing goal: an objective (with optional criteria, each text or `{text, command}`) the session keeps working toward on its own, checked after every turn, within `max_iterations`, `max_spend_cents` and `max_minutes`. The writes need an `idempotency_key`. Workspace derived from the session | read / write | | `list_capabilities` | Everything the agents can use in the workspace, as Settings › Capabilities lists it: connected apps, tools, plugins, MCP servers, secrets, browser logins, what your Macs lend and your plans, each with where it works, who shared it, its status and its cost (external services say Provider pricing when their price is unknown). Optional `workspace` | read | | `list_plugins` / `install_plugin` / `uninstall_plugin` | Claude Code plugins in the workspace (RFC-0155): `install_plugin` takes `source` (`github:owner/repo[/path][@ref]` or a github.com URL) and installs or updates its skills and commands as workspace skills every new session gets, answering the connectors it uses and which to connect; `uninstall_plugin` takes the `name` `list_plugins` shows | read / write / write | | `update_plugin` / `start_plugin_session` / `set_plugin_policy` / `list_plugin_marketplaces` / `add_plugin_marketplace` / `remove_plugin_marketplace` | RFC-0155 phase 2: `update_plugin` switches a plugin on or off; `start_plugin_session` starts a session with only that plugin's skills and MCP servers, its calls to connected apps limited to the apps the plugin declared; `set_plugin_policy` (owners and admins) marks it recommended, default (installs it) or blocked; the marketplace tools browse and manage the workspace's Claude Code marketplaces. `install_plugin` also takes `mcp_tokens` | write / write / write / read / write / write | | `list_routines` / `create_routine` / `run_routine` / `delete_routine` | Agents on a schedule: each fire starts a new session of `agent_id` or sends `message` into `session_id`, on a 5-field `cron` in `timezone` or only when `run_routine` fires it; `budget_cents` caps each run. A routine whose paying wallet is empty pauses itself after three failed runs (`paused_reason`). `run_routine` needs an `idempotency_key`; `delete_routine` removes one for good. Optional `workspace` on list and create | read / write | | `get_usage` | Your balance, whether a card is saved, and what you spent across sessions and API keys over UTC days `from`–`to` grouped by `group_by`; with `session_id`, that session's totals and newest entries | read | | `share_session` | Turn a session's share link on (`view_only` for a read-only link) or off; returns `share_url`. Workspace derived from the session | write | | `list_session_people` / `read_session_presence` | Everyone who may send a session turns (the people a turn can name with @, by name or email local part, and the people it can be assigned to), and who has it open right now | read | | `assign_session` | Assign a session to one of its people (`user_id` from `list_session_people`), or clear it with `null`; they see it waiting on them and are notified — the thread's "Assign to…" on the web and the Mac | write | | `read_session_threads` | The threads of a session's task: root first, then helpers, handoffs and children, each with its agent, model, status and spend. Workspace derived from the session | read | | `read_session_record` | The human decisions (adopted, rerun, revise, discard, vote, did_the_job, approve) and observed outcomes (pr_merged, deployed, reopened, later_failure, shipped) recorded about a session's result. Empty means nobody decided yet | read | | `read_agent_performance` | Decisions, outcomes, spend and undecided count for every harness and model the caller's sessions ran on, over a `since`–`until` range (default last 30 days). A read model rebuilt from the session record | read | | `help_search` / `help_get` | Search the AgentSky Help Center in English or Chinese, then read one article by its `slug`; answer with the article's link, `agentsky.dev/help/` | read | | `update_agent` / `archive_agent` | Change an agent's name, engine, model or reasoning effort (only the fields passed), or archive it — the Library's agent folder | write | | `list_skills` / `list_skill_revisions` / `read_skill_file` | The workspace's skills, a skill's saved versions, and one UTF-8 file (e.g. `SKILL.md`) of a version — the Library's skills folders | read | | `list_memory_stores` / `list_memory_notes` / `read_memory_note` | The Library's memory stores, the notes in one (optionally under a folder), and one note with its content | read | | `save_memory_note` | Create a note at a `path`, or save a new version of `note_id`; every save keeps the earlier version | write | | `list_library_artifacts` / `list_files` | The person's Library outputs, and the workspace Drive's files | read | | `read_library_file_links` | Read any Library file's sharing, current revision and real available members without creating a link; pass `ref` and optional audience `workspace` | read | | `share_library_file` | Change `workspace_access` or `member_changes: [{user_id, access: none/view/edit}]`; none clears the individual override and restores workspace access; reads first, preserves every unmentioned grant and public access. Optional reviewed `revision` | write | | `publish_library_file` | Set `enabled` to create or revoke public view-only access; reads first and preserves workspace/member grants. Optional reviewed `revision` | write | | `revoke_library_file_links` | Revoke all sharing of `ref` at an explicit `revision` from a prior read; stale revisions fail without retry | write | | `read_shared_library_file` | Read a native snapshot directly from a 43-character shared URL `token`; optional `public: true` chooses the public view-only route. Returns editable, source revision and native content/history | read | | `save_shared_library_file` | Save through the original source writer with a shared token, opaque `revision`, exactly one `text` / `html` / `office` / `docx` / `restoreRevision`, and optional `requestId` UUID. Live edit grant required; no public writer; 403/409 remain tool errors without retry | write | | `read_workspace_context` | What every session in the workspace loads at start: AGENTS.md, MEMORY.md, the indexes and the caller's member page, with sizes and limits | read | | `rename_workspace` | Rename a workspace the caller owns; the slug stays | write | | `search_api` | Find the AgentSky API operation for anything a person can do on agentsky.dev or in the Mac app that no tool above covers (threads, the Library's artifacts, files, skills and memory notes, agents, workspaces, connectors, vaults, channels, routines, billing, …): words match method, path, summary and operation id | read | | `describe_api` | One operation from `search_api`: its description, parameters and request body schema | read | | `call_api` | Run one documented operation as the person with this connection's own key: the same route, checks and answer as REST at `https://api.agentsky.dev/v1`. `method`, concrete `path`, `query`, JSON `body`, optional `workspace` and `idempotency_key`; returns `status` and `body`. Only operations in the [API reference](https://agentsky.dev/docs/api) are reachable; binary transfers are for REST or `sky api` | read for GET, write otherwise | Errors carry the API's own codes (`not_found`, `insufficient_scope`, `insufficient_credits`, …) as tool errors, so a client sees the same contract it would over HTTP. ## Workspaces Every list and create tool accepts an optional `workspace` slug. Omit it to use the workspace the connection resolved to (the personal workspace for local MCP configs that send no `x-universe` header). Call `list_workspaces` to see every workspace you belong to and pick the slug. Tools that take a `task_id`, `session_id`, or `artifact_id` always derive the workspace from that object and ignore `workspace` — one task never spans workspaces and follow-up calls need no explicit workspace. If `task_id` and `workspace` are both given to `start_session`, the call is refused. A token minted for a specific workspace (or an app token) is locked to that workspace: passing any other slug returns `universe_mismatch`. The Library link tools use `GET/PUT/DELETE /library/links` through the same public API with this connection's bearer key. Their `workspace` selects the team audience without moving the original file and is never inferred from the connection workspace. The owner's native source permission and each recipient's precise workspace membership are checked independently. The reference is one of `{kind:"agent_prompt",agentId}` (the agent's user-authored AGENTS.md only), `{kind:"file",fileId,sessionId?}`, `{kind:"task_file",taskId,path}`, `{kind:"memory_note",storeId,memoryId}`, `{kind:"skill_file",skillId,path}`, `{kind:"artifact",artifactId}`, or `{kind:"platform_artifact",artifactId,path?}`. An uploaded meeting note is a Drive file. For a DesignSystem file, `platform_artifact.path` is its full native manifest path: actual `root` plus the relative `editable_files` path, or the native reader's `libraryRef`; omitting it shares the entire artifact entry. Editable files use the original versioned writer, including derived files and republishing. Token reader APIs reject `?path` rather than expanding the file grant. The API enforces source ownership, real membership, workspace-key bounds and whether edit access is supported. These tools never send messages or start an agent session. Conflicts, denied requests and incomplete API receipts return tool errors; read the current state before intentionally retrying. Existing `share_artifact` and artifact version management remain available. ## A task, start to finish 1. `list_agents` — pick the agent's `id`. 2. Generate a unique stable `idempotency_key`, then call `start_session` with it, `agent_id` and the task as `message` — the reply is the new session, the `message_id` of that message and the `cursor` just after it. 3. `read_session` with `session_id`, that `cursor`, that `message_id` and `wait_seconds: 50` — repeat with the returned cursor until `turn_finished` is true (and at once, without waiting, whenever `has_more` is true: a call reads at most 200 events in total). The first turn also boots the agent's sandbox, which can take a minute. 4. Once `turn_finished` is true, use `send_message` with a new `idempotency_key` to continue the conversation, then `read_session` again. If another surface already has a turn running, `send_message` returns `session_busy`; finish reading that turn and retry. Keep each mutation's key until its receipt arrives. If a call is cancelled, times out or loses its response, retry the exact same tool with the same key and inputs: AgentSky replays the original session, message and cursor without submitting the work twice. Reusing a key with changed inputs returns `idempotency_conflict`. Use a new key only for intentionally new work. ## Splitting work across agents 1. `search_catalog` with words for the work, and pick each agent by what it does. 2. Start the first agent with `start_session` and a `budget_cents`. Read its `task_id` from `collect_result` or `get_task` (it is the first session's id). 3. Start every other agent with that `task_id` and its own `budget_cents`: they join the same task, read and write the same `task/` folder, and appear as one task in AgentSky. 4. Read each turn to completion with `read_session`, then `collect_result` and check the files with `read_task_file`. `stop_session` the sessions you are done with. Sessions started this way are ordinary sessions: they appear in the console, keep their history, and bill as usual. ## Meta Muse (not verified) AgentSky has not exercised or certified this flow in Meta Muse. Meta's connector documentation describes custom connectors backed by a remote MCP server or public API, with credentials entered through Muse's credential prompt ([How Muse works with Connectors](https://www.meta.com/help/artificial-intelligence/1687253048996149/)). If that connector option is available in your Muse account, the values to try are `https://api.agentsky.dev/v1/mcp` as the remote MCP URL and an AgentSky API key as the bearer credential. Use a key with `write` scope to start or continue sessions. Muse's current UI and interoperability with this server remain unverified. ## One command for Claude Code and Codex ```bash npx @agentsky/sky setup ``` It signs you in if needed, registers this server with every Claude Code and Codex it finds, and installs the `agentsky` skill, which carries the agent catalog (`catalog.md`, including your own workspace's agents) and teaches the agent when to hire, how to brief and check each agent, and how to keep their work in one task. Run it again to update. `--dry-run` shows what it would change. Codex reads the key from `AGENTSKY_API_KEY`. To set things up by hand instead, use the commands below. ## Claude Code ```bash claude mcp add --transport http agentsky https://api.agentsky.dev/v1/mcp --header "Authorization: Bearer ast_..." ``` ## Codex ```bash export AGENTSKY_API_KEY=ast_... codex mcp add agentsky --url https://api.agentsky.dev/v1/mcp --bearer-token-env-var AGENTSKY_API_KEY ``` ## Other clients Most clients accept a JSON entry for a remote server with a bearer header: ```json { "mcpServers": { "agentsky": { "type": "http", "url": "https://api.agentsky.dev/v1/mcp", "headers": { "Authorization": "Bearer ast_..." } } } } ``` Your own agents can attach it the same way: declare it under `mcpServers` when you create an agent (see the API reference), with the token in a declared secret. ## Help Center server `https://api.agentsky.dev/v1/help/mcp` is a second server with only `help_search` and `help_get`. It needs no key, because the Help Center is public. Every cloud session already has it as `agentsky_help`; add it to any other MCP client the same way as the main server, without the `Authorization` header. It allows 600 calls a minute per caller address. ## Rate limits and waiting The server shares the API's per-token rate limit. `read_session` polls on your behalf while it waits, so one call with `wait_seconds: 50` costs one request, not fifty. Prefer waiting over tight loops of `get_session`. --- # Capabilities reference — what your agent can do out of the box Built-in capabilities are actions the agent invokes on its own while working — search the web, fetch pages, generate images and video, transcribe voice. You choose which ones an agent gets when you create it; no API keys or extra subscriptions. Each action below is listed with its input parameters, what it returns, and its per-call limits. Some actions have more than one provider — the agent uses the default unless asked otherwise. Media outputs are returned as hosted assets the agent can deliver on any channel. Usage is metered per the listed unit and billed from your credits; actions marked with an approval threshold ask before spending more than it. ## dataforseo ### dataforseo.ai_keywords AI keyword data — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.ai-keywords` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. search-volume — default: "search-volume" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.llm_responses LLM response data — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.llm-responses` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. live · submit · get — default: "live" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.app_info App information — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.app-info` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. apple-submit · apple-get · google-submit · google-get — default: "apple-submit" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.app_search App search — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.app-search` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. apple-submit · apple-get · google-submit · google-get — default: "apple-submit" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.backlinks Backlink analytics — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.backlinks` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. anchors · backlinks · referring-domains · summary — default: "anchors" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.business_reviews Business reviews — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.business-reviews` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. google-submit · google-get · tripadvisor-submit · tripadvisor-get · trustpilot-submit · trustpilot-get — default: "google-submit" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.business_pinterest Pinterest business data — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.business-pinterest` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. live — default: "live" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.content_analysis Content analysis — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.content-analysis` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. phrase-trends · search · summary — default: "phrase-trends" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.technologies Technology analytics — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.technologies` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. domains · summary — default: "domains" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.keyword_data Keyword data — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.keyword-data` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. bing-search-volume · google-keywords-for-keywords · google-search-volume · google-trends — default: "bing-search-volume" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.labs SEO Labs — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.labs` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. competitors · domain-rank · historical-volume · keyword-ideas · keyword-suggestions · ranked-keywords · related-keywords — default: "competitors" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.amazon Amazon marketplace data — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.amazon` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. asin-submit · asin-get · products-submit · products-get · sellers-submit · sellers-get · ready — default: "asin-submit" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ### dataforseo.onpage On-page analysis — Unavailable until the supplier pricing contract for these operations is verified. id: `dataforseo.onpage` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Allowlisted operation for this capability. content-parsing · instant-page · lighthouse — default: "content-parsing" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ## embedding ### embedding.create (openai) Create embeddings — Unavailable pending native request, billing and release acceptance. Generate vectors for text input. id: `embedding.openai` (grant it via `capabilities = […]`) · provider: openai · read-only · billed per million_tokens Parameters: - `input` (array, required) — Text strings to embed. - `model` (string) — Embedding model override. Returns: - `data` (array) — Embedding vectors. - `model` (string) — Model used. - `provider` (string) — Resolved provider. Limits: timeout 60s ### embedding.create (voyage) Create embeddings — Unavailable pending native request, billing and release acceptance. Generate Voyage vectors for text input. id: `embedding.voyage` (grant it via `capabilities = […]`) · provider: voyage · read-only · billed per million_tokens Parameters: - `input` (array, required) — Text strings to embed. - `model` (string) — Embedding model override. Returns: - `data` (array) — Embedding vectors. - `model` (string) — Model used. - `provider` (string) — Resolved provider. Limits: timeout 60s ## rerank ### rerank Rerank documents — Unavailable pending native request, billing and release acceptance. Score and order candidate documents for a query. id: `rerank.voyage` (grant it via `capabilities = […]`) · provider: voyage · read-only · billed per million_tokens Parameters: - `model` (enum) — Reranking model. rerank-3 · rerank-3-lite · rerank-2.5 · rerank-2.5-lite - `query` (string, required) — Ranking query. - `documents` (array, required) — Candidate documents. - `top_k` (integer) — Maximum results; omission ranks up to 100 documents. 1–100 Returns: - `results` (array) — Ranked documents. - `model` (string) — Model used. - `provider` (string) — Resolved provider. Limits: timeout 60s · up to 100 results ## ocr ### ocr.parse OCR document — Unavailable pending native request, billing and release acceptance. Parse a PDF or image document with Mistral OCR. id: `ocr.mistral` (grant it via `capabilities = […]`) · provider: mistral · read-only · billed per page Parameters: - `model` (enum) — OCR model. mistral-ocr-latest · mistral-ocr-4-1 - `document_url` (url) — Public document URL. - `document_base64` (string) — Base64 PDF, PNG or JPEG; data URL MIME must match bytes. - `document_type` (enum) — Use image for an image URL; inline data is detected from bytes. document · image Returns: - `pages` (array) — Parsed page content. - `provider` (string) — Resolved provider. Limits: timeout 120s · up to 10 MB ## Web ### web.search Search web — Find public web pages and return sourced search results. id: `exa.search` (grant it via `capabilities = […]`) · provider: exa · read-only · billed per run Parameters: - `query` (string, required) — Search query text sent to the web search provider. - `limit` (integer) — Maximum number of search results to return. default: 8 — 1–20 Returns: - `results` (array) — Search result objects. - `provider` (string) — Resolved provider. Limits: timeout 30s · up to 20 results ### web.fetch Fetch web page — Retrieve readable public URL content as markdown, text, or JSON metadata. id: `exa.contents` (grant it via `capabilities = […]`) · provider: exa · read-only · billed per page · default provider for this action Parameters: - `url` (url, required) — Public URL to fetch. - `format` (enum) — Returned content format. markdown · text · json — default: "markdown" Returns: - `url` (url) — Fetched URL. - `content` (string) — Normalized content. - `provider` (string) — Resolved provider. Limits: timeout 30s · up to 2 MB ### web.fetch (tinyfish) Fetch JS-heavy page — Explicit JS-heavy/browser fallback for pages Exa cannot fetch statically. id: `tinyfish.fetch` (grant it via `capabilities = […]`) · provider: tinyfish · read-only · billed per page Parameters: - `url` (url, required) — Public URL to fetch. - `format` (enum) — Returned content format. markdown · text · json — default: "markdown" Returns: - `url` (url) — Fetched URL. - `content` (string) — Normalized content. - `provider` (string) — Resolved provider. Limits: timeout 60s · up to 2 MB ### web.browser Browser session — Returns connection details for a session; it does not execute browser interactions. id: `tinyfish.browser` (grant it via `capabilities = […]`) · provider: tinyfish · read-only · billed per minute Parameters: - `url` (url, required) — Public URL to open. - `task` (string) — Legacy hint retained for compatibility; session creation does not execute it. default: "extract readable content" Returns: - `url` (url) — Opened URL. - `content` (string) — Session id and connection URLs encoded as JSON. - `provider` (string) — Resolved provider. Limits: timeout 120s · up to 120s duration ### web.automate Browser automation — Runs browser interactions and returns the completed structured result. id: `tinyfish.agent` (grant it via `capabilities = […]`) · provider: tinyfish · acts on a public site · billed per step Parameters: - `url` (url, required) — Public URL where the automation starts. - `goal` (string, required) — Browser goal to execute. Returns: - `url` (url) — Starting URL. - `provider` (string) — Resolved provider. - `goal` (string) — Executed browser goal. - `status` (string) — Terminal automation status. - `runId` (string) — Automation run id. - `steps` (integer) — Supplier-reported billable steps. - `result` (object) — Completed structured result. Limits: timeout 120s · up to 1 MB · up to 90s duration · capped at $4.8/call · asks for approval above $1.6 per call ## SEO / SERP ### serp.search Search SERP — Run bounded Google organic search and retrieve owned tasks. id: `dataforseo.serp` (grant it via `capabilities = […]`) · provider: dataforseo · read-only · billed per run Parameters: - `operation` (enum) — Supported bounded Google organic operation. Bing, Maps, News and YouTube operations are unavailable and rejected before supplier dispatch. google-organic-live · google-organic-submit · google-organic-get — default: "google-organic-live" - `tasks` (array) — Provider-native task objects: exactly one for live search; 1-10 for asynchronous submission. - `query` (string) — Query shortcut supported by SERP operations. - `locale` (string) — Locale used by the SERP query shortcut. default: "en-US" - `limit` (integer) — Legacy result-limit hint; provider returns its native result page. default: 10 — 1–100 - `task_id` (string) — Task identifier for task-get operations. - `max_cost_usd` (number) — Maximum provider cost accepted for POST operations. 0–10 Returns: - `results` (array) — Provider result objects. - `tasks` (array) — Provider task receipts. - `provider` (string) — Resolved provider. - `operation` (string) — Resolved operation. Limits: timeout 60s · up to 100 results ## Images ### image.generate Generate image — Create an image from a text prompt with OpenAI gpt-image-2. id: `gptimage.generate` (grant it via `capabilities = […]`) · provider: openai · generates media · billed per asset Parameters: - `prompt` (string, required) — Image prompt. - `size` (enum) — Output image size. 1024x1024 · 1024x1536 · 1536x1024 — default: "1024x1024" Returns: - `assetId` (asset-ref) — Guild asset id. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 20 MB · capped at $1/call · asks for approval above $1 per call ### image.remove_background Remove image background — Deterministic background removal for images. id: `rembg.remove-background` (grant it via `capabilities = […]`) · provider: rembg · transforms input · billed per asset Parameters: - `image` (file, required) — Input image file. Returns: - `assetId` (asset-ref) — Guild asset id. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 60s · up to 20 MB ## Video ### video.generate Generate video — Generate a video from a text prompt. id: `seedance.generate` (grant it via `capabilities = […]`) · provider: bytedance · generates media · billed per second · default provider for this action Parameters: - `prompt` (string, required) — Video prompt. - `duration` (integer) — Duration in seconds. default: 5 — 1–10 - `ratio` (enum) — Aspect ratio. 16:9 · 9:16 · 1:1 — default: "16:9" Returns: - `assetId` (asset-ref) — Guild asset id. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 10s duration · capped at $5/call · asks for approval above $5 per call ### video.generate (alibaba) Generate with Wan 2.7 — Staged Wan2.7 text-to-video; unavailable until supplier release acceptance. id: `wan.t2v` (grant it via `capabilities = […]`) · provider: alibaba · generates media · billed per second Parameters: - `max_cost_usd` (number) — Maximum supplier cost accepted; unavailable for unverified clip continuation. min 0 - `prompt` (string, required) — Video prompt up to 5000 characters. - `negative_prompt` (string) — Exclude these elements; up to 500 characters. - `audio_url` (url) — Optional driving audio URL. - `ratio` (enum) — Output aspect ratio. 16:9 · 9:16 · 1:1 · 4:3 · 3:4 — default: "16:9" - `duration` (integer) — Requested output seconds. default: 5 — 2–15 - `resolution` (enum) — Output resolution. 720P · 1080P — default: "1080P" - `watermark` (boolean) — Include watermark. default: false - `seed` (integer) — Random seed. 0–2147483647 - `prompt_extend` (boolean) — Rewrite the prompt before generation. default: true Returns: - `assetId` (asset-ref) — Generated video asset. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 15s duration · capped at $5/call ### video.generate (alibaba) Generate with HappyHorse 1.1 — Staged HappyHorse 1.1 text-to-video. Only the documented five-second request is supported; unavailable until release acceptance. id: `happyhorse.t2v` (grant it via `capabilities = […]`) · provider: alibaba · generates media · billed per second Parameters: - `max_cost_usd` (number) — Maximum supplier cost accepted; unavailable for unverified clip continuation. min 0 - `prompt` (string, required) — Video prompt: up to 5000 non-Chinese or 2500 Chinese characters. - `ratio` (enum) — Output aspect ratio. 16:9 · 9:16 · 1:1 · 4:3 · 3:4 · 4:5 · 5:4 · 9:21 · 21:9 — default: "16:9" - `duration` (integer) — Requested output seconds. default: 5 — 5–5 - `resolution` (enum) — Output resolution. 480P · 720P · 1080P — default: "1080P" - `watermark` (boolean) — Include watermark. default: true - `seed` (integer) — Random seed. 0–2147483647 Returns: - `assetId` (asset-ref) — Generated video asset. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 5s duration · capped at $5/call ### video.generate (minimax) Generate with MiniMax-H3 — Generate MiniMax-H3 video using a prompt and optional public media URLs. Requested duration is billed rounded up to whole seconds. id: `minimax.h3` (grant it via `capabilities = […]`) · provider: minimax · generates media · billed per second Parameters: - `max_cost_usd` (number) — Maximum supplier cost accepted. min 0 - `prompt` (string, required) — Video prompt. - `quality` (enum, required) — Required H3 generation quality. low · high - `duration_s` (number) — Requested seconds, billed rounded up. default: 5 — 3–15 - `resolution` (enum) — Output resolution. 480p · 768p · 1080p — default: "768p" - `aspect_ratio` (enum) — Output aspect ratio. 16:9 · 9:16 · 1:1 · 4:3 · 3:4 · 21:9 — default: "16:9" - `watermark` (boolean) — Include watermark. default: false - `enhance` (boolean) — Enhance prompt. default: true - `priority` (integer) — Normal 1000 or immediate 0 priority. default: 1000 — 0–1000 - `seed` (integer) — Nonnegative safe integer seed. 0–9007199254740991 - `first_frame_image_url` (url) — Optional first frame URL. - `last_frame_image_url` (url) — Optional last frame; requires a first frame. - `reference_video_url` (array) — At most one public video URL; cannot combine references and frames. - `reference_image_url` (array) — One to four public image URLs; cannot combine references and frames. Returns: - `assetId` (asset-ref) — Generated video asset. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 15s duration · capped at $5/call ### video.from_image Image to video — Generate with the existing Wan2.6 I2V Flash wrapper from a public image URL. id: `mm.i2v` (grant it via `capabilities = […]`) · provider: alibaba · generates media · billed per second · default provider for this action Parameters: - `image` (url, required) — Public first-frame image URL. - `prompt` (string) — Motion/video prompt. - `duration` (integer) — Duration in seconds. default: 5 — 1–10 Returns: - `assetId` (asset-ref) — Guild asset id. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 10s duration · capped at $5/call · asks for approval above $5 per call ### video.from_image (alibaba) Animate with Wan 2.7 — Staged Wan2.7 frame or clip video. Full reported continuation duration is billable; unavailable until release acceptance. id: `wan.i2v` (grant it via `capabilities = […]`) · provider: alibaba · generates media · billed per second Parameters: - `max_cost_usd` (number) — Maximum supplier cost accepted; unavailable for unverified clip continuation. min 0 - `media` (array, required) — Native media objects with type and public url: first_frame with optional last_frame/driving_audio, or first_clip with optional last_frame. - `prompt` (string) — Optional video prompt up to 5000 characters. - `negative_prompt` (string) — Exclude these elements; up to 500 characters. - `duration` (integer) — Requested output seconds. default: 5 — 2–15 - `resolution` (enum) — Output resolution. 720P · 1080P — default: "1080P" - `watermark` (boolean) — Include watermark. default: false - `seed` (integer) — Random seed. 0–2147483647 - `prompt_extend` (boolean) — Rewrite the prompt before generation. default: true Returns: - `assetId` (asset-ref) — Generated video asset. - `mimeType` (string) — Output MIME type. - `provider` (string) — Resolved provider. Limits: timeout 300s · up to 15s duration · capped at $5/call ## Voice ### voice.transcribe Transcribe voice with Fish Audio — Default transcription for audio and video files. id: `fish-audio.transcribe` (grant it via `capabilities = […]`) · provider: fish-audio · transforms input · billed per minute Parameters: - `audio` (file, required) — Audio or video file to transcribe. - `language` (string) — Language code or auto. default: "auto" - `format` (enum) — Transcript output format. text · json · srt — default: "text" Returns: - `text` (string) — Transcript text. - `language` (string) — Detected or requested language. - `provider` (string) — Resolved provider. Limits: timeout 120s · up to 50 MB · up to 3600s duration --- # Skills > Reusable instruction + script bundles your agents load on demand. Upload a skill once to your universe, attach it to any agent by reference — pinned to an immutable revision or tracking `latest` — or point an agent at a GitHub repo's `.claude/skills/` directory. The endpoint-level reference lives in the [API reference](https://agentsky.dev/docs/api.md). ## What a skill is A skill is a directory with a `SKILL.md` at its root plus any supporting files — reference docs, templates, and executable scripts the agent runs with bash. `SKILL.md` starts with YAML frontmatter: ```markdown --- name: pdf-processing description: Extract text and tables from PDF files. Use when working with PDFs. --- # PDF processing Run \`scripts/extract.py \` with bash and summarize its output. ``` Frontmatter rules (enforced at upload): `name` ≤64 chars, lowercase letters, digits, and hyphens only; `description` non-empty, ≤1024 chars — it is what the agent matches your request against, so say what the skill does *and* when to use it. Bundles are capped at 30 MB uncompressed; binary files are fine. ## Upload `POST /v1/skills` takes multipart `files` entries — one zip archive, or individual files whose multipart filenames carry their relative paths. Zip entries keep their unix exec bit, so bundled scripts arrive runnable (loose files default to non-executable — ship executables via zip). ```bash curl -X POST https://api.agentsky.dev/v1/skills \ -H "Authorization: Bearer ast_..." \ -F "files=@pdf-processing.zip" # -> { "skill": { "id": "skill_…", "latestRevisionId": "skr_…", … }, "revision": { … } } ``` Skills are scoped to your universe: any token with access can list, attach, and delete them. ## Revisions Every upload is an **immutable revision**; the skill keeps a `latest` pointer. A new revision (`POST /v1/skills/{skillId}/revisions`) is a complete snapshot — omitted files are not carried over — and its `SKILL.md` `name` must match the skill's name. Deleting is reference-aware: a skill attached to an agent returns 409 `skill_in_use`; a pinned revision returns 409 `revision_in_use`; detach first. ## Read a file Use `GET /v1/skills/{skillId}/revisions/{revisionId}/content?path=SKILL.md` with read scope to retrieve a UTF-8 file as `{ "path": "SKILL.md", "content": "…" }`. URL-encode nested paths. The revision is immutable and must belong to the selected skill in your universe. Missing paths return 400, absent files return 404, and binary files return 415. File content responses are not cached. ## Attach to an agent The `skills` array on agent create/PATCH (no skill-count limit; PATCH replaces the whole array, omitted preserves): ```json "skills": [ { "type": "skill", "skillId": "skill_…", "version": "latest" }, { "type": "skill", "skillId": "skill_…", "version": "skr_…" }, { "type": "github", "url": "https://github.com/org/repo", "ref": "main" } ] ``` `latest` resolves to a concrete revision **when a session's pod is provisioned** — a running session never changes skills because you uploaded a new revision; new sessions pick up the new latest. Pin a `skr_…` id for exact reproducibility. Changes to the array take effect at the next session start. In [agent.toml](https://agentsky.dev/docs/agent-toml.md) the same entries are `[[skills]]` tables, and `sky deploy` re-applies them. ## The GitHub shortcut A `{ "type": "github" }` entry skips the store entirely: at every session start the repo is shallow-cloned and its root `.claude/skills//SKILL.md` directories — exactly one level deep — are installed. `ref` pins a branch or commit SHA (default branch when omitted); a branch re-resolves at each session start. For private repos, set `tokenSecretRef` to the name of an agent secret holding a clone token. Not discovered: a bare `.claude/skills/SKILL.md`, anything nested deeper than one level, a `skills/` directory outside `.claude`, or a `.claude/skills` inside a subdirectory. A repo you attach is part of your agent's trust boundary — anyone who can commit to it ships instructions your agent will follow. ## How agents use skills At session start each skill lands on the pod's filesystem where the engine reads it. The agent sees every skill's name and description, reads `SKILL.md` when a task matches, and runs bundled scripts with bash — script output enters the conversation, script code does not. Agents can also install more skills mid-session from the marketplace (`actl skill`). --- # Routines > **Use this when:** you want an agent to fire automatically on a cron schedule — daily reports, weekly scans, periodic checks — without writing a cron service yourself. > > **Don't use this when:** you need to trigger an agent in response to a live user action (use the [Agent API](https://agentsky.dev/docs/agent-api) instead for request-response flows). Or if you want to receive agent messages in a messaging platform (see [Channels](https://agentsky.dev/docs/channels)). A **routine** makes an agent act on its own, on a schedule. It binds the events to deliver on each fire to a **target** — create a fresh session per run, or send the turn into an existing long-lived session — and a cron **schedule**. Every fire records a **routine run** you can list, and can push webhooks. ## Create one ```bash curl -X POST https://api.agentsky.dev/v1/routines \ -H "Authorization: Bearer ast_..." -H "Content-Type: application/json" \ -d '{ "name": "Weekly compliance scan", "target": { "type": "new_session", "agent": "agent_..." }, "initial_events": [ { "type": "user.message", "content": [{ "type": "text", "text": "Run the weekly compliance scan." }] } ], "schedule": { "type": "cron", "expression": "0 20 * * 5", "timezone": "America/New_York" } }' ``` The response echoes the routine with `schedule.upcoming_runs_at` — the next (up to 5) fire times — so you can confirm the cron before it runs. `POST /v1/routines/{id}/run` fires immediately, outside the schedule, to test it. ## Two target modes - `{"type": "new_session", "agent": "agent_...", "environment_id"?, "vault_ids"?}` — each fire creates a fresh session (lazily provisioned) and delivers `initial_events` as its first turn. Sessions are ordinary sessions: they show up in lists, bill normally, and keep their history. - `{"type": "session", "session_id": "sess_..."}` — each fire sends `initial_events` as a turn into the same long-lived session, so the agent keeps its memory, files, and history across runs. If the session already has a turn in flight at fire time, the fire is skipped and recorded as a failed run (`session_busy_error`); nothing queues, and the next occurrence tries again. ## Schedule semantics 5-field POSIX cron (`minute hour day-of-month month day-of-week`, day-of-week 0–7 with both 0 and 7 = Sunday) plus a required IANA timezone. Extended syntax (`L W # ?`, `@daily`, seconds fields) is rejected. Matching is literal wall-clock in the configured timezone: `0 20 * * *` in `America/New_York` fires at 8 PM local year-round. On DST days, a time that doesn't exist fires at the first valid instant after the gap and a time that occurs twice fires twice — schedule outside 1–3 AM local (or in UTC) if that matters. Missed occurrences are never backfilled: any number of missed fires collapse into one at the next tick. Minimum granularity is one minute. `schedule` is nullable — a routine without one only fires manually. ## Budgets Pass the optional `budget` (same shape as a session budget — see the API reference) to bound **each run separately**: - new_session mode: the cap is copied onto each session the routine creates. - session mode: each fire grants a delta allowance — the target session's cap becomes `consumed + budget` at fire time (overwritten each run, never accumulated). The target must have been created with a budget. ## Runs Every fire attempt — scheduled or manual — records a run with exactly one of `session_id` or a typed `error`: ```bash curl "https://api.agentsky.dev/v1/routine-runs?routine=rtn_...&has_error=true" -H "Authorization: Bearer ast_..." ``` Three runs in a row that fail with an error the next run would repeat — an archived or deleted agent/session/environment/vault, a rejected run, or `insufficient_credits_error` (the wallet that pays for the routine is empty) — **pause the routine** with the error mirrored in `paused_reason`, so you can fix the reference and `unpause` (which resumes from the next occurrence). A routine paused for `insufficient_credits_error` resumes on its own, from its next occurrence, within a minute of its paying wallet holding credit again; a routine you paused yourself stays paused. A paused routine makes no runs. Transient errors — `rate_limited_error`, `session_busy_error` — leave the routine active. ## Lifecycle `POST .../pause` suppresses scheduled fires (manual `run` still works); `POST .../unpause` resumes from the next occurrence; `POST .../archive` is terminal. `DELETE /routines/{id}` deletes it: it never fires again and no read returns it (the sessions its runs started are kept). Updates are omit-to-preserve: `initial_events` replaces wholesale, `metadata` is key-patched, `schedule`/`budget` clear with `null`. ## Webhooks Register an endpoint to be notified without polling: ```bash curl -X POST https://api.agentsky.dev/v1/webhooks \ -H "Authorization: Bearer ast_..." -H "Content-Type: application/json" \ -d '{"url": "https://example.com/hooks/agentsky", "events": ["routine_run.failed", "routine.paused"]}' ``` The create response is the only place the `whsec_...` signing secret appears — store it. Deliveries carry `x-asteroids-event`, `x-asteroids-delivery`, `x-asteroids-timestamp`, and `x-asteroids-signature: v1=.")>`; verify the signature, dedupe on the event id, and fetch the resource by `data.id` — payloads are thin and ordering is not guaranteed. Event types: `routine.created/updated/paused/unpaused/archived`, `routine_run.started/succeeded/failed` (scheduled fires only; manual runs emit none), `run.completed/interrupted/failed/blocked` (session run lifecycle; fetch the run via `GET /api/v1/runs/{data.id}`), and `goal.completed/stopped/paused/resumed` (fires when a session goal reaches that status). Goal events also include `data.session_id`; fetch the current goal state via `GET /api/v1/sessions/{data.session_id}/goal`. Failed deliveries retry twice (5s, 30s); sustained failure disables the endpoint until you `PATCH {"status": "active"}`. --- # Channels > **Use this when:** you want to put your agent in Telegram, Slack, Discord, WhatsApp, or other messaging platforms — Channels handles the webhook ingress, platform rendering, retries, and routing from a message to an agent session and back. > > **Don't use this when:** you are building an HTTP API integration directly in your own application (use the [Agent API](https://agentsky.dev/docs/agent-api.md) instead). Channels is for messaging platform surfaces, not for your own product's backend-to-backend calls. > One normalized API — connections, bindings, threads, events — with the platform machinery (webhook ingress, socket fleets, rendering, reactions, retries) managed for you. Every conversation runs under **your** bot identity: your Slack app, your BotFather bot, your Discord bot, your WhatsApp business number, your Azure Bot, your Lark app — and soon your iMessage number. In this section: [Quickstart](https://agentsky.dev/docs/channels/quickstart.md) · [Routing](https://agentsky.dev/docs/channels/routing.md) · [Webhooks](https://agentsky.dev/docs/channels/webhooks.md) · guides for [Telegram](https://agentsky.dev/docs/channels/guides/telegram.md), [Slack](https://agentsky.dev/docs/channels/guides/slack.md), [Discord](https://agentsky.dev/docs/channels/guides/discord.md), [WhatsApp](https://agentsky.dev/docs/channels/guides/whatsapp.md), [Microsoft Teams](https://agentsky.dev/docs/channels/guides/teams.md), [Lark](https://agentsky.dev/docs/channels/guides/lark.md), [LINE](https://agentsky.dev/docs/channels/guides/line.md), [iMessage](https://agentsky.dev/docs/channels/guides/imessage.md) (coming soon), and [Email (SES)](https://agentsky.dev/docs/channels/guides/email.md). The endpoint-level reference lives in the [API reference](https://agentsky.dev/docs/api.md). ## The object model - **Channel app** — your own bot credentials for one platform (`POST /v1/channels/apps`), encrypted at rest and write-only through the API. Registration proves the credentials against the platform where an API exists and returns the `setup` steps the platform cannot automate. - **Connection** — one installed conversation surface: a Telegram chat, a Slack channel, a Discord channel, a WhatsApp conversation, a Lark chat or group, an iMessage number pairing. - **Binding** — routes a connection (or one thread of it) to a destination: an AgentSky session, or your own webhook endpoint. - **Thread ids** — plain `{platform}:…` strings used verbatim for posting and events (e.g. `telegram:12345`, `slack:C0123:1712.0034`). Treat them as opaque. - **Webhook endpoint** — where your service receives signed channel events (`message.received` and friends). ## Routing modes **Session destination (zero code)** — bind a connection to an AgentSky session: inbound messages become agent turns, replies flow back automatically, and working markers (⏳ → ✅) ride the platform's reactions where supported. **Webhook destination (your code)** — bind to a webhook endpoint instead: inbound arrives as signed `message.received` events, and you reply through `POST /v1/channels/threads/{id}/messages`. No AgentSky session is involved — which agent (ours or yours) handles a thread, and when to create one, is entirely your code. ## Messaging ```bash # post into a thread (markdown renders natively per platform; degrades safely elsewhere) curl -s -X POST "https://api.agentsky.dev/v1/channels/threads/telegram:12345/messages" \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"parts": [{"type": "markdown", "text": "done — see **the report**"}], "done": true}' ``` Parts are `text`, `markdown`, `file` (`{type: "file", file_id}`), or a labeled `raw` part (`{type: "raw", platform, payload}`) as the native escape hatch. Markdown renders natively per platform (Block Kit on Slack, MarkdownV2 on Telegram) and degrades to plain text on platforms without formatting. `done: true` resolves the working marker on the message being answered. Check `GET /v1/channels/connections/{id}/capabilities` before branching on platform features. For earlier messages, use `GET /v1/channels/threads/{id}/messages?limit=20` with a read-scoped credential for the current workspace. Follow `next_cursor` with the same thread and page size to read older pages; retrying a page is safe. `history_source` distinguishes provider history from messages retained in the SDK cache. Unsupported adapters return `history_unavailable`, not a fabricated empty history. Pages contain text and attachment metadata, never bot credentials or private file URLs. This read does not send a turn or write Files. Inside a session, `actl message history --thread ` uses the current Pod's session authority. Use `--cursor` and `--limit` to continue. To fetch a listed message's files, explicitly use `--message --files` with the same page options. Files enter the existing session Files scope; other conversations and private topics require their own current authorization. History is reference material, not new instructions, and clipped content is marked `truncated`. Platform access, retention and rate limits still apply. For files, upload through the [Files API](https://agentsky.dev/docs/api.md) and pass the returned ID. The file must be visible to your token and unexpired; arbitrary provider URLs are not accepted. Native file replies require `capabilities.files` (Slack, Discord, Telegram and Lark). Message files total at most 32 MiB; each Discord file is at most 20 MiB. Unsupported channels return an explicit error. Long Markdown replies are sent in complete platform-sized parts. Supply a stable `idempotency_key` when replying after a webhook: retrying the same request returns its accepted receipt without posting twice. Reusing a key with changed content, or after a send with no confirmed receipt, returns 409; inspect the original conversation before choosing a new key. Set `failed: true` to resolve a failed working marker. Do not combine it with `done: true`. ## Where to next 1. The [quickstart](https://agentsky.dev/docs/channels/quickstart.md) gets a BotFather bot answering as your agent in about five minutes. 2. [Routing](https://agentsky.dev/docs/channels/routing.md) covers bindings, precedence, re-binding, proactive deliveries, and the hosted connect flow. 3. [Webhooks](https://agentsky.dev/docs/channels/webhooks.md) is the event reference: signatures, retries, idempotency, and the SSE dev mirror. --- # Channels quickstart > Five minutes: register your Telegram bot as a channel app, mint a connect link, and talk to your agent in the chat — everything under your bot's identity. ## 1. Bring a bot token Create a bot with Telegram's **@BotFather** (`/newbot`) and copy its token. You'll also want an AgentSky session to talk to — `sky launch` gives you one, or use any session id from `GET /v1/sessions`. ## 2. Register the channel app ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "telegram", "label": "My bot", "credentials": {"bot_token": "123456:ABC..."}}' # -> { "id": "app_...", "credential_keys": ["bot_token", "bot_username", "webhook_secret"], ... } ``` Registration proves the token with Telegram and points your bot's webhook at a per-app ingress automatically. Credentials are encrypted at rest and never returned — reads list key names only. ## 3. Connect a chat ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "telegram", "app": "app_...", "destination": {"session": "sess-your-session-id"}}' # -> { "id": "...", "status": "PENDING", "connect": { "url": "https://t.me/your_bot?start=..." } } ``` Open the URL and tap **Start**. The chat claims the link (links expire after 15 minutes), the connection flips `CONNECTED`, your session becomes its default binding, and the bot greets the user. A `channel.connected` event lands on your webhook endpoints with your `metadata` echoed. ## 4. Talk Message the bot — the text becomes an agent turn and the reply lands back in the chat, rendered from markdown. The working marker rides the message (👀 while working on Telegram, resolved when the agent finishes). ```bash # proactive: post into the thread yourself curl -s -X POST "https://api.agentsky.dev/v1/channels/threads/telegram:app_...:12345/messages" \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"parts": [{"type": "markdown", "text": "nightly report is **ready**"}]}' ``` ## No agent? Route to your own code instead Skip `destination` and bind the connection to a webhook endpoint: inbound arrives as signed `message.received` events and you answer through the threads API — no AgentSky session involved. See [Routing](https://agentsky.dev/docs/channels/routing.md) and [Webhooks](https://agentsky.dev/docs/channels/webhooks.md). --- # Channel routing > A binding routes a connection — or one thread of it — to a destination: an AgentSky session, or your webhook endpoint. Which agent handles a conversation is a binding you control; with webhook destinations it's entirely your code. ## Bindings ```bash # route the whole connection to a session (or {"webhook": "whk_..."}) curl -s -X PUT https://api.agentsky.dev/v1/channels/connections/{id}/binding \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"destination": {"session": "sess-..."}}' # route one thread to your webhook endpoint instead curl -s -X POST https://api.agentsky.dev/v1/channels/bindings \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"connection_id": "...", "thread_id": "telegram:app_...:12345", "destination": {"webhook": "whk_..."}}' ``` A destination names exactly one of `session` or `webhook`. Connection-level routing is a **single slot**: `PUT .../binding` replaces whatever the connection routed to before, so there is never a second competing default. `POST /bindings` creates thread-scoped rules only, and **thread-scoped bindings win over the connection default**. `route_key` is a free label for organizing multiple binding rows. An inbound message on a connected surface with no active binding emits `channel.unmapped_conversation` instead of dropping. ## Session mode Session-bound inbound becomes agent turns automatically: the platform shows a working marker while the agent runs, the reply posts back into the thread as rendered markdown, and `turn.refused` reaches your endpoints when a turn is rejected. Re-point a surface with one call — the connection keeps a **single active session**: ```bash curl -s -X PUT https://api.agentsky.dev/v1/channels/connections/{id}/binding \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"destination": {"session": "sess-other"}}' # posts "You're now talking to ..." into the surface and emits channel.rebound ``` ## Webhook mode Bind to a webhook endpoint and the service delivers `message.received` for every inbound; you reply through `POST /v1/channels/threads/{thread_id}/messages`. Mapping threads to agents — yours or AgentSky sessions you drive via the [sessions API](https://agentsky.dev/docs/api.md) — is your code: look the thread up, create what's missing, send the turn. ## Proactive deliveries ```bash # fan out to every thread bound to a destination curl -s -X POST https://api.agentsky.dev/v1/channels/deliveries \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"destination": {"session": "sess-..."}, "parts": [{"type": "text", "text": "nightly report ready"}]}' ``` The response carries per-thread results; failures also emit `delivery.failed`. ## The connect flow `POST /v1/channels/connections` on a link-flow platform (telegram, imessage, whatsapp, lark) returns a `PENDING` connection with a `connect` ceremony — a deep link, and for code flows a phone number + `LINK:` code. Links expire after 15 minutes. - **Hosted connect page** — `https://connect.agentsky.dev/connect/{token}` renders the ceremony for you; agents can hand the link to an end user mid-conversation (no credentials transit the page). - **`callback_url`** — pass it on the connect call and the hosted page polls `GET /connect/{token}/status`, then redirects the end user to your URL with `connection_id`, `status`, and your `metadata` echoed as query params once the claim lands. - **Claim** — the end user taps Start (Telegram) or sends the `LINK:` code (iMessage, WhatsApp, Lark). The connection flips `CONNECTED`, the pre-bound `destination` becomes the default binding, and `channel.connected` fires with your `metadata`. Slack and Discord with `destination.webhook` use the same pending LINK flow; pass your registered `app`, open the returned hosted URL and send its code in the intended bot conversation. Their session/channel-creation flows still return `CONNECTED` synchronously and do not accept `callback_url`. The hosted page shows pending, connected, expired or disconnected status even without a callback, retries after temporary network failures, and preserves the result when refreshed. Your authenticated app can poll `GET /v1/channels/connections/status?token={token}`; it returns the effective `connection_id`, `platform`, `metadata` and lowercase `status`. The token is the part after LINK: or the Telegram start parameter. Treat it as private. Reconnecting a conversation can replace the pending ID with its existing ID. A connected status proves the claim, not that your webhook has finished processing it; honor normal API rate limits. ## Migrate an existing verified conversation An admin-scoped token can call `POST /v1/channels/connections` with `platform`, an owned active `app`, one `destination` and `adopt: {thread_id, author_id}`. Use this only when your application already verified the sender binding and consent. AgentSky checks the app-scoped thread and destination; adoption does not challenge the user or send a platform message. Preserve the original identity and conversation metadata. An identical replay reuses the connection. A different sender, metadata or destination, an inaccessible workspace, or a disconnected record is rejected. Do not combine adoption with `callback_url` or `invite_user`. Ordinary new connections should use the LINK ceremony. --- # Channel webhooks > Everything that happens on your channels arrives as signed HTTP events. Register an endpoint once; consume idempotently. ## Endpoints ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/webhooks \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"url": "https://your.app/hook", "events": ["message.received", "channel.connected"]}' # -> returns the whsec_ signing secret ONCE — store it ``` `events` defaults to `["message.received"]`. `GET /v1/channels/webhooks` lists endpoints; `DELETE /v1/channels/webhooks/{id}` removes one; `PATCH /v1/channels/webhooks/{id}` re-enables a failure-disabled endpoint (keeping its secret) or re-picks events. The URL must be https (http is allowed for localhost during development). The [developer console](https://platform.agentsky.dev/developer/webhooks) manages the same endpoints — the signing secret is shown exactly once, at creation: ![The developer console's webhook endpoints page showing the one-time whsec_ signing secret](https://platform.agentsky.dev/guides/channels/webhooks/endpoint-secret.png) ## Delivery format Every delivery is a POST with a JSON body `{id, type, createdAt, data}` and these headers: | Header | Meaning | |---|---| | `X-Asteroids-Event` | the event type | | `X-Asteroids-Delivery` | unique delivery id — your dedupe key | | `X-Asteroids-Timestamp` | unix seconds, signed | | `X-Asteroids-Signature` | `v1=` — HMAC-SHA256 over `{timestamp}.{rawBody}` with your `whsec_` secret | ```js import { createHmac, timingSafeEqual } from "node:crypto"; function verify(secret, timestamp, rawBody, signatureHeader) { const expected = "v1=" + createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex"); return timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected)); } ``` Failed deliveries retry at 0s/5s/30s. After ~20 consecutive failures the endpoint is disabled and events stop until you re-create it. Retries reuse the delivery id — consume idempotently by `X-Asteroids-Delivery`. ## Event catalog | Event | When | |---|---| | `message.received` | an inbound message on a webhook-bound conversation — carries `connection_id`, `app_id`, `thread_id`, `message_id`, `text`, `conversation`, `author`, optional `attachments`, and your connection `metadata` | | `message.reaction` | an end user reacted to a message | | `interaction.action` | a button/select was tapped (platform spinners are acknowledged automatically) | | `command.received` | a slash command arrived on a connected surface | | `channel.connected` | a connect link was claimed — carries your `metadata` | | `channel.needs_reauth` | the platform surface needs re-authorization | | `channel.disconnected` | the surface was disconnected | | `channel.unmapped_conversation` | a message arrived on a connected surface with no active binding — bind or reply via the threads API | | `turn.refused` | a session-bound turn was refused (for example, out of credits) | | `delivery.failed` | a proactive delivery could not be posted | The signed `app_id` comes from the registered connection, and `conversation.is_dm` comes from the platform SDK. Use these fields for app identity and private/group handling; user-supplied metadata is not an identity proof. Attachments contain `file_id`, `type`, `name`, `mime_type` and `size_bytes`, scoped through the Files API. A media-only message can have empty text. Inbound files total at most 32 MiB per message; paired WhatsApp accepts at most 20 MiB. A failed download produces retry guidance rather than forwarding an incomplete message. Events remain in the connection's workspace, including disconnect notifications. Personal legacy connections and the owner's personal workspace share one scope. Workspace-scoped event streams cannot read another workspace; owner-wide API tokens retain their management view. ## Local development: the SSE mirror ```bash curl -N https://api.agentsky.dev/v1/channels/events/stream \ -H "Authorization: Bearer $AST_TOKEN" ``` `GET /v1/channels/events/stream` mirrors your webhook events as server-sent events — same payloads, no public URL or tunnel needed while developing. The [developer console's Events tab](https://platform.agentsky.dev/developer/events) renders the same stream live: ![The developer console's Events tab streaming channel events live](https://platform.agentsky.dev/guides/channels/events/live-stream.png) --- # Telegram guide > The fastest platform to wire up: bring a BotFather token, and registration does the rest. ## Prerequisites - A bot created with **@BotFather** (`/newbot`) and its token. - An AgentSky API token with write scope. ## Register the channel app No terminal needed — the [developer console](https://platform.agentsky.dev/developer/apps/new?platform=telegram) walks the same checklist: ![The developer console's new-app checklist for Telegram: ① create a bot with BotFather, ② paste its token](https://platform.agentsky.dev/guides/channels/telegram/register-app.png) ① Message @BotFather, send `/newbot`, copy the token · ② paste it and create — the webhook is registered automatically: ![After creation the console confirms the webhook was registered and links to the app](https://platform.agentsky.dev/guides/channels/telegram/registered.png) Or over the API: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "telegram", "label": "My bot", "credentials": {"bot_token": "123456:ABC..."}}' ``` Registration proves the token with Telegram and points the bot's webhook at a per-app ingress with a fresh secret — nothing to paste anywhere. The stored credential keys grow `bot_username` and `webhook_secret`. ## Connect ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "telegram", "app": "app_...", "destination": {"session": "sess-..."}}' ``` The response's `connect.url` is a `t.me/your_bot?start=…` deep link; **Start** in the chat claims it. Links expire after 15 minutes, and a link minted for your bot is refused if opened on any other bot. ## Capabilities | threads | reactions | markers | proactive | markdown | modals | ephemeral | streaming | |---|---|---|---|---|---|---|---| | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | Markdown renders as Telegram MarkdownV2. Working markers use Telegram's reaction set (👀 while working, 👍 done, 💔 failed — Telegram only allows a fixed reaction list). ## Troubleshooting - **App creation answers 400 `telegram_error`** — the token failed `getMe`; re-copy it from BotFather. - **The deep link says it belongs to a different bot** — it was minted for another channel app; mint a new connection with the right `app`. - **Nothing arrives from a group** — add the bot to the group and mention it (or message it directly); group privacy rules apply until the bot is engaged. --- # Slack guide > Bring your own Slack app: end users see your name and icon, and conversations run in channels your app creates. ## Prerequisites - A Slack app installed in your workspace, with its **bot token** (`xoxb-…`) and **signing secret**. The bot needs permission to create channels, invite users, post messages, and add reactions. - An AgentSky API token with write scope. ## Create the Slack app At [api.slack.com/apps](https://api.slack.com/apps), choose **Create New App**: ![Slack's Create-new-app dialog with the manifest path highlighted](https://platform.agentsky.dev/guides/channels/slack/create-app-modal.png) ① Pick **From a manifest** — the [developer console](https://platform.agentsky.dev/developer/apps/new?platform=slack) hands you a manifest with the bot identity, scopes, and event subscriptions pre-filled · ② **Continue**, pick your workspace, and install. Then copy the **Bot User OAuth Token** (OAuth & Permissions) and **Signing Secret** (Basic Information). ## Register the channel app The [developer console](https://platform.agentsky.dev/developer/apps/new?platform=slack) runs the same checklist with the manifest copy button and the events URL ready to paste: ![The developer console's new-app checklist for Slack: manifest, credentials, events URL](https://platform.agentsky.dev/guides/channels/slack/register-app.png) Or over the API: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "slack", "label": "My app", "credentials": {"bot_token": "xoxb-...", "signing_secret": "..."}}' # -> setup: { "events_url": "https://.../webhooks/slack/app_..." } ``` Registration proves the token (`auth.test`). Slack has no API for webhook config, so paste `setup.events_url` into your app's **Event Subscriptions** request URL and **Interactivity** request URL — Slack's URL verification is answered automatically at that path. ## Connect ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "slack", "app": "app_...", "destination": {"session": "sess-..."}, "invite_user": "U012345"}' ``` For the session destination above, Slack connects synchronously: the call creates a per-agent channel under your app's identity, optionally invites `invite_user`, posts a greeting, and returns `CONNECTED` — no pending ceremony. For a `destination.webhook`, the response instead includes a pending connection and a `connect.code`. Send that code in the bot's DM, or invite the bot into the public or private channel you want to connect and send `@YourBot LINK:…`. Use the exact returned code with one leading mention of your bot and no other text. Replies stay in that conversation's thread. A later speaker's identity remains distinct from the person who connected the channel. Private-channel follow-ups in the connected thread without another mention require [`groups:history`](https://docs.slack.dev/reference/scopes/groups.history/) and the [`message.groups` event](https://docs.slack.dev/reference/events/message.groups/). The generated manifest includes both. For an existing app, add the scope and event subscription, reinstall it in the workspace to approve the new scope, and update the registered credentials if Slack issues a new token. The bot must be a member of the private channel; granting the scope alone does not add it. Mention the bot again to start a new top-level conversation. The existing AgentSky shared-app OAuth flow already requests private-channel history access. ## Capabilities | threads | reactions | markers | proactive | markdown | modals | ephemeral | streaming | |---|---|---|---|---|---|---|---| | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Markdown renders as Block Kit; tables become native table blocks. ## Troubleshooting - **App creation answers 400 `slack_error`** — `auth.test` rejected the token; reinstall the app and re-copy the bot token. - **Slack's URL verification fails** — the events URL must be exactly the returned `setup.events_url` (per-app path); the signing secret in your credentials must match the app whose URL you pasted. - **Channel name collisions** — an existing channel with the generated name is reused rather than failing. --- # Discord guide > Discord inbound needs a long-lived gateway WebSocket — the capability serverless deployments can't self-host. Register your bot and our worker fleet holds the socket for you. ## Prerequisites - A Discord application with a **bot token** (Developer Portal → Bot). - A server you can add the bot to. - An AgentSky API token with write scope. ## Create the bot In the [Discord Developer Portal](https://discord.com/developers/applications): ![The Developer Portal's Applications page with the New Application button highlighted](https://platform.agentsky.dev/guides/channels/discord/portal-new-application.png) ① **New Application** → add a **Bot** → enable the **Message Content** intent → copy the bot token. ## Register the channel app The [developer console](https://platform.agentsky.dev/developer/apps/new?platform=discord) runs the same checklist and shows the invite link after create: ![The developer console's new-app checklist for Discord](https://platform.agentsky.dev/guides/channels/discord/register-app.png) Or over the API: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "discord", "label": "My bot", "credentials": {"bot_token": "..."}}' # -> setup: { "invite_url": "https://discord.com/oauth2/authorize?..." } ``` Registration proves the token and returns `setup.invite_url` — open it to add your bot to a server. Within about a minute the worker fleet opens your bot's gateway connection and starts mirroring events. ## Connect ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "discord", "app": "app_...", "destination": {"session": "sess-..."}}' ``` For the session destination above, Discord connects synchronously: the call creates a per-agent text channel in your server over REST, greets, binds, and returns `CONNECTED`. ## Capabilities | threads | reactions | markers | proactive | markdown | modals | ephemeral | streaming | |---|---|---|---|---|---|---|---| | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ## Troubleshooting - **Guild messages don't reach the agent** — mention the bot (or DM it). The gateway runs without the privileged MessageContent intent, so untagged guild messages carry no text. - **Connect fails after registration** — make sure the bot was actually added via `setup.invite_url` and has permission to create channels in the server. - **Events lag right after registration** — the worker fleet reconciles registered bots periodically; the first connection can take up to about a minute. --- # Microsoft Teams guide > Bring your own Azure Bot: register your app ID and password, paste the webhook URL into your Azure Bot's messaging endpoint, and your agent answers in Teams channels under your bot's identity. ## Prerequisites - An **Azure Bot** resource (or an Azure App Registration with Bot Framework service connected) and its **Application (client) ID** and a **client secret** (app password). For single-tenant bots, also the **Directory (tenant) ID**. - An AgentSky API token with write scope. ## Register the channel app Register in [Channel apps](https://platform.agentsky.dev/developer/apps/new?platform=teams) or through the API below. Teams must be enabled in the deployment; when disabled, existing apps remain readable and deletable, while new registration, credential rotation and connections are unavailable. Registration proves the credentials by obtaining a Bot Framework client-credentials token from Azure AD. For **multi-tenant bots** (no tenant ID), leave `app_tenant_id` out. For **single-tenant bots** (Azure Bot created with a specific tenant), include `app_tenant_id`: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "teams", "label": "My bot", "credentials": {"app_id": "…", "app_password": "…"}}' # -> { "id": "app_...", "setup": { "webhook_url": "https://.../webhooks/teams/app_..." } } ``` Single-tenant bots include `app_tenant_id`: ```bash -d '{"platform": "teams", "label": "My bot", "credentials": {"app_id": "…", "app_password": "…", "app_tenant_id": "…"}}' ``` ## Finish in the Azure portal After registration, paste `setup.webhook_url` into your Azure Bot's **Configuration → Messaging Endpoint**. Azure verifies it with a signed JWT on the first message — the webhook validates it automatically. Enable **Microsoft Teams** under the Azure Bot resource's **Channels** settings. ## Install the Teams app The Azure Bot resource alone does not install your bot in Teams. Create an app in [Developer Portal for Teams](https://dev.teams.microsoft.com/), complete its basic information, and add a **Bot** capability using the same Azure Application (client) ID. Select **Personal** for direct messages and **Team** for channel conversations; include **Group chat** only if you will use that scope. Upload a color PNG and an outline PNG icon. Validate and download the app package: its ZIP must contain `manifest.json` and both referenced icon files at the root. Check that the manifest's `bots[].botId` matches the registered Azure bot and its `scopes` include the places where you will install it. See Microsoft's [app package requirements](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/apps-package). For ordinary follow-ups without mentioning the bot, request resource-specific consent (RSC) in the app package. In Developer Portal, open **Configure → Permissions**: add `ChannelMessage.Read.Group` under Team permissions, and `ChatMessage.Read.Chat` under Chat/Meeting permissions if using group chats. The corresponding manifest fragment is: ```json { "webApplicationInfo": { "id": "", "resource": "https://RscBasedStoreApp" }, "authorization": { "permissions": { "resourceSpecific": [ { "name": "ChannelMessage.Read.Group", "type": "Application" }, { "name": "ChatMessage.Read.Chat", "type": "Application" } ] } } } ``` Include only permissions for the scopes you enable. These permissions let the provider deliver messages from the installed team or group chat, including messages without a mention; describe that access in your app information. The team or chat owner must consent during installation. After changing permissions, update the package version and upgrade or reinstall it in the target conversation so the new consent takes effect. See Microsoft's [RSC message delivery instructions](https://learn.microsoft.com/en-us/microsoftteams/platform/agents-in-teams/enable-receive-all-chat-messages). In Teams, use **Apps → Manage your apps → Upload an app → Upload a custom app**, select the ZIP, and install it for yourself or the chosen team. If custom upload is unavailable, your organization's Teams administrator must allow it or distribute the app through the organization's catalog. Complete installation before using the connection link below. ## Connect Create a pending connection for the session you want to reach: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "teams", "app": "app_...", "destination": {"session": "sess-..."}}' # -> { "id": "...", "status": "PENDING", "connect": { "code": "LINK:...", "expires_at": "...", "url": "https://teams.microsoft.com/..." } } ``` Open `connect.url` and send `connect.code` to the bot. To connect a channel instead, add the bot there and send the same code while mentioning the bot. Send only the code, with an optional bot mention; surrounding prose or mentions of other people do not claim a connection. The code expires after 15 minutes. The connection becomes `CONNECTED` only after the message is received, and binds that specific conversation to the session. Other conversations using the same bot need their own connections. Changing the default session sends a notice back to the conversation where you linked the connection. ## Capabilities | Capability | Availability | |---|---| | Threads | Supported | | Reactions | Supported | | Working/done/failed markers | Not supported | | Proactive messages | Supported | | Markdown | Supported | | Modals | Not supported | | Ephemeral messages | Not supported | | Streaming | Supported | The SDK supports Markdown messages, message edits, and reactions. Generic working/done/failed markers are not advertised for Teams. Proactive delivery targets an already connected conversation; it does not create an unsolicited chat with a new person. Streaming uses message edits through the channels API. ## Troubleshooting - **App creation answers 400 `invalid_credentials`** — Azure AD rejected the app ID or client secret; re-copy both from the Azure portal (app ID is the Application (client) ID, not the object ID). - **`platform_unavailable` on registration** — Azure AD was unreachable; retry in a moment. - **LINK works but ordinary follow-ups do not arrive** — verify the matching RSC permission in the installed package and the team/chat owner's consent. A local AgentSky subscription cannot grant Teams permission; without RSC, continue mentioning the bot until the installation is upgraded. - **Bot receives nothing** — confirm Microsoft Teams is enabled in Azure, the Teams package uses the same bot ID, and the app is installed in the intended personal or team scope. A direct-chat link does not replace installation. - **401 on the messaging endpoint** — the Bot Framework signature, issuer, app audience, or service URL failed validation. Check the Azure Bot app ID and messaging endpoint; direct unsigned requests are rejected. - **The LINK code does nothing** — send the exact code, mention the bot in a channel, and mint a new connection if the code expired. --- # WhatsApp guide > Bring your WhatsApp Business number through Meta's Cloud API: your WABA, your number, your brand on every message. ## Prerequisites - A Meta Business with a **WhatsApp Business Account (WABA)** and a registered phone number — you need the **phone number id** (not the number itself), a **System User access token** with WhatsApp permissions, and your Meta app's **app secret**. - An AgentSky API token with write scope. ## Register the channel app The [developer console](https://platform.agentsky.dev/developer/apps/new?platform=whatsapp) runs the checklist and shows the callback URL + verify token to paste into the Meta App Dashboard after create: ![The developer console's new-app checklist for WhatsApp Cloud API](https://platform.agentsky.dev/guides/channels/whatsapp/register-app.png) Or over the API: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "whatsapp", "label": "My number", "credentials": {"access_token": "...", "phone_number_id": "1234567890", "app_secret": "..."}}' # -> setup: { "webhook_url": "https://.../webhooks/whatsapp/app_...", "verify_token": "..." } ``` Registration proves the credentials by reading the phone number from the Graph API and stores its display number. Meta has no API for App Dashboard webhook config, so in **App Dashboard → WhatsApp → Configuration** set the callback URL to `setup.webhook_url`, the verify token to `setup.verify_token`, and subscribe to the **messages** field — Meta's verification challenge is answered automatically. ## Connect ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "whatsapp", "app": "app_...", "destination": {"session": "sess-..."}}' ``` The response's `connect.url` is a `wa.me` deep link that pre-fills a `LINK:` code; sending it to your number claims the connection. WhatsApp connections always require a channel app. ## Capabilities | threads | reactions | markers | proactive | markdown | modals | ephemeral | streaming | |---|---|---|---|---|---|---|---| | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | Outbound markdown is converted to WhatsApp's own formatting (`*bold*`, `_italic_`) and split to fit message limits. ## The customer service window WhatsApp is user-initiated by design: once a user messages you, Meta opens a limited customer-service window for free-form replies. Outside that window, business-initiated messages must use Meta-approved templates — the channels API posts free-form messages, so proactive sends outside the window will be rejected by Meta. Check Meta's current messaging policy for the exact rules. ## Troubleshooting - **App creation answers 400 `whatsapp_error`** — the Graph API rejected the token or phone number id; verify the System User token's WhatsApp permissions and the id (it is not the phone number). - **Meta's webhook verification fails** — URL and verify token must match `setup` exactly; re-run a credential rotation (`PATCH /apps/{id}`) to mint a fresh verify token if lost. - **Proactive sends fail** — you are likely outside the customer service window (see above). --- # Lark guide > Bring your own Lark (open.larksuite.com) or Feishu (open.feishu.cn) custom or App Store app. Configure its event URL and connect a direct chat or group. App Store bots also require an enabled installation in that organization. ## Funding and paused tasks Every workspace, including one with a single member, has its own funding context. If a channel request cannot run for lack of credit, the bot reports it here and provides that workspace’s Billing link. A task budget or member spending limit gets its own recovery instructions. After adding credit, check the conversation. Existing recovery policies remain in effect; if it is still blocked, send “continue” there to resume the same session. ## Connect your company Open **Channels → Connect Lark**. An administrator completes setup once; employees do not link individual AgentSky accounts. The company uses one shared workspace. Choose **Copy message** and paste it into Codex, Claude Code or another agent with terminal or browser access. The instructions include this workspace, required scopes, the current callback when available, and end-to-end verification. They contain no credentials. The agent should prefer the official SDK's [device registration](https://github.com/larksuite/node-sdk#app-registration): you confirm in Lark while it handles app creation. Availability varies; requested scopes must be verified, and some apps still need console configuration. For agents doing the setup: use the official Node SDK's `registerApp` where supported. For Lark international, set `domain` and `larkDomain` to `accounts.larksuite.com`; for Feishu use `accounts.feishu.cn`. Use `createOnly: true` for a new app and `addons.preset: false`, with the tenant scopes listed below and event `im.message.receive_v1`. Reuse an existing app when present. Addons can be unavailable or ignored: verify the actual scopes. Show its official verification URL as a QR code plus an **Open Lark** link for mobile. Keep the SDK registration process running until confirmation, expiry or cancellation. Never put a device code or app secret in the QR image. Transfer credentials directly through the signed-in setup form or authenticated API using a workspace-scoped credential; never paste them in chat or use an unscoped API token. If secure transfer is unavailable, let the administrator enter them in the form. Register the app in AgentSky before configuring its returned callback URL so the challenge can succeed. Eligible custom apps can use `client.application.v7.applicationConfig.patch` with `event.subscription_type: webhook`, the returned URL, and `event.add_events: [im.message.receive_v1]`. Also configure `callback: { callback_type: "webhook", request_url: "", add_callbacks: ["card.action.trigger"] }`. Preserve unrelated settings. Device-created apps may not support this API: handle the provider's response and complete those settings in the console when needed. Publish with administrator approval, then connect the workspace and verify a real bot reply. Saved credentials or a successful API response alone do not prove messaging works. Verify group conversations, attachments and authorized documents when those features are needed. For manual setup: 1. Create a custom app in your organization's Lark Developer Console and enable Bot. 2. Paste **App ID**, **App Secret** and **Verification Token** into AgentSky, then choose **Verify and continue**. Feishu and encryption options are under advanced settings. 3. Copy the permission scopes into Lark's batch-import dialog. Copy the returned event URL into Event Subscriptions, add `im.message.receive_v1`, and use the same URL under Callback Configuration with `card.action.trigger`. Publish the version and make it available to your organization. 4. Choose **Connect workspace**, open your bot and send the connection code once. Wait for its connection confirmation before assigning work. Members can continue a shared session or send `/new` to start another. Send `/status` for the current task's progress and session link, `/tasks` to see workspace tasks, or `/tasks N` to continue a listed task here. `/help` explains the controls and `/stop` interrupts the current task. Native topics have their own context. Progress, results and files are delivered in Lark; the session link provides the full process when needed. An ordinary message does not approve a pending human request; explicit request actions require the verified administrator who connected the bot. When native callbacks are enabled, single-choice questions have buttons. A click is acknowledged only after it is stored for processing; approval still depends on the original request and the clicking member's authority. Expired or replaced choices are refused. Multiple choices and unsupported controls remain text replies. If buttons do nothing, check **Callback Configuration**, enable `card.action.trigger` and publish the updated app. Event Subscriptions alone does not enable button callbacks. A custom app belongs to its own Lark organization. Another company configures its own app; an internal company bot is not a global click-to-authorize install. App Store installation is described separately below. ## Prerequisites Use a **custom or App Store app** in the [Lark Developer Console](https://open.larksuite.com/app) (or the Feishu console) with the **Bot** capability enabled. Grant the permissions below; each item explains what stops working without that permission: - `im:message`, `im:message.p2p_msg:readonly` — the bot receives nothing in a direct chat. - `im:message.group_at_msg:readonly` — the bot receives nothing when mentioned in a group. - `im:message.group_msg` — ordinary group follow-ups without a bot mention are not delivered; requires administrator approval. - `im:message:send_as_bot` — the bot cannot reply. - `im:message:update` — a streamed reply cannot be revised — it arrives as one block at the end. - `im:message:recall` — a reply that gets shorter leaves its stale tail behind. - `im:resource` — native images and other file attachments cannot be read or uploaded. - `docx:document:readonly` — authorized native Docs links cannot be read. Share the document with the bot first; this scope does not grant access to private documents. - `im:message.reactions:write_only` or `im:message` — add and remove working markers (OnIt → DONE). - `im:message.reactions:read` or `im:message:readonly` — recover interrupted removals when the marker’s handle is known. Normal confirmed add/remove works with write permission alone; uncertain operations may require administrator recovery, and an uncertain removal blocks re-adding that same marker even after cleanup. Automated status reactions belong to their current turn. An explicit reaction request that collides with one returns `409 reaction_owner_conflict`; choose a different emoji. The service removes the previous turn’s status before starting the next one, and leaves explicitly added reactions alone. Finishing a Lark turn requires the matching `marker_ref` from its reply instructions; a reply without that reference still sends, but does not clear the status. Permissions take effect when you publish an app version. Request the all-group-messages scope only when you want group conversations to continue without a mention; it permits receiving messages in every group containing the bot. With mention-only permission, mention the bot on every turn. See the [official SDK permission guidance](https://github.com/larksuite/node-sdk/blob/main/docs/channel.md#common-issues). - Its **App ID** and **App Secret** (Credentials & Basic Info) and the **Verification Token** from Event Subscriptions. If you set an **Encrypt Key** there, pass it too — deliveries then arrive encrypted and signed. - An AgentSky API token with write scope. ## Register the channel app The [developer console](https://platform.agentsky.dev/developer/apps/new?platform=lark) walks the same checklist, or over the API: ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "lark", "label": "My Lark app", "credentials": {"app_id": "cli_...", "app_secret": "...", "verification_token": "...", "domain": "lark"}}' # -> setup: { "events_url": "https://.../webhooks/lark/app_..." } ``` Custom-app registration mints a tenant token and reads the bot's identity — a wrong secret answers `400 lark_error`. The stored credential keys grow `bot_open_id` and `bot_name`. `domain` is `lark` (default) or `feishu`; `encrypt_key` is optional. **Configure events.** Eligible custom apps can use the official [application config API](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/application-v7/application-v7/application-config/patch) to configure event subscriptions. Check the app's eligibility and the actual response; device-created apps may require console setup. In the console, paste `setup.events_url` as the **Request URL** under Event Subscriptions (Lark verifies it with a challenge immediately), add the **`im.message.receive_v1`** event, and configure the same URL under **Callback Configuration** with **`card.action.trigger`**. Then publish an app version so the bot and callbacks are available to your organization. ## App Store applications Choose **App Store app** when the bot is installed in multiple organizations and set `app_mode: store`. Registration returns the events URL; it does not prove that an organization has installed the app. Send `app_ticket`, `app_open`, `app_status_change` and `app_uninstalled` events to that same URL, in addition to `im.message.receive_v1`. Messaging is refused until the tenant is enabled. Removing or stopping an installation prevents later sends and queued file reads. To migrate an existing installation, an administrator may register its current `app_ticket` and adopt the existing conversation with `adopt.tenant_key`. The service checks the provider's current application and chat state before binding that organization. A missing, disabled or mismatched installation is refused; registration alone is never a completed migration. ## Files Upload a file through the Files API, then send its `file_id` as a `{type: "file", file_id}` part on the existing thread-messages endpoint. The file must be visible in the connection's workspace. Lark receives the original bytes; incoming file/image resources become scoped Files identifiers in the signed event. Images are limited to 10 MiB each and other files to 30 MiB each; the combined message attachment budget is 32 MiB. Queued downloads recheck the current app credentials, so disabled apps cannot retrieve previously queued resources. If delivery reports a partial or unknown result, inspect the chat before sending again with a new idempotency key. ## Connect ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "lark", "app": "app_...", "destination": {"session": "sess-..."}}' # -> connect: { "url": "https://applink.larksuite.com/client/bot/open?appId=cli_...", "code": "LINK:…", "app_id": "cli_...", "expires_at": "…" } ``` `connect.url` opens the bot's chat inside Lark; the end user sends `connect.code` there and the chat claims the connection. To connect a **group**, add the bot to the group and send the code in the group with an @mention of the bot. The connection's thread is then `lark:{app}:GROUP:{chat_id}` and the service subscribes that conversation after the claim. With administrator-approved `im:message.group_msg` permission, subsequent messages need no mention; with mention-only permission, every follow-up must mention the bot. Unconnected, unmentioned groups are not routed to an agent. Codes expire after 15 minutes, and a code minted for your app is refused if sent to any other bot. The [hosted connect page](https://agentsky.dev/docs/channels/routing.md#the-hosted-connect-flow) renders this ceremony for you. ## Capabilities | threads | reactions | markers | proactive | markdown | modals | ephemeral | streaming | |---|---|---|---|---|---|---|---| | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | Markdown renders as an interactive card (Lark's `markdown` element: bold, italic, links, inline code, code blocks, lists, headings), which is also what lets replies be edited in place. A `raw` part is sent as a plain text message. Working markers use Lark's reaction set — **OnIt** while working, **DONE** on completion, **CrossMark** on failure. Thread ids are `lark:P2P:{chat_id}` / `lark:GROUP:{chat_id}` for the main conversation (with the app id as the second segment on your own app). Private replies carry `lark:P2P:{chat_id}:REPLY:{root_message_id}` for a flat reply or `lark:P2P:{chat_id}:TOPIC:{root_message_id}` for a Lark sub-thread; group replies use the corresponding `lark:GROUP:{chat_id}:REPLY:{root_message_id}` or `lark:GROUP:{chat_id}:TOPIC:{root_message_id}`. Use the matching chat-level channel id (`lark:P2P:{chat_id}` or `lark:GROUP:{chat_id}`) to send to the top-level conversation regardless of which reply-scoped thread id delivered the inbound message. ## Troubleshooting - **The console refuses the Request URL** — the app was registered with a different verification token than the console shows; rotate the app's credentials (`PATCH /v1/channels/apps/{id}`) with the current token and try again. - **Nothing arrives from a group** — the bot only receives group messages that @mention it unless the app holds the receive-all-group-messages scope; mention the bot, and check the event subscription includes `im.message.receive_v1`. - **Replies fail with a permission error** — grant `im:message:send_as_bot` (and the reactions scope for markers) and re-publish the app version; permissions take effect on publish. - **Encrypted deliveries answer 401** — the `encrypt_key` credential does not match the console's Encrypt Key. --- # Buzz guide ## Prepare the existing bot Ask your Buzz relay administrator for the bot's private key and current JSON auth tag. This integration uses the SkillBoss Buzz relay at buzz-relay-skillboss.fly.dev. Reuse the bot's existing identity so people do not have to invite a different bot. In [Developer apps](https://platform.agentsky.dev/developer/apps/new?platform=buzz), select Buzz and enter both credentials. API callers register platform buzz with credentials private_key (hex or nsec) and auth_tag (the JSON array as a string). Registration verifies relay access through an empty signed query without publishing or reading messages. It does not prove group membership or connect a conversation. Credentials are encrypted and never displayed after registration. ## Invite and connect Copy the returned bot_public_key and invite that identity to the intended group in your Buzz client. Create a connection with platform buzz, the registered app ID and a session or webhook destination. Open the returned connection instructions, mention the bot in the group and send the displayed LINK code. The page stays pending until a verified signed message claims that connection. If the code expires, request a new one. AgentSky maintains the relay connection; there is no provider webhook to configure. Existing integrations must import their verified members and groups before retiring their former receiver. Registering an app alone does not migrate identity bindings. ## Shared conversations and replies Buzz connects a shared group, not a private chat. Replies preserve Markdown and the original reply topic; a connected conversation can continue without repeating the bot mention. Pass the thread_id from signed message.received events unchanged when replying through the API. A group connection does not authenticate every participant to your product: webhook consumers must authorize the actual sender and shared audience. Native attachments, edits, reactions and streaming are not supported. Share accessible file links and send corrections as new messages. An uncertain delivery returns an error instead of automatically sending again; check the group before retrying to avoid duplicates. ## Recovery If registration is rejected, check that the private key and auth tag belong to the same currently authorized relay identity. A not-configured result requires the deployment administrator to enable relay forwarding. If the connection stays pending, confirm the bot is in the group and that the message mentions it with the complete unexpired code. Disconnect the conversation to stop routing new messages. Revoking the app also removes its relay subscription when the worker reconciles. --- # Google Chat guide ## Register your Chat app Enable the Google Chat API and configure the Chat app in your Google Cloud project. Create a service-account JSON key in the same project and copy the numeric project number. Enable direct messages and add your test users to the app's visibility settings. In [Developer apps](https://platform.agentsky.dev/developer/apps/new?platform=gchat), choose Google Chat and enter the JSON key and project number. API callers use platform gchat with credentials service_account_json (the JSON document as a string) and project_number. Registration obtains an app token from Google and makes a read-only Chat API call. It does not configure or publish your app in Google Workspace. ## Configure the callback Set the Chat app's HTTP endpoint and authentication audience to the returned setup.webhook_url. Project-number audiences are also accepted for classic Chat apps. Workspace Add-on requests must have the exact service account identity associated with the configured project. Both classic MESSAGE events and Workspace Add-on message payloads are supported. Pub/Sub subscriptions are not enabled by this integration. ## Connect and reply Create a connection with platform gchat, your registered app ID and a destination containing either a session ID or a webhook endpoint ID. Open the connect URL, copy its LINK code and send it to your app in Google Chat. The page opens Google Chat; locate the app by the display name you configured in Google Cloud. The connection remains PENDING until a verified message claims it. Use the thread_id from the signed message.received event unchanged when replying through the threads API. Direct messages stay in their space; replies in a space stay in the originating thread. Connecting a space connects that shared conversation, so a webhook consumer must apply its own member authorization. ## Failure and recovery Inbound native attachment bytes can be downloaded under the current registered credentials. Outbound file uploads are unavailable with the current Google Chat adapter; the API returns files_not_supported rather than silently dropping them. A rejected key or disabled Chat API returns invalid_credentials. A failed Google service returns platform_unavailable. A callback with a missing token, wrong audience or unexpected service account returns 401 and cannot claim a connection. Fix the configured audience before retrying. Expired LINK codes require a new connection. --- # iMessage guide > **Coming soon.** Bring-your-own iMessage is not yet accepting apps — `POST /v1/channels/apps` with `platform: "imessage"` answers `501 coming_soon` today. The guide below describes the flow that will open up, so you can prepare your Linq account. > iMessage rides the Linq partner API: bring your Linq account's number and end users text a real iMessage identity you own. ## Prerequisites - A **Linq** account with a provisioned iMessage number — its partner **API key**, the **phone number**, and a **webhook secret** for the subscription you'll point at us. - An AgentSky API token with write scope. ## Register the channel app ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "imessage", "label": "My number", "credentials": {"api_key": "...", "phone_number": "+15551234567", "webhook_secret": "..."}}' # -> setup: { "webhook_url": "https://.../webhooks/imessage/app_..." } ``` Linq has no webhook-registration API, so point your Linq webhook subscription at `setup.webhook_url`, signed with the same secret you stored — deliveries are verified per app. ## Connect ```bash curl -s -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "imessage", "app": "app_...", "destination": {"session": "sess-..."}}' ``` The response carries an `sms:` deep link and a `LINK:` code; texting the code to your number claims the connection. ## Capabilities | threads | reactions | markers | proactive | markdown | modals | ephemeral | streaming | |---|---|---|---|---|---|---|---| | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | Outbound markdown is flattened to plain text (link labels keep their targets in parens) and long messages are chunked rather than truncated. ## Troubleshooting - **Webhook deliveries answer 401** — the subscription's secret doesn't match the stored `webhook_secret`, or the signed timestamp is stale; Linq signatures are only accepted within a short freshness window. - **Replies land as a new conversation** — Linq's per-conversation chat ids can shift; the service re-learns them from every inbound automatically. - **The LINK code does nothing** — codes expire with the connect link (15 minutes); mint a new connection. --- # Email guide (AWS SES) > BYO email channel: connect your own AWS SES verified domain so your agent replies from your address. Inbound mail arrives via SNS; outbound is sent through SES v2, preserving observed reply headers. ## How it works 1. **SES receipt rule** — AWS stores incoming MIME mail in your S3 bucket and sends its object reference to an SNS topic. Legacy inline SNS receipts remain available for small mail. 2. **SNS HTTPS subscription** — you subscribe that topic to the returned webhook URL; when mail arrives the SNS notification is POSTed to `/webhooks/email/{appId}`. AgentSky confirms the signed subscription challenge after verifying its topic and AWS endpoint. 3. **Thread mapping** — the `In-Reply-To` / `References` headers are used to map replies to the same conversation thread. New conversations start a fresh thread. 4. **Outbound** — your agent's reply is sent via SES v2 `SendEmail` with the correct threading headers so email clients show the conversation inline. ## Credentials | Field | Description | |---|---| | **AWS Region** | The region where your SES identity is verified (e.g. `us-east-1`). | | **Access Key ID** | IAM access key with `ses:SendEmail` permission for your verified sender. | | **Secret Access Key** | The corresponding IAM secret. | | **From address** | The SES-verified sender address (e.g. `agent@yourdomain.com`). | | **SNS Topic ARN** | The ARN of the SNS topic your SES receipt rule publishes to. | For ordinary attachments, supply `s3_bucket`, `s3_prefix` and `s3_bucket_owner` together. The bucket must be in the configured region; the nonempty prefix ends in `/` (for example `inbound/astra/`); the owner is the bucket account’s 12-digit AWS ID. Use a dedicated mailbox prefix. Leaving all three absent preserves legacy inline SNS receiving. Optional credentials: `from_name` supplies the sender display name (Unicode and quoted names are MIME-encoded; control characters are rejected), while `from_address` remains the bare SES-verified mailbox. Omitting the name sends a bare address. `inbound_address` selects an additional receiving mailbox, `allow_subaddressing` accepts plus-tagged receiving addresses, and `reply_to_sender_only` ignores a sender's alternate MIME Reply-To. The two flags are strings `"true"` or `"false"`, defaulting to false. Once any connection exists, changing sender-only mode is rejected because it changes conversation scope; ordinary IAM credential rotation remains available. ## AWS setup ### 1 — Verify your sender domain or address Open the [AWS SES console](https://console.aws.amazon.com/ses/) and verify the domain or address you will send from. ### 2 — Create an SNS topic In **Amazon SNS → Topics**, create a Standard topic (e.g. `ses-inbound-agentsky`). Note the Topic ARN. ### 3 — Create an SES receipt rule In **SES → Email receiving → Rule sets**, match the exact receiving mailbox and enable spam and virus scanning. Use a **Deliver to S3** action with the configured bucket and prefix, and select the SNS topic from step 2 in that same action. SES stores raw MIME at the prefix followed by its message ID. Do not add a second SNS-content action for the same message. Grant the SES service permission to write only this prefix and publish to this topic, constrained by your source account and receipt-rule ARN. Use ordinary S3 server-side encryption; the SES action’s separate **Message encryption** option uses client-side encryption and is not supported by this reader. For SSE-KMS, the runtime identity also needs decrypt permission on that exact key. See [AWS receiving permissions](https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). Retain objects beyond your delivery/retry window using a lifecycle rule limited to the owned prefix. AgentSky never deletes receipt objects. Versioned buckets pin the original version; unversioned buckets require an unchanged ETag, and both verify the MIME digest when queued attachments resume. Missing or changed objects refuse delivery rather than substituting bytes. **Legacy mode:** an SNS action with UTF-8 content encoding still works when all three S3 fields are absent. It accepts at most **150 KB including headers**; AWS bounces larger mail. Base64 receipt actions are unsupported. ### 4 — Create an IAM user Create an IAM user with an inline policy granting: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ses:SendEmail", "Resource": "arn:aws:ses:REGION:ACCOUNT_ID:identity/YOUR_VERIFIED_DOMAIN" }, { "Effect": "Allow", "Action": ["s3:GetObject", "s3:GetObjectVersion"], "Resource": "arn:aws:s3:::YOUR_BUCKET/inbound/astra/*" } ] } ``` Replace the region, account ID, verified domain, bucket and exact prefix in the policy. Omit GetObjectVersion for an unversioned bucket and omit both S3 actions for legacy inline mode. The reader needs no ListBucket, PutObject or DeleteObject permission. Existing send-only keys need this narrow read grant before S3 receiving works. Generate an access key and copy both values. Read-only `ses:GetAccount` on Resource `*` is optional and lets AgentSky check whether account sending is enabled. ## Connect the app In the [developer console](https://platform.agentsky.dev/developer/apps/new?platform=email) fill in the five required fields and the three S3 fields for attachment receiving, then click **Create app**. AgentSky first checks the account with read-only `GetAccount`. If AWS denies that read, it authenticates the same credentials with STS `GetCallerIdentity` instead. It returns the SNS webhook URL without sending a test email. A successful connection verifies credentials; it does not probe the bucket or prove sending permission, sender identity, S3 read permission or receiving configuration: ``` setup: { "webhook_url": "" } ``` Add an **HTTPS subscription** on your SNS topic pointing to this URL. AgentSky auto-confirms it. ## Connect a conversation ```sh curl -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer " \ -d '{"platform": "email", "label": "Support inbox", "app": ""}' # -> { "id": "conn_...", "status": "CONNECTED" } ``` Bind an agent session: ```sh curl -X PUT https://api.agentsky.dev/v1/channels/connections//binding \ -H "Authorization: Bearer " \ -d '{"destination": {"session": "sess-..."}}' ``` ## Consumer replies and files Normal BYO apps retain one mailbox connection, `email:{appId}`. Sender-only apps isolate each administratively verified recipient at `email:{appId}:{recipient}`; use the existing admin-scoped `adopt` connection flow with a matching `author_id`. Threads in both modes use `email:{appId}:{recipient}:{16-character-root-hash}`. Never construct the retired five-segment wrapper. `POST /api/v1/channels/threads/{thread_id}/messages` accepts `email: { subject, reply_to }` beside `parts` and `idempotency_key`. These headers cannot change the conversation recipient. A replay returns the accepted receipt; an uncertain send is not automatically repeated. Incoming consumer events include `email.to`, `email.subject`, the observed RFC `email.message_id`, and `email.provider_message_id`. Inbound MIME attachments become scoped public Files with name, MIME type and byte size in `attachments`. S3 receiving supports raw MIME up to the service’s **40 MiB** bound, with at most **32 MiB total decoded attachment bytes**; AWS may impose a lower message limit. Oversized objects stop while streaming and do not create Files. SNS carries metadata only, and the busy-conversation queue stores pinned object/part references instead of attachment base64. Restoration rechecks the current ACTIVE app, owner, workspace, mailbox, topic and S3 namespace before reading. The existing queue expires logically after 90 seconds; S3 storage does not extend that queue window. Legacy inline SNS remains limited to **150 KB including headers and MIME encoding**. Outbound `file` parts resolve authorized Files and send real MIME attachments with their bytes and names. Files must total at most 32 MiB before encoding, and the full SES v2 MIME message must fit 40 MiB after encoding. Cross-workspace files are refused before sending. ## Troubleshooting - **S3 receipt returns 503** — check the exact key exists, the current IAM key can read it (and its version), the bucket owner matches, and any KMS permission is present. Retryable reads never fall back to another bucket or credentials. Registration alone does not check this. - **S3 receipt returns 403** — compare the configured bucket/prefix/topic against the signed SES action. The reader accepts only prefix + SES message ID in that owned namespace. - **SNS sends Confirmation but webhook returns 403** — the TopicArn in the SNS notification doesn't match the one stored in the app credentials; verify the app was registered with the correct ARN. - **Replies start a new thread** — inspect the received `In-Reply-To` / `References` headers and the 30-day state lifetime. Outbound replies reference message IDs actually received; SES response IDs are tracked as aliases rather than fabricated email headers. - **Mail not processed** — the signed SES receipt must have a passing DMARC verdict and name the configured receiving address. MIME authentication headers alone are not trusted. - **Spam or virus verdict FAIL** — inbound messages that fail SES spam or virus checks are silently dropped. Check your SES receipt rule's spam action if you need to handle these differently. --- # LINE guide ## Register your Official Account Enable the Messaging API for your LINE Official Account. In the [LINE Developers Console](https://developers.line.biz/console/), open the associated channel, copy its channel access token from Messaging API settings and its channel secret from Basic settings. In [Developer apps](https://platform.agentsky.dev/developer/apps/new?platform=line), select LINE and enter both credentials. API callers register platform line with credentials channel_access_token and channel_secret. Registration makes a read-only bot-info request to verify the token and obtain the Official Account's identity. It does not change the current webhook or prove that the supplied channel secret can receive events. ## Configure the webhook Copy setup.webhook_url to Messaging API → Webhook settings and enable Use webhook. Disable automatic replies that would duplicate the agent's response. AgentSky checks the raw webhook signature with the channel secret and requires the event destination to match the registered bot. If this Official Account already serves another integration, import and verify its existing conversations before switching its webhook. Registration alone does not migrate users or replace the previous callback. ## Connect and talk Add the Official Account as a friend. Create a connection using platform line, the registered app ID and a session or webhook destination. Open the returned connect URL and send its LINK code in LINE. The open-chat shortcut is intended for the LINE mobile app; on desktop, copy the code into your chat manually. A verified incoming message changes the connection from PENDING to CONNECTED. Use thread_id from signed message.received events unchanged for replies. Direct messages, groups and rooms keep separate app-scoped conversations. A shared conversation does not establish every participant's identity or authorization in your product; the webhook consumer must check the actual sender. ## Message behavior and recovery Text replies preserve links and split long content to fit LINE's message limits. Incoming images, audio, video and files are downloaded through the authenticated LINE content API. Native file sending and edits of sent messages are not supported; integrations should present accessible file links and send corrections as new messages. Push delivery is subject to the Official Account's messaging quota. A rejected token fails registration. A mismatched secret or destination rejects the webhook without connecting a conversation. If the open-chat shortcut fails, use the displayed code in LINE. An expired code requires a new connection. After an uncertain send, check the conversation before retrying to avoid duplicate messages. --- # X (Twitter) channel guide > **X Activity access required.** Confirm webhook access, OAuth scopes, and current usage costs in your X developer account before connecting. See [X Activity documentation](https://docs.x.com/x-api/activity/introduction). ## Prerequisites - An X developer account at [developer.x.com](https://developer.x.com) - An X app with [X Activity API](https://docs.x.com/x-api/activity/introduction) webhook access; confirm current access and usage costs in your developer account. - OAuth 2.0 User Authentication configured with `dm.read`, `dm.write`, `tweet.read`, `tweet.write`, `users.read` scopes - **Consumer secret** from your app's "Keys and tokens" page - Either a **user access token** (OAuth 2.0 Bearer), OR a **client ID + refresh token** for managed refresh — `client_secret` is optional (required only for confidential OAuth 2.0 apps); include `offline.access` scope when generating the refresh token ## 1. Create an X app 1. Go to [developer.x.com/en/portal/dashboard](https://developer.x.com/en/portal/dashboard) and create a new project and app. 2. Under "User authentication settings", enable OAuth 2.0 and set the callback URL (the HTTPS redirect URL used by your OAuth authorization flow; this is separate from the webhook URL). 3. From "Keys and tokens", copy the **consumer secret** (also called "API secret key"). 4. Generate a **user access token** (OAuth 2.0 Bearer) with the scopes above, OR configure OAuth 2.0 PKCE and note the **client ID** and a **refresh token** with `offline.access` scope (the **client secret** is optional — only required for confidential apps). ## 2. Register a channel app ```bash curl -s -X POST "https://api.agentsky.dev/v1/channels/apps" \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{ "platform": "x", "label": "My X bot", "credentials": { "consumer_secret": "", "user_access_token": "" } }' # -> { # "id": "app_...", # "setup": { # "webhook_url": "https://.../webhooks/x/app_...", # "console_url": "https://console.x.com", # "required_subscriptions": "post.mention.create,dm.received,dm.sent" # } # } ``` The API validates your token against `GET /2/users/me` and stores the verified user ID and username. If you supply `client_id` + `refresh_token` instead of `user_access_token`, the adapter manages token refresh automatically. If X rejects the credentials, token refresh becomes uncertain, or the provider is temporarily unavailable after the app is saved, the API returns HTTP 202 with `status: PENDING_VERIFICATION` and the saved app ID. Messages stay disabled. Open the app and choose **Retry verification**, or send `PATCH /channels/apps/{id}` with `{"retry_registration":true}`. This retries the saved credentials; do not create another app to reuse an already-consumed refresh token. If the refresh result is uncertain, replace the credentials with fresh OAuth credentials. Unexpected internal failures return an error instead of being represented as recoverable verification state. ## 3. Register the webhook Register the URL returned in `setup.webhook_url` using X’s [webhook setup guide](https://docs.x.com/x-api/webhooks/quickstart). This channel API returns the setup URL; it does not create the X webhook or subscription for you: 1. Open your X developer account and follow the current X Activity webhook setup. 2. Register the webhook URL under your app. 3. X sends a CRC GET challenge to the URL — the service responds automatically using your consumer secret. 4. Create a subscription for your bot account: subscribe to `post.mention.create`, `dm.received`, and `dm.sent`. Alternatively use [console.x.com](https://console.x.com) if it is available in your account. ## 4. Connect a session ```bash # Connect the channel app to an agent session curl -s -X POST "https://api.agentsky.dev/v1/channels/connections" \ -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \ -d '{"platform": "x", "app": "app_...", "destination": {"session": "sess-..."}}' ``` The response is `PENDING`, with `connect.url`, `connect.code` and an expiry. Open the hosted URL, then use its X button to send the exact code to the bot and connect that DM. The page also shows the exact `@YourBot LINK:...` post for connecting the account’s public-mention surface. Avoid surrounding prose. A valid claim changes the connection to `CONNECTED` and confirms the selected agent. The page polls that status and, when the request supplied `callback_url`, redirects there with the connection ID, status, and original metadata. The code expires after 15 minutes. DM connections are separate from the public-mention connection. Replies use the app’s credentials and target the corresponding DM or post conversation. A later agent switch sends a notice to the place where the connection was linked. ## Thread model - **Public mentions:** one default agent handles the app's public-mention surface; each root post conversation retains its own reply thread. Long replies are split into consecutive posts within X's weighted limit, preserving URLs and graphemes. - **Direct messages:** each correspondent has a separate connection. The adapter supports legacy `dm.received`/`dm.sent` events; encrypted XChat `chat.*` events are a different protocol and are not supported by this adapter. - **Uncertain or partial sends:** the API reports accepted post IDs when available. Check the conversation before retrying, to avoid publishing duplicates. ## Troubleshooting - **CRC challenge fails** — confirm the consumer secret is the exact string from "Keys and tokens → API secret key"; do not confuse it with the client secret. - **Events not arriving** — check the webhook subscription status in the X Developer Portal; the subscription must be active and the URL must match exactly. - **Token expired** — user access tokens expire in approximately 2 hours. Switch to `client_id` + `refresh_token` for automatic refresh, or rotate the `user_access_token` credential via `PATCH /v1/channels/apps/{id}`. - **Access and rate limits** — check the current [X Activity documentation](https://docs.x.com/x-api/activity/introduction) and your developer account. This integration does not purchase access or credits. --- # API reference — one origin, one namespace Everything lives under `https://api.agentsky.dev/v1` with a Bearer token. Management is request/response; conversation is a 202 send plus one standing SSE stream. Machine-readable spec (generated from the same schemas the routes validate with): https://api.agentsky.dev/v1/openapi.json. ## Authentication Every request sends `Authorization: Bearer ast_…`. Tokens are minted in Developer → API keys or by `sky 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: ` to act inside a non-personal universe. ## Errors Every non-2xx body is `{ "error": { "code", "message" } }`: | HTTP | code | When | |------|------|------| | 400 | `invalid_request` | malformed JSON / validation failure | | 400 | `invalid_memory` | invalid memory operation or memory limit reached | | 401 | `invalid_token` | missing, unknown, revoked, or expired token | | 402 | `insufficient_credits` | the spend gate blocked the turn | | 403 | `insufficient_scope` · `universe_mismatch` · `forbidden` | scope too low; X-Universe conflicts with a scoped token; agent has no API binding | | 404 | `not_found` | unknown agent/universe (also masks unauthorized slugs) | | 409 | `conflict` | version conflict | | 403 | `personal_memory_only` | caller or universe is not eligible for personal memory | | 409 | `memory_conflict` | memory changed; read the current version before writing | | 400 | `not_downloadable` | `GET /files/{file_id}/content` on an upload — only session files download | | 400 | `file_is_session_resource` | `DELETE /files/{file_id}` on a copy that backs a live session resource — delete the resource instead | | 400 | `too_many_resources` | more than 500 resources or 5 repositories on one session | | 400 | `egress_header_conflict` | two secrets, or two repository tokens, set one header on one host with different values | | 413 | `file_too_large` | a single-request file upload over 32 MiB (use `/v1/files/uploads`), or a resumable one over 50 GiB | | 422 | `invalid_spec` | valid JSON, invalid domain rules (e.g. llm not allowed for type) | | 429 | `rate_limited` | 100,000 requests/min per token exceeded — honor Retry-After | ## Billing ### GET /billing Read credit balance and card availability (scope: read) Returns the caller's credit balance and whether a card is on file. No card details are returned. Responses: - `200` — OK - `balance_usd` (number, required) - `card_on_file` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing' \ -H "Authorization: Bearer ast_..." ``` ### POST /billing/card-setup Start saving a payment card (scope: write) Returns a hosted card form URL to open in a browser. Saving the card does not charge it; auto top-up can charge it when a later session starts below the configured threshold. No request body is required. Responses: - `200` — OK - `url` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/billing/card-setup \ -H "Authorization: Bearer ast_..." ``` ### GET /billing/auto-topup Read auto top-up settings (scope: read) Whether the caller's saved card is charged when their balance falls under threshold_usd, and for how much credit. opted_out is true once the person turned it off, so a later top-up does not turn it back on. failure is the last charge that did not go through while it is on; paused when repeated failures stopped it. Responses: - `200` — OK - `enabled` (boolean, required) - `opted_out` (boolean, required) - `threshold_usd` (number, required) - `amount_usd` (number, required) - `card_on_file` (boolean, required) - `failure` (object, required) - `reason` (string) - `paused` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing/auto-topup' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /billing/auto-topup Change auto top-up settings (scope: write) Omitted fields keep their value. threshold_usd is 5, 10 or 20; amount_usd is 10, 20, 50 or 100. Turning it on needs a saved card (402 no_payment_method — add one with POST /billing/card-setup) and agrees to the card processing fee each charge adds; turning it off is remembered. A workspace key or a restricted credential cannot change it. Body (application/json): - `enabled` (boolean) - `threshold_usd` (enum) — 5 · 10 · 20 - `amount_usd` (enum) — 10 · 20 · 50 · 100 Responses: - `200` — Saved - `enabled` (boolean, required) - `threshold_usd` (number, required) - `amount_usd` (number, required) - `failure` (object, required) - `reason` (string) - `paused` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/billing/auto-topup \ -H "Authorization: Bearer ast_..." ``` ### POST /billing/topups Buy credit for your own balance with your saved card (scope: write) Charges the caller's saved card the credit plus the card processing fee and adds the credit to their balance. amount is 1000, 2000, 5000 or 10000 cents. request_id makes a repeat of the same request charge once (200 instead of 201). Auto top-up is not changed. 409 card_required without a saved card (POST /billing/card-setup adds one), 402 card_declined, 503 payments_unavailable where card payments are not set up. A workspace key or a restricted credential cannot buy credit. Body (application/json): - `amount` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `request_id` (string, required) — max length 100 Responses: - `201` — Bought - `charged` (boolean) - `balance` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/billing/topups \ -H "Authorization: Bearer ast_..." ``` ### POST /billing/credit-invites/claim Claim a credit invite link for your own balance (scope: write) Redeems an operator's credit invite link (agentsky.dev/i/) the way the invite page's Claim button does; code is the part of the link. Not a workspace invitation: those are POST /invites/{token}. 201 granted, or 200 already_claimed when this account redeemed the link before (nothing is funded twice). 404 not_found for an unknown code; 410 invite_expired, invite_disabled or invite_used; 403 invite_not_yours for a personal link issued to another address, 403 account_ineligible for an unverified or banned account. Rate-limited per account (429 rate_limited with Retry-After). A workspace key or a restricted credential cannot claim credit. Body (application/json): - `code` (string, required) — max length 200 Responses: - `200` — Already claimed by this account - `status` (enum, required) — "granted" · "already_claimed" - `amount_usd` (number, required) - `201` — Granted - `status` (enum, required) — "granted" · "already_claimed" - `amount_usd` (number, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/billing/credit-invites/claim \ -H "Authorization: Bearer ast_..." ``` ### GET /billing/usage Account-wide usage across sessions and API keys (scope: read) What the caller spent over a range of UTC days, from both lanes that debit their balance: their sessions (model, capability, connector and runtime calls) and their developer API keys. totals.bySource splits the totals by lane; groups break them down by group_by. A group's key is null where the dimension does not apply to its lane, so the groups always sum to the totals. Per-session detail is GET /sessions/{id}/usage. Query parameters: - `from` (string) — First UTC day included, YYYY-MM-DD; defaults to 29 days before today - `to` (string) — First UTC day excluded, YYYY-MM-DD; defaults to tomorrow. A range spans at most 366 days - `source` (string) — Only sessions, or only developer API keys; both when omitted - `api_key_id` (string) — Only this API key's usage - `group_by` (string) Responses: - `200` — OK - `range` (object, required) - `from` (string) - `to` (string) - `groupBy` (enum, required) — "day" · "kind" · "session" · "api_key" - `totals` (object, required) - `calls` (integer) - `inputTokens` (integer) - `outputTokens` (integer) - `cachedInputTokens` (integer) - `billedCostUsd` (number) - `bySource` (object) - `session` (object) - `calls` (integer) - `inputTokens` (integer) - `outputTokens` (integer) - `cachedInputTokens` (integer) - `billedCostUsd` (number) - `api_key` (object) - `calls` (integer) - `inputTokens` (integer) - `outputTokens` (integer) - `cachedInputTokens` (integer) - `billedCostUsd` (number) - `groups` (object[], required) - `key` (string) - `label` (string) - `source` (enum) — "session" · "api_key" - `calls` (integer) - `inputTokens` (integer) - `outputTokens` (integer) - `cachedInputTokens` (integer) - `billedCostUsd` (number) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing/usage' \ -H "Authorization: Bearer ast_..." ``` ### GET /billing/usage/export Download every usage record in a range as CSV or JSONL (scope: read) One row per session call and one per API key, UTC day and kind, streamed. Columns: source, at, sessionId, sessionName, apiKeyId, apiKeyName, kind, model, provider, tool, calls, inputTokens, outputTokens, cachedInputTokens, cacheCreationTokens, listCostUsd, billedCostUsd, coveredBy (subscription when a connected plan paid), requestId. The same file Billing's export downloads. Query parameters: - `from` (string) — First UTC day included, YYYY-MM-DD; defaults to 29 days before today - `to` (string) — First UTC day excluded, YYYY-MM-DD; defaults to tomorrow. A range spans at most 366 days - `source` (string) — Only sessions, or only developer API keys; both when omitted - `api_key_id` (string) — Only this API key's usage - `format` (string) Responses: - `200` — The records - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing/usage/export' \ -H "Authorization: Bearer ast_..." ``` ### GET /billing/usage/requests Per-request history of your developer API keys (scope: read) One row per API gateway request the caller's developer API keys made in a range of UTC days, newest first, a page at a time: model, endpoint, tokens, list and billed cost, HTTP status and latency. Pass nextCursor back as cursor for the next page; it is null on the last page. Session usage is not included (source=session returns no rows). The same rows as Settings › API usage. 503 unavailable when the request log cannot be read; retry. Query parameters: - `from` (string) — First UTC day included, YYYY-MM-DD; defaults to 29 days before today - `to` (string) — First UTC day excluded, YYYY-MM-DD; defaults to tomorrow. A range spans at most 366 days - `api_key_id` (string) — Only this API key's requests - `cursor` (string) — nextCursor from the previous page Responses: - `200` — OK - `rows` (object[], required) - `id` (string) - `requestId` (string) - `apiKeyId` (string) - `apiKeyName` (string) - `at` (string) - `model` (string) - `endpoint` (string) - `inputTokens` (integer) - `outputTokens` (integer) - `cachedInputTokens` (integer) - `cacheCreationTokens` (integer) - `totalTokens` (integer) - `listCostUsd` (number) - `billedCostUsd` (number) - `httpStatus` (integer) - `metered` (string) - `latencyMs` (integer) - `nextCursor` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing/usage/requests' \ -H "Authorization: Bearer ast_..." ``` ### GET /billing/coding-plan Read your coding plan (scope: read) Whether a paid month of the coding plan covers now (every API gateway call is then billed at cost), its monthly credit, when the month ends, whether it renews, why the last charge failed, and whether new members can join (enrollment open, full or closed). Responses: - `200` — OK - `active` (boolean, required) — A paid month covers now - `monthly_credit_usd` (number, required) - `current_period_end` (string, required) - `renews` (boolean, required) — The plan charges again at current_period_end - `charge_failure` (string, required) — Why the last charge failed, worded for the cardholder - `enrollment` (enum, required) — "open" · "full" · "closed" - `amounts_usd` (number[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing/coding-plan' \ -H "Authorization: Bearer ast_..." ``` ### POST /billing/coding-plan Join the coding plan, or change your monthly amount (scope: write) Joining charges the caller's saved card amount_usd plus the card processing fee and adds amount_usd as purchased credit; the plan renews monthly on the same card. A current member's call changes the amount from the next renewal, charges nothing, and undoes a pending cancellation. request_id makes a repeat of the same join charge once. 409 card_required without a saved card (POST /billing/card-setup adds one), 402 card_declined, 403 enrollment_closed, 409 enrollment_full, 409 charge_in_progress, 503 payments_unavailable. A workspace key or a restricted credential cannot change the plan. Body (application/json): - `amount_usd` (enum, required) — 10 · 40 - `request_id` (string, required) — max length 100 Responses: - `200` — The plan after the change - `active` (boolean, required) — A paid month covers now - `monthly_credit_usd` (number, required) - `current_period_end` (string, required) - `renews` (boolean, required) — The plan charges again at current_period_end - `charge_failure` (string, required) — Why the last charge failed, worded for the cardholder - `enrollment` (enum, required) — "open" · "full" · "closed" - `amounts_usd` (number[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/billing/coding-plan \ -H "Authorization: Bearer ast_..." ``` ### DELETE /billing/coding-plan Stop your coding plan renewing (scope: write) The plan does not charge again; the paid month and its credit stay. Join again with POST to undo it. A workspace key or a restricted credential cannot change the plan. Responses: - `200` — The plan after the change - `active` (boolean, required) — A paid month covers now - `monthly_credit_usd` (number, required) - `current_period_end` (string, required) - `renews` (boolean, required) — The plan charges again at current_period_end - `charge_failure` (string, required) — Why the last charge failed, worded for the cardholder - `enrollment` (enum, required) — "open" · "full" · "closed" - `amounts_usd` (number[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/billing/coding-plan \ -H "Authorization: Bearer ast_..." ``` ### GET /billing/referral-links Your customer discount links and what they earned (scope: read) For an account on an eligible platform discount plan: the plan links are priced from (source_plan.multiplier is the lowest share of public price a customer can be given), the active SKUs with your own price for each, every link you created with its customer price, redemptions and status, and your earnings per referred customer. 403 not_eligible for any other account. Responses: - `200` — OK - `source_plan` (object, required) - `id` (string) - `name` (string) - `multiplier` (number) - `skus` (object[], required) - `id` (string) - `name` (string) - `kind` (string) - `source_multiplier_bps` (integer) - `second_list_usd` (number) - `source_second_usd_per_unit` (number) - `links` (object[], required) - `id` (string) - `url` (string) - `created_at` (string) - `customer_multiplier` (number) — Share of public price the customer pays - `custom_price_count` (integer) — SKUs priced differently from your own price - `prices` (object[]) - `sku_id` (string) - `customer_multiplier_bps` (integer) - `customer_second_usd_per_unit` (string) - `audience_email` (string) - `email_status` (string) - `max_redemptions` (integer) - `redeemed_count` (integer) - `expires_at` (string) - `disabled_at` (string) - `earnings` (object[], required) - `enrollment_id` (string) - `amount_usd` (number) - `last_earned_at` (string) - `referred_user` (object) - `name` (string) - `email` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/billing/referral-links' \ -H "Authorization: Bearer ast_..." ``` ### POST /billing/referral-links Create a customer discount link (scope: write) Pass exactly one of customer_multiplier (one share of public price for every model, 0.01 to 100) or sku_prices (a multiplier, or for SECOND-only SKUs an exact second_usd_per_unit, per SKU). No price may be below your own (400 below_floor). With audience_email the link is emailed to that customer and redeems once; email_status says whether the email was sent. max_redemptions defaults to 100 and expires_in_days to 30. 403 not_eligible without an eligible discount plan, 429 rate_limited after 20 emailed links in an hour, 503 unavailable while referral pricing is paused. A workspace key or a restricted credential cannot create links. Body (application/json): - `customer_multiplier` (number) - `sku_prices` (object[]) - `sku_id` (string, required) - `multiplier` (number) - `second_usd_per_unit` (number) - `max_redemptions` (integer) - `expires_in_days` (integer) - `audience_email` (string) Responses: - `201` — Created - `url` (string, required) - `email_status` (enum, required) — "sent" · "suppressed" · "uncertain" · null - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/billing/referral-links \ -H "Authorization: Bearer ast_..." ``` ### POST /billing/referral-links/{id}/disable Disable one of your customer discount links (scope: write) The link can no longer be redeemed; customers already enrolled keep their price. 404 not_found when the link is not yours or is already disabled. Path parameters: - `id` (string, required) Responses: - `200` — Disabled - `id` (string, required) - `disabled` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/billing/referral-links/sess_cmtb7q2j/disable \ -H "Authorization: Bearer ast_..." ``` ## Credit campaigns ### GET /credit-campaigns List live and recent credit campaigns (scope: admin) Responses: - `200` — Campaign result - `campaigns` (string[], required) - `400` — Invalid request - `401` — Authentication required - `403` — Admin token scope and administrator allowlist membership required - `404` — Unknown campaign - `503` — Campaign migrations required - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/credit-campaigns' \ -H "Authorization: Bearer ast_..." ``` ### POST /credit-campaigns Issue a public credit link or personal email invitations (scope: admin) Only allowlisted administrators can use these operations. Use one UUID requestId per campaign and reuse the identical body after timeouts. Personal emails are deduplicated and each gets a distinct, email-bound single-use code. Existing verified, unbanned recipients are credited immediately. sendEmail explicitly authorizes product email. Code creation is atomic; delivery/credit failures are per recipient. Retry the same request for retry_needed credit results; never change requestId just to retry. Email status sent means SES accepted, not inbox delivery. Sending/uncertain attempts are not automatically resent; use the returned personal link for manual delivery. Responses contain bearer codes: keep private. A different body with the same requestId returns 409. Body (application/json): - `requestId` (string, required) — pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$ - `amountUsd` (number, required) - `expiresInDays` (integer, required) - `note` (string, required) — max length 200 - `audience` (object, required) - option 1 (object) - `kind` ("anyone", required) - `slots` (integer, required) - option 2 (object) - `kind` ("recipients", required) - `emails` (string[], required) — pattern ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$ - `sendEmail` (boolean) — default: false Responses: - `201` — Campaign result - `campaignId` (string, required) - `recipients` (object[], required) - `id` (string, required) - `email` (string | null, required) - `code` (string, required) - `url` (string, required) - `claim` (string, required) - `emailStatus` (string, required) - `notifiedAt` (string | null, required) - `400` — Invalid request - `401` — Authentication required - `403` — Admin token scope and administrator allowlist membership required - `404` — Unknown campaign - `409` — requestId reused with different settings - `415` — Content-Type must be application/json - `503` — Campaign migrations required - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/credit-campaigns \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"requestId":"40a9ae2a-4ea5-4a50-987e-d8b3c99c96f0","amountUsd":20,"expiresInDays":30,"note":"Launch invitations","audience":{"kind":"recipients","emails":["recipient@example.com"]},"sendEmail":true}' ``` ### GET /credit-campaigns/{id} Read claim, email and recipient usage results (scope: admin) Returns grantedUsd, claimed, activeRecipients, agentUsageUsd and individual link states. Usage is agent/runtime ledger spend since first claim, excludes API gateway and is not attribution to the gifted credit pool. Path parameters: - `id` (string, required) Responses: - `200` — Campaign result - `campaignId` (string, required) - `note` (string, required) - `amountUsd` (number, required) - `maxRedemptions` (integer, required) - `claimed` (integer, required) - `grantedUsd` (number, required) - `activeRecipients` (integer, required) - `agentUsageUsd` (number, required) - `usageBasis` (string, required) - `links` (object[], required) - `id` (string, required) - `email` (string | null, required) - `state` (enum, required) — "active" · "disabled" · "expired" · "exhausted" - `claimed` (integer, required) - `emailStatus` (string, required) - `expiresAt` (string, required) - `notifiedAt` (string | null, required) - `url` (string | null, required) - `400` — Invalid request - `401` — Authentication required - `403` — Admin token scope and administrator allowlist membership required - `404` — Unknown campaign - `503` — Campaign migrations required - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/credit-campaigns/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /credit-campaigns/{id} Disable all unclaimed campaign codes (scope: admin) Already granted credit stays in recipients' accounts. Path parameters: - `id` (string, required) Responses: - `200` — Campaign result - `disabled` (integer, required) - `400` — Invalid request - `401` — Authentication required - `403` — Admin token scope and administrator allowlist membership required - `404` — Unknown campaign - `503` — Campaign migrations required - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/credit-campaigns/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ## Memory stores ### GET /workspace-context What a session loads from the workspace's files (scope: read) The workspace's context store, the files every session the caller starts loads (AGENTS.md, MEMORY.md, people/INDEX.md, projects/INDEX.md and the caller's own members/.md) with their sizes and limits, and the folders read on demand. What one session actually loaded is its `contextFiles`. Responses: - `200` — The workspace context - `workspace_context` (object, required) — The caller's workspace files as a session loads them. A session the caller starts loads the always_loaded files into its instructions (each cut at limit_chars, with a marker saying where to read the rest); everything else in the store is read on demand. Read and write the files themselves through /memory_stores/{store_id}/memories. - `object` (enum, required) — "workspace_context" - `enabled` (boolean, required) — Whether this workspace keeps context files; where it does, the store is created the first time it is asked for - `memory_store_id` (string | null, required) — The workspace's store (memstore_…); null where it has none - `loads_by_default` (boolean, required) — Whether new sessions get the store (with write access) and load its always-loaded files - `mount_path` (string, required) — Where a session finds the store's files - `member_handle` (string | null, required) — The caller's page name under /members/ - `upkeep_routine_id` (string | null, required) — The workspace's daily upkeep routine (rtn_…), which folds the daily notes into MEMORY.md and keeps each member's "How we work together"; pause or archive it to turn it off. Null where the workspace has none - `always_loaded` (object[], required) - `path` (string, required) - `purpose` (string, required) - `limit_chars` (integer, required) — Characters of the file a session loads at most - `exists` (boolean, required) - `memory_id` (string | null, required) — mem_…, when the file exists - `content_bytes` (integer | null, required) - `on_demand` (object[], required) - `path_prefix` (string, required) - `purpose` (string, required) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-context' \ -H "Authorization: Bearer ast_..." ``` ### GET /memory_stores List memory stores (scope: read) Query parameters: - `include_archived` (boolean) - `after` (string) - `limit` (integer) Responses: - `200` — List of memory stores - `memory_stores` (object[], required) - `id` (string, required) — memstore_… - `object` (enum, required) — "memory_store" - `name` (string, required) - `description` (string, required) - `attach_by_default` (enum, required) — "none" · "read_only" - `created_at` (string, required) - `updated_at` (string, required) - `archived_at` (string | null, required) - `has_more` (boolean, required) - `next_cursor` (string) — Opaque cursor; pass as `after` on the next request. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory_stores' \ -H "Authorization: Bearer ast_..." ``` ### POST /memory_stores Create a memory store (scope: write) Body (application/json): - `name` (string, required) — max length 255 - `description` (string) — max length 4096 - `attach_by_default` (enum) — "none" · "read_only" Responses: - `201` — Created memory store - `memory_store` (object, required) - `id` (string, required) — memstore_… - `object` (enum, required) — "memory_store" - `name` (string, required) - `description` (string, required) - `attach_by_default` (enum, required) — "none" · "read_only" - `created_at` (string, required) - `updated_at` (string, required) - `archived_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory_stores \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"name":"team-notes","description":"Shared team knowledge base"}' ``` ### GET /memory_stores/{store_id} Get a memory store (scope: read) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Responses: - `200` — Memory store - `memory_store` (object, required) - `id` (string, required) — memstore_… - `object` (enum, required) — "memory_store" - `name` (string, required) - `description` (string, required) - `attach_by_default` (enum, required) — "none" · "read_only" - `created_at` (string, required) - `updated_at` (string, required) - `archived_at` (string | null, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x' \ -H "Authorization: Bearer ast_..." ``` ### POST /memory_stores/{store_id} Update a memory store (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Body (application/json): - `name` (string) — max length 255 - `description` (string) — max length 4096 - `attach_by_default` (enum) — "none" · "read_only" Responses: - `200` — Updated memory store - `memory_store` (object, required) - `id` (string, required) — memstore_… - `object` (enum, required) — "memory_store" - `name` (string, required) - `description` (string, required) - `attach_by_default` (enum, required) — "none" · "read_only" - `created_at` (string, required) - `updated_at` (string, required) - `archived_at` (string | null, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"name":"team-notes-v2","description":"Updated description"}' ``` ### DELETE /memory_stores/{store_id} Delete a memory store (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Responses: - `204` — Deleted - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x \ -H "Authorization: Bearer ast_..." ``` ### POST /memory_stores/{store_id}/archive Archive a memory store (one-way, idempotent) (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Responses: - `200` — Archived memory store - `memory_store` (object, required) - `id` (string, required) — memstore_… - `object` (enum, required) — "memory_store" - `name` (string, required) - `description` (string, required) - `attach_by_default` (enum, required) — "none" · "read_only" - `created_at` (string, required) - `updated_at` (string, required) - `archived_at` (string | null, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/archive \ -H "Authorization: Bearer ast_..." ``` ### GET /memory_stores/{store_id}/memories List memories in a store (scope: read) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Query parameters: - `path_prefix` (string) — Must end with / (default: /) - `depth` (integer) - `after` (string) - `limit` (integer) Responses: - `200` — List of memories - `memories` (object[], required) - option 1 (object) — Memory without content — returned in list responses. - `id` (string, required) — mem_… - `type` (enum, required) — Discriminator for the memories list union. — "memory" - `object` (enum, required) — "memory" - `memory_store_id` (string, required) - `path` (string, required) — Absolute path starting with / - `content_sha256` (string, required) - `content_bytes` (integer, required) - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "memory_prefix" - `path` (string, required) — Common path prefix ending with / - `has_more` (boolean, required) - `next_cursor` (string) — Opaque cursor; pass as `after` on the next request. - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memories' \ -H "Authorization: Bearer ast_..." ``` ### POST /memory_stores/{store_id}/memories Create a memory (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Body (application/json): - `path` (string, required) — Absolute path starting with / - `content` (string) — max length 102400 Responses: - `201` — Created memory - `memory` (object, required) — Memory with content — returned by single-get, create, and update responses. - `id` (string, required) — mem_… - `object` (enum, required) — "memory" - `memory_store_id` (string, required) - `path` (string, required) — Absolute path starting with / - `content` (string, required) - `content_sha256` (string, required) - `content_bytes` (integer, required) - `created_at` (string, required) - `updated_at` (string, required) - `version_id` (string, required) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memories \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"path":"/notes/hello","content":"Hello, world"}' ``` ### GET /memory_stores/{store_id}/memories/{memory_id} Get a memory with content (scope: read) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) - `memory_id` (string, required) — The memory id (mem_…) Responses: - `200` — Memory with content - `memory` (object, required) — Memory with content — returned by single-get, create, and update responses. - `id` (string, required) — mem_… - `object` (enum, required) — "memory" - `memory_store_id` (string, required) - `path` (string, required) — Absolute path starting with / - `content` (string, required) - `content_sha256` (string, required) - `content_bytes` (integer, required) - `created_at` (string, required) - `updated_at` (string, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memories/mem_cmtc5q1y' \ -H "Authorization: Bearer ast_..." ``` ### POST /memory_stores/{store_id}/memories/{memory_id} Update a memory (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) - `memory_id` (string, required) — The memory id (mem_…) Body (application/json): - `content` (string) — max length 102400 - `path` (string) — Rename the memory to this path - `precondition` (object) - `type` (enum, required) — "content_sha256" - `content_sha256` (string, required) Responses: - `200` — Updated memory - `memory` (object, required) — Memory with content — returned by single-get, create, and update responses. - `id` (string, required) — mem_… - `object` (enum, required) — "memory" - `memory_store_id` (string, required) - `path` (string, required) — Absolute path starting with / - `content` (string, required) - `content_sha256` (string, required) - `content_bytes` (integer, required) - `created_at` (string, required) - `updated_at` (string, required) - `version_id` (string, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memories/mem_cmtc5q1y \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"content":"Hello, world v2","precondition":{"type":"content_sha256","content_sha256":"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"}}' ``` ### DELETE /memory_stores/{store_id}/memories/{memory_id} Delete a memory (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) - `memory_id` (string, required) — The memory id (mem_…) Responses: - `204` — Deleted - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memories/mem_cmtc5q1y \ -H "Authorization: Bearer ast_..." ``` ### GET /memory_stores/{store_id}/memory_versions List memory versions for a store (scope: read) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) Query parameters: - `memory_id` (string) — Filter to a single memory - `after` (string) - `limit` (integer) Responses: - `200` — List of memory versions - `memory_versions` (object[], required) - `id` (string, required) — memver_… - `object` (enum, required) — "memory_version" - `memory_store_id` (string, required) - `memory_id` (string, required) - `path` (string, required) - `content` (string | null, required) — null when redacted - `content_sha256` (string | null, required) - `operation` (enum, required) — "create" · "update" · "delete" - `actor_type` (enum, required) — "user" · "session" - `actor_id` (string, required) - `redacted_at` (string | null, required) - `created_at` (string, required) - `has_more` (boolean, required) - `next_cursor` (string) — Opaque cursor; pass as `after` on the next request. - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memory_versions' \ -H "Authorization: Bearer ast_..." ``` ### GET /memory_stores/{store_id}/memory_versions/{version_id} Get a single memory version (scope: read) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) - `version_id` (string, required) — The memory version id (memver_…) Responses: - `200` — Memory version - `memory_version` (object, required) - `id` (string, required) — memver_… - `object` (enum, required) — "memory_version" - `memory_store_id` (string, required) - `memory_id` (string, required) - `path` (string, required) - `content` (string | null, required) — null when redacted - `content_sha256` (string | null, required) - `operation` (enum, required) — "create" · "update" · "delete" - `actor_type` (enum, required) — "user" · "session" - `actor_id` (string, required) - `redacted_at` (string | null, required) - `created_at` (string, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memory_versions/memver_cmtc5q1z' \ -H "Authorization: Bearer ast_..." ``` ### POST /memory_stores/{store_id}/memory_versions/{version_id}/redact Redact the content of a memory version (scope: write) Path parameters: - `store_id` (string, required) — The memory store id (memstore_…) - `version_id` (string, required) — The memory version id (memver_…) Responses: - `200` — Redacted memory version - `memory_version` (object, required) - `id` (string, required) — memver_… - `object` (enum, required) — "memory_version" - `memory_store_id` (string, required) - `memory_id` (string, required) - `path` (string, required) - `content` (string | null, required) — null when redacted - `content_sha256` (string | null, required) - `operation` (enum, required) — "create" · "update" · "delete" - `actor_type` (enum, required) — "user" · "session" - `actor_id` (string, required) - `redacted_at` (string | null, required) - `created_at` (string, required) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory_stores/memstore_cmtc5q1x/memory_versions/memver_cmtc5q1z/redact \ -H "Authorization: Bearer ast_..." ``` ## Memory ### GET /memory Read your personal cross-session memory (scope: read) Requires read scope. Only unrestricted owner-facing credentials in the authenticated owner's personal universe are supported; restricted tokens, delegated callers, and team universes return 403. Responses: - `200` — Saved entries and the opaque version required for the next write. - `entries` (object[], required) - `index` (integer, required) - `text` (string, required) - `version` (string | null, required) - `401` — Authentication required - `403` — insufficient_scope or personal_memory_only: caller or universe is not eligible - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/memory' \ -H "Authorization: Bearer ast_..." ``` ### POST /memory Save, edit, remove or clear a personal memory (scope: write) Requires write scope and unrestricted owner-facing credentials in your personal universe. Read GET /memory first and copy its version exactly into expectedVersion, even when entries is empty. The sample uses null for a store before its first write; replace it with the returned version for an existing document. Stale writes return 409; reload and resolve the change instead of overwriting. Entries are limited to 2048 UTF-8 bytes and the complete recall context to 4000 characters. A state-changing clear advances the version; a no-op on an existing document retains it. Clear preserves allocated indexes. Body (application/json): - option 1 (object) - `op` ("save", required) - `text` (string, required) — max length 8192 - `expectedVersion` (string | null, required) — max length 128 - option 2 (object) - `op` ("update", required) - `index` (integer, required) - `text` (string, required) — max length 8192 - `expectedVersion` (string | null, required) — max length 128 - option 3 (object) - `op` ("remove", required) - `index` (integer, required) - `expectedVersion` (string | null, required) — max length 128 - option 4 (object) - `op` ("clear", required) - `expectedVersion` (string | null, required) — max length 128 Responses: - `200` — Saved entries and the opaque version required for the next write. - `entries` (object[], required) - `index` (integer, required) - `text` (string, required) - `version` (string | null, required) - `400` — invalid_memory: invalid operation or memory limit reached - `401` — Authentication required - `403` — insufficient_scope or personal_memory_only: caller or universe is not eligible - `409` — memory_conflict: reload before writing - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/memory \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"op":"save","text":"Use Celsius for temperatures.","expectedVersion":null}' ``` ### GET /context/search Search one platform source: memory, conversations, the trace library, memory notes, files, meeting notes, skills or agents (scope: read) Searches one platform-owned source in the workspace your key is in. Restricted and delegated callers are refused. The consumer app's `/api/context/search` merges every source (including imports, artifacts and the Help Center) under the same grammar; use it to search everything at once. `q` is plain words (one case-insensitive substring, one hit per item), "an exact phrase" (case-sensitive, every matching line) or /a regex/ with optional i, s or u flags (every matching line). Nested repetition and backreferences are refused, and a pattern stops at a time budget with partial=true. Each hit carries `path` (such as `skills/review/SKILL.md`) and, for a match in the text, its 1-based `line`. Results are in stable source order, not a relevance ranking. Follow nextCursor until null and inspect partial/coverage before concluding no match exists. Memory entries have no per-entry timestamps, so their coverage reports that time filtering does not apply. Query parameters: - `q` (string, required) - `scope` (string, required) — The source: traces is the current workspace's trace library (every member's uploaded local agent sessions); notes the workspace's memory-store notes; files and meetings its uploaded files (meetings is the Mac app's meeting notes); skills every file of its skills; agents each agent's name and prompt. - `path` (string) — Keep only hits under this folder, such as skills/review/ or memory/sales/. - `limit` (integer) - `cursor` (string) — Source continuation returned by this query. - `since` (string) — Inclusive lower time bound. - `until` (string) — Exclusive upper time bound; must follow since. Responses: - `200` — A bounded source page, with continuation and coverage. - `hits` (object[], required) - `hit` (object, required) - `source` (enum, required) — "memory" · "notes" · "sessions" · "imports" · "traces" · "artifacts" · "files" · "meetings" · "skills" · "agents" · "docs" - `id` (string, required) - `title` (string, required) - `snippet` (string, required) - `occurredAt` (string) - `path` (string) — max length 2048 - `line` (integer) - `ref` (object, required) - option 1 (object) - `kind` ("memory", required) - `index` (integer, required) - `version` (string | null, required) - option 2 (object) - `kind` ("session_message", required) - `sessionId` (string, required) — max length 200 - `messageId` (string, required) — max length 200 - option 3 (object) - `kind` ("import_event", required) - `traceId` (string, required) — max length 200 - `seq` (integer, required) - option 4 (object) - `kind` ("artifact", required) - `artifactId` (string, required) — max length 200 - `version` (integer, required) - option 5 (object) - `kind` ("trace_message", required) - `traceId` (string, required) — max length 200 - `seq` (integer, required) - option 6 (object) - `kind` ("memory_note", required) - `storeId` (string, required) — max length 200 - `memoryId` (string, required) — max length 200 - option 7 (object) - `kind` ("file", required) - `fileId` (string, required) — max length 200 - option 8 (object) - `kind` ("skill_file", required) - `skillId` (string, required) — max length 200 - `revisionId` (string, required) — max length 200 - `path` (string, required) — max length 1024 - option 9 (object) - `kind` ("agent", required) - `agentId` (string, required) — max length 200 - option 10 (object) - `kind` ("doc", required) - `slug` (string, required) — max length 200 - `citation` (string) - `cursor` (string, required) — max length 4096 - `nextCursor` (string | null, required) — max length 4096 - `coverage` (string, required) - `partial` (boolean) - `400` — Invalid query, source reference, cursor or text offset - `401` — Authentication required - `403` — The caller lacks personal-context authority or read scope - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/context/search?q=concise&scope=memory' \ -H "Authorization: Bearer ast_..." ``` ### GET /context/read Read the source behind a search hit (scope: read) Rechecks ownership, workspace and deletion. Accepts memory, session_message, trace_message, memory_note, file, skill_file and agent references from search; a file that is not text reads as text null. Memory returns the current entry and flags version changes; it does not preserve old versions. Message reads return bounded text and adjacent references for surrounding context. Use nextOffset for a long main message; follow adjacent refs to continue reading. All text together is bounded to 20,000 characters per page. Query parameters: - `ref` (string, required) — URL-encoded JSON source reference copied from a search hit. - `offset` (integer) Responses: - `200` — Current reference, text, nextOffset, truncated, updated, adjacent references and coverage. - `ref` (object) - `text` (string) - `nextOffset` (integer | null) - `truncated` (boolean) - `updated` (boolean) - `coverage` (string) - `adjacent` (object[]) - `ref` (object) - `text` (string) - `truncated` (boolean) - `400` — Invalid query, source reference, cursor or text offset - `401` — Authentication required - `403` — The caller lacks personal-context authority or read scope - `404` — Source unavailable or not owned by this caller - `409` — Memory changed while paging; read again from offset zero - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/context/read?ref=%7B%22kind%22%3A%22memory%22%2C%22index%22%3A0%2C%22version%22%3Anull%7D' \ -H "Authorization: Bearer ast_..." ``` ## Identity ### GET /whoami Caller identity, resolved universe, effective scopes (scope: read) Responses: - `200` — OK - `user` (object, required) - `id` (string) - `email` (string) - `name` (string | null) - `universe` (object, required) - `slug` (string) - `name` (string) - `isPersonal` (boolean) - `scopes` (enum[], required) — "read" · "write" · "admin" - `auth` (enum, required) — "token" · "session" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/whoami' \ -H "Authorization: Bearer ast_..." ``` ## Universes ### GET /universes List universes (scope: read) Responses: - `200` — OK - `universes` (object[], required) - `slug` (string) - `name` (string) - `isPersonal` (boolean) — The workspace is its owner's default workspace - `isDefault` (boolean) — The workspace is the caller's own default workspace - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes' \ -H "Authorization: Bearer ast_..." ``` ### POST /universes Create a universe (scope: write, personal tokens only) Any credential of the person may create one, whichever workspace the request selects with X-Universe. A key limited to one team workspace (a workspace-scoped token, a workspace key or an app credential) is 403 forbidden. Body (application/json): - `slug` (string, required) — max length 40 — pattern ^[a-z0-9][a-z0-9-]*[a-z0-9]$ - `name` (string) — max length 80 Responses: - `201` — Created - `universe` (object, required) - `slug` (string) - `name` (string) - `isPersonal` (boolean) — The workspace is its owner's default workspace - `isDefault` (boolean) — The workspace is the caller's own default workspace - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"slug":"acme","name":"Acme"}' ``` ### GET /universes/{slug} Universe detail (scope: read) Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `universe` (object, required) - `slug` (string) - `name` (string) - `isPersonal` (boolean) — The workspace is its owner's default workspace - `isDefault` (boolean) — The workspace is the caller's own default workspace - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /universes/{slug} Rename a workspace (scope: write, owner) Owner only, as Settings › General renames it. The slug does not change. A credential locked to another workspace is 403 universe_mismatch; a member who is not the owner is 403 forbidden. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `name` (string, required) — max length 100 Responses: - `200` — OK - `universe` (object) - `slug` (string) - `name` (string) - `isPersonal` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/universes/acme \ -H "Authorization: Bearer ast_..." ``` ### DELETE /universes/{slug} Permanently delete a workspace and everything in it (scope: write, owner) Owner only, with a personal ast_ API key (a workspace key, a workspace-scoped token or an app credential is 403). Everyone else loses access at once; every session is stopped and its pod destroyed; agent fees held on its tasks are settled; the purchased credit in its wallet goes to the owner; apps the workspace connected are disconnected; then its sessions and transcripts, agents, artifacts, task files, files, memory, routines, channels, webhooks, keys, vaults, skills, environments, traces, members and invitations are removed. Money records (ledger, bills, receipts) are kept. A default workspace cannot be deleted (409 default_workspace), nor one with an agent live in the catalog (409 live_listing), a public environment (409 public_environment) or a published product (409 published_product). 503 pod_teardown_failed when a pod would not stop: nothing else was removed, and repeating the call finishes the delete. Read GET /universes/{slug}/deletion first to show what will go. Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `deleted` (object, required) - `slug` (string, required) - `sessions_stopped` (integer, required) — Pods that were running and were destroyed - `wallet_returned_micros` (integer, required) — Purchased credit moved from the workspace wallet to the owner, in USD micros - `session_ids` (string[], required) — Every session the workspace held, all now deleted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/deletion What deleting the workspace would remove, and whether it can be deleted (scope: read, owner) Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `workspace` (object, required) - `slug` (string) - `name` (string) - `created_at` (string) - `can_delete` (boolean, required) - `blockers` (object[], required) - `code` (enum) — "default_workspace" · "live_listing" · "public_environment" · "published_product" - `message` (string) - `removes` (map, required) — Counts by kind: sessions, running_sessions, agents, artifacts, files, memory_stores, routines, channels, webhooks, api_keys, vaults, skills, environments, connections, traces, members (besides the owner), invites (pending) - `wallet` (object, required) — Present when the workspace wallet holds purchased credit - `balance_micros` (integer) - `returns_to` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/deletion' \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/members People in the workspace and, for owners and admins, waiting invitations (scope: read) Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `role` (enum, required) — "owner" · "admin" · "member" - `members` (object[], required) - `user_id` (string) - `email` (string) - `name` (string) - `role` (enum) — "owner" · "admin" · "member" - `joined_at` (string) - `invites` (object[], required) - `id` (string) - `email` (string) - `role` (enum) — "admin" · "member" - `invited_by` (string) - `expires_at` (string) - `created_at` (string) - `limit_requests` (object[]) — Members' tasks that wait for a higher monthly limit, oldest first - `id` (string) - `session_id` (string) - `title` (string) - `member` (object) - `user_id` (string) - `name` (string) - `email` (string) - `raise_to` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `held` (integer) — Messages the task holds and will run when the limit is raised - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/members' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /universes/{slug}/members/{userId} Remove a non-owner from the workspace, or leave it (scope: write) The owner removes anyone but themselves. Anyone else may name only themselves (their id, or me), which is how a person leaves; naming someone else is 403, and the owner cannot leave (400). Ends membership access, stops sharing every connection the person shared with the workspace, and closes pending and accepted invitation links. Accounts and threads remain; workspace-visible threads owned by the removed person become read-only for remaining members. Path parameters: - `slug` (string, required) — The universe slug - `userId` (string, required) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/members/cmtb3u5r7k2p9x01s6q8n4w2 \ -H "Authorization: Bearer ast_..." ``` ### PATCH /universes/{slug}/members/{userId} Make someone an admin or a member (scope: write, owner) Path parameters: - `slug` (string, required) — The universe slug - `userId` (string, required) Body (application/json): - `role` (enum, required) — "admin" · "member" Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/universes/acme/members/cmtb3u5r7k2p9x01s6q8n4w2 \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"role":"admin"}' ``` ### GET /universes/{slug}/agents The agents this workspace chose from the catalog (scope: read) The catalog is the same for everyone; this is what the workspace installed from it, enabled or not, with the model members start each agent with by default. agent is the listing as the catalog serves it now, null once it has left the catalog. Installing gates nothing: a policy rule forbids an agent, and starting a session still validates the model it is given. Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `role` (enum, required) — "owner" · "admin" · "member" - `installs` (object[], required) - `handle` (string, required) — The catalog handle without its @ - `enabled` (boolean, required) - `default_model` (string, required) — The model members start this agent with unless they pick another; null = the agent's own default - `installed_by` (object, required) - `user_id` (string, required) - `name` (string) - `email` (string) - `created_at` (string, required) - `updated_at` (string, required) - `agent` (object, required) — The listing as the catalog serves it to this workspace now; null once it has left the catalog - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/agents' \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/agents/{handle} One agent the workspace chose from the catalog (scope: read) Path parameters: - `slug` (string, required) — The universe slug - `handle` (string, required) — The catalog handle, with or without its @ Responses: - `200` — OK - `install` (object, required) - `handle` (string, required) — The catalog handle without its @ - `enabled` (boolean, required) - `default_model` (string, required) — The model members start this agent with unless they pick another; null = the agent's own default - `installed_by` (object, required) - `user_id` (string, required) - `name` (string) - `email` (string) - `created_at` (string, required) - `updated_at` (string, required) - `agent` (object, required) — The listing as the catalog serves it to this workspace now; null once it has left the catalog - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/agents/handle' \ -H "Authorization: Bearer ast_..." ``` ### PUT /universes/{slug}/agents/{handle} Install an agent from the catalog, or change its default model or enabled state (scope: write, owner or admin) Idempotent on the handle: the first call installs (201), later calls update (200). The handle must be one the catalog serves to this workspace (404 otherwise). default_model is checked against the agent's engine the way starting a session checks it; null clears it so the agent's own default applies again. Path parameters: - `slug` (string, required) — The universe slug - `handle` (string, required) — The catalog handle, with or without its @ Body (application/json): - `enabled` (boolean) - `default_model` (string | null) — max length 200 Responses: - `200` — Updated - `install` (object, required) - `handle` (string, required) — The catalog handle without its @ - `enabled` (boolean, required) - `default_model` (string, required) — The model members start this agent with unless they pick another; null = the agent's own default - `installed_by` (object, required) - `user_id` (string, required) - `name` (string) - `email` (string) - `created_at` (string, required) - `updated_at` (string, required) - `agent` (object, required) — The listing as the catalog serves it to this workspace now; null once it has left the catalog - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `201` — Installed - `install` (object, required) - `handle` (string, required) — The catalog handle without its @ - `enabled` (boolean, required) - `default_model` (string, required) — The model members start this agent with unless they pick another; null = the agent's own default - `installed_by` (object, required) - `user_id` (string, required) - `name` (string) - `email` (string) - `created_at` (string, required) - `updated_at` (string, required) - `agent` (object, required) — The listing as the catalog serves it to this workspace now; null once it has left the catalog - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/universes/acme/agents/handle \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"enabled":true,"default_model":"claude-sonnet-5"}' ``` ### DELETE /universes/{slug}/agents/{handle} Remove an agent from the workspace's choices (scope: write, owner or admin) Removes the install and its default model. Sessions already running with the agent are untouched, and members can still start it by handle: use a policy rule to forbid it. Path parameters: - `slug` (string, required) — The universe slug - `handle` (string, required) — The catalog handle, with or without its @ Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/agents/handle \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/invites Add people to the workspace by email (scope: write, owner or admin) Up to 50 emails per call; the workspace may hold at most 200 pending invitations in total. To invite more than 50 people, send multiple calls. Set send_email to true to send invitation mail and include per-email delivery results. sent means provider acceptance; unavailable leaves a usable link to share. No email is sent and no delivery field is returned by default. The response carries one link token per new invitation (shown only here; links are /join/{token} on the consumer app). Each person joins through the link, or the next time they use AgentSky signed in with that verified email; an invitation lasts 30 days. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `emails` (string[], required) - `send_email` (boolean) — default: false - `role` (enum) — "admin" · "member" — default: "member" Responses: - `201` — Created - `invites` (object[]) - `links` (object[]) - `email` (string) - `token` (string) - `delivery` (object[]) - `email` (string) - `status` (enum) — "sent" · "unavailable" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/invites \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"emails":["teammate@acme.com"],"role":"member","send_email":true}' ``` ### DELETE /universes/{slug}/invites/{inviteId} Withdraw an invitation that has not been taken up (scope: write, owner or admin) Path parameters: - `slug` (string, required) — The universe slug - `inviteId` (string, required) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/invites/cmtb4i7v2n6q8z01s6k3m5p1 \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/wallet A workspace's own wallet: balance, each member's spending this month, limits, members waiting for a higher limit (scope: read, owner or admin) 404 where workspace wallets are not switched on for the workspace, 400 for a person's default workspace that has no wallet and nobody else in it, 403 for a plain member. Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `wallet` (object | null, required) — Null until the workspace is given a wallet - `month_start` (string, required) — The first instant (UTC) of the month the spending below is counted from - `members` (object[], required) - `user_id` (string) - `email` (string) - `name` (string) - `role` (enum) — "owner" · "admin" · "member" - `spent` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `monthly_limit` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `funding` (object[], required) — The latest additions to the wallet, newest first - `id` (string) - `amount` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `added_by` (string) - `created_at` (string) - `limit_requests` (object[], required) — Members' tasks that wait for a higher monthly limit, oldest first - `id` (string) - `session_id` (string) - `title` (string) - `member` (object) - `user_id` (string) - `name` (string) - `email` (string) - `raise_to` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `held` (integer) — Messages the task holds and will run when the limit is raised - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/wallet' \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/wallet Give the workspace a wallet of its own (scope: write, owner or admin) From then on a new session a member starts in the workspace is paid from this wallet unless its request names payer personal, and is refused with 402 workspace_wallet_empty while the wallet holds nothing. Sessions that already exist keep the payer recorded for them. Repeating the call returns the wallet that exists. Path parameters: - `slug` (string, required) — The universe slug Responses: - `201` — Created - `wallet` (object | null, required) — Null until the workspace is given a wallet - `month_start` (string, required) — The first instant (UTC) of the month the spending below is counted from - `members` (object[], required) - `user_id` (string) - `email` (string) - `name` (string) - `role` (enum) — "owner" · "admin" · "member" - `spent` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `monthly_limit` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `funding` (object[], required) — The latest additions to the wallet, newest first - `id` (string) - `amount` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `added_by` (string) - `created_at` (string) - `limit_requests` (object[], required) — Members' tasks that wait for a higher monthly limit, oldest first - `id` (string) - `session_id` (string) - `title` (string) - `member` (object) - `user_id` (string) - `name` (string) - `email` (string) - `raise_to` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `held` (integer) — Messages the task holds and will run when the limit is raised - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/wallet \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/wallet/transfers Move some of your own purchased credit into the workspace wallet (scope: write, owner or admin) All or nothing. Only credit the caller bought moves; free credit stays. 402 insufficient_purchased_credit when the caller holds less purchased credit than the amount, 409 workspace_wallet_missing before the wallet exists. request_id makes a repeat of the same request land once (200 instead of 201); the same id with another amount is 409 idempotency_conflict. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `amount` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `request_id` (string, required) — max length 100 Responses: - `201` — Moved - `transferred` (boolean) - `balance` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/wallet/transfers \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/wallet/topups Buy credit for the workspace wallet with your own saved card (scope: write, owner or admin) Charges the caller's own saved card the credit plus the card processing fee and adds the credit to the workspace wallet. The caller's own balance, card and auto top-up are not changed. amount is 1000, 2000, 5000 or 10000 cents. request_id makes a repeat of the same request charge once (200 instead of 201). 409 card_required when the caller has no saved card (add one to your own account first), 402 card_declined, 409 workspace_wallet_missing before the wallet exists, 503 payments_unavailable where card payments are not set up. A workspace key or a restricted credential cannot buy credit. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `amount` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `request_id` (string, required) — max length 100 Responses: - `201` — Bought - `charged` (boolean) - `balance` (object) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/wallet/topups \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/wallet/limit-requests/{requestId} Answer a member's request for a higher monthly limit (scope: write, owner or admin) A member who reaches their monthly limit is not only refused: the task they started or wrote to waits, holding what it was given, and a request is raised (limit_requests on the wallet, and a spend request on the task). raise sets the member's limit to the amount the request offered and sends on what every waiting task of theirs was holding; decline closes this request and drops what its task held. 404 when the request is not open. Path parameters: - `slug` (string, required) — The universe slug - `requestId` (string, required) Body (application/json): - `answer` (enum, required) — "raise" · "decline" Responses: - `200` — OK - `ok` (boolean) - `message_id` (string) — The first message sent on to a waiting task, when one was - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/wallet/limit-requests/requestId \ -H "Authorization: Bearer ast_..." ``` ### PUT /universes/{slug}/wallet/limits/{userId} Set or clear a member's monthly limit on workspace wallet spending (scope: write, owner or admin) The limit counts workspace-wallet charges attributed to that member in the calendar month (UTC): the admitted sender of each turn or consumption, including helpers. Historical charges without a recorded sender and work outside an active turn count against the session starter. At the limit a message to a session the workspace pays for, a helper, a handoff and a fork are refused with 402 member_limit_reached, and a new session is created waiting; other members are not affected. monthly_limit null removes the limit. When the new limit lets the member spend again their waiting tasks continue by themselves: resumed counts the messages that were sent on. Path parameters: - `slug` (string, required) — The universe slug - `userId` (string, required) Body (application/json): - `monthly_limit` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" Responses: - `200` — OK - `ok` (boolean) - `resumed` (integer) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/universes/acme/wallet/limits/cmtb3u5r7k2p9x01s6q8n4w2 \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/connections Connected apps people share with the workspace, and the ones it owns (scope: read) sharing says whether members can share connected apps with this workspace; while it is on, can_connect says whether the caller may connect an app for the workspace itself, and a connection the workspace owns carries workspace_owned. A person's default workspace takes shared connections only once someone besides its owner is in it. Agents in the workspace's sessions can call through a shared connection whoever started the session; nobody but the person who shared it holds its sign-in. Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `sharing` (boolean, required) — Whether members can share connected apps with this workspace. - `can_connect` (boolean) — While sharing is on: whether the caller may connect an app for the workspace itself. - `connections` (object[], required) - `connection_id` (string) - `app` (string) - `app_name` (string) - `label` (string) - `shared_by` (object) - `user_id` (string) - `name` (string) - `shared_at` (string) - `mine` (boolean) — True when the caller is the person who shared it. - `workspace_owned` (boolean) — Present and true when the workspace itself owns the connection. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/connections' \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/connections Share one of your own connections with the workspace, or start connecting an app for it (scope: write) With connection_id: only a connection of the caller's can be shared; answers 201 with the workspace's connections. With app, success_url and error_url (owner or admin): starts connecting an app for the workspace itself and answers 200 with the address to open in a browser; record the result with POST /universes/{slug}/connections/complete. A connection made this way belongs to the workspace, not to the person who made it, and stays when they leave. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `connection_id` (string) - `app` (string) - `success_url` (string) - `error_url` (string) Responses: - `200` — Started - `url` (string) - `201` — Created - `sharing` (boolean, required) — Whether members can share connected apps with this workspace. - `can_connect` (boolean) — While sharing is on: whether the caller may connect an app for the workspace itself. - `connections` (object[], required) - `connection_id` (string) - `app` (string) - `app_name` (string) - `label` (string) - `shared_by` (object) - `user_id` (string) - `name` (string) - `shared_at` (string) - `mine` (boolean) — True when the caller is the person who shared it. - `workspace_owned` (boolean) — Present and true when the workspace itself owns the connection. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/connections \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/connections/complete Record an app connected for the workspace itself (scope: write, owner or admin) The account is checked against the workspace's own accounts at the provider; without account_id its newest one for the app is used. From then on agents in every session of the workspace can call through it. 404 when the workspace has no such account. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `app` (string, required) - `account_id` (string) Responses: - `201` — Created - `sharing` (boolean, required) — Whether members can share connected apps with this workspace. - `can_connect` (boolean) — While sharing is on: whether the caller may connect an app for the workspace itself. - `connections` (object[], required) - `connection_id` (string) - `app` (string) - `app_name` (string) - `label` (string) - `shared_by` (object) - `user_id` (string) - `name` (string) - `shared_at` (string) - `mine` (boolean) — True when the caller is the person who shared it. - `workspace_owned` (boolean) — Present and true when the workspace itself owns the connection. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/connections/complete \ -H "Authorization: Bearer ast_..." ``` ### DELETE /universes/{slug}/connections/{connectionId} Stop sharing a connection with the workspace (scope: write, the sharer, owner or admin) Agents lose it at once. A connection the workspace owns itself is disconnected for good, by an owner or admin only. Path parameters: - `slug` (string, required) — The universe slug - `connectionId` (string, required) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/connections/connectionId \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/keys Keys that belong to the workspace (scope: read, owner or admin) A workspace key belongs to the workspace, not to the person who made it, and keeps working after they leave. It acts through the workspace owner's account, whose wallet pays for the sessions it starts, and reaches only the workspace's agents, sessions, files, skills and environments. The listing never carries a secret. Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `keys` (object[], required) - `id` (string, required) - `name` (string, required) - `prefix` (string, required) — The start of the secret, to tell one key from another. - `scopes` (enum[], required) — "read" · "write" · "admin" - `created_by` (object, required) - `user_id` (string) - `name` (string) - `created_at` (string, required) - `last_used_at` (string) - `expires_at` (string) - `revoked_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/keys' \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/keys Make a workspace key (scope: admin, owner or admin) The secret is returned only in this response. The key carries no more scope than the credential that makes it. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `name` (string, required) — max length 80 - `scopes` (enum[], required) — "read" · "write" · "admin" - `expires_at` (string) Responses: - `201` — Created - `key` (object, required) - `id` (string, required) - `name` (string, required) - `prefix` (string, required) — The start of the secret, to tell one key from another. - `scopes` (enum[], required) — "read" · "write" · "admin" - `created_by` (object, required) - `user_id` (string) - `name` (string) - `created_at` (string, required) - `last_used_at` (string) - `expires_at` (string) - `revoked_at` (string) - `secret` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/keys \ -H "Authorization: Bearer ast_..." ``` ### DELETE /universes/{slug}/keys/{keyId} Revoke a workspace key (scope: admin, owner or admin) Path parameters: - `slug` (string, required) — The universe slug - `keyId` (string, required) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/keys/keyId \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/hooks Get workspace shared hooks (scope: read) Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `hooks` (any[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/hooks' \ -H "Authorization: Bearer ast_..." ``` ### PUT /universes/{slug}/hooks Replace workspace shared hooks (owner only) (scope: write) Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - `hooks` (any[], required) Responses: - `200` — OK - `hooks` (any[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/universes/acme/hooks \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"hooks":[{"event":"pre_tool_use","matcher":"Bash","command":"./.agentsky/check-command.sh","timeout_s":30}]}' ``` ### GET /universes/{slug}/policies List workspace policy rules (scope: read) Path parameters: - `slug` (string, required) — The universe slug Responses: - `200` — OK - `rules` (object[]) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/policies' \ -H "Authorization: Bearer ast_..." ``` ### POST /universes/{slug}/policies Create a workspace policy rule (owner only) (scope: write) A rule {name, kind: "CAPABILITY", config: {"platform.orchestration": "deny"}} stops every session of the workspace from bringing in other agents as helpers (403 hiring_disabled to the agent; helpers it already has can still be listed, collected and stopped), and sessions created afterwards are not told how to hire. Disable or delete the rule to turn hiring back on. Path parameters: - `slug` (string, required) — The universe slug Body (application/json): - option 1 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("TOOL_CALL", required) - `config` (object, required) - `action` (enum, required) — "allow" · "ask" · "deny" - `matcher` (string) — max length 255 - `commandPatterns` (string[]) — max length 500 - option 2 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("SPEND_CAP", required) - `config` (object, required) - `warnUsdMicros` (integer | null) - `hardCapUsdMicros` (integer, required) - option 3 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("CALL_COUNT", required) - `config` (object, required) - `limitPerSession` (integer, required) - option 4 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("CAPABILITY", required) - `config` (map>, required) — "allow" · "ask" · "deny" Responses: - `201` — Created - `rule` (object) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/universes/acme/policies \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"name":"Block destructive shell commands","kind":"TOOL_CALL","config":{"action":"deny","matcher":"Bash","commandPatterns":["rm -rf *"]}}' ``` ### GET /universes/{slug}/policies/{ruleId} Get a workspace policy rule (scope: read) Path parameters: - `slug` (string, required) — The universe slug - `ruleId` (string, required) — The policy rule id Responses: - `200` — OK - `rule` (object) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/policies/cmtb5p0l3r8w2k01s6x7n9q4' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /universes/{slug}/policies/{ruleId} Update a workspace policy rule (owner only) (scope: write) Path parameters: - `slug` (string, required) — The universe slug - `ruleId` (string, required) — The policy rule id Body (application/json): - `name` (string) — max length 128 - `enabled` (boolean) - `position` (integer) - `config` (object | map>) - option 1 (object) - `action` (enum, required) — "allow" · "ask" · "deny" - `matcher` (string) — max length 255 - `commandPatterns` (string[]) — max length 500 - option 2 (object) - `warnUsdMicros` (integer | null) - `hardCapUsdMicros` (integer, required) - option 3 (object) - `limitPerSession` (integer, required) - option 4 (map>) — "allow" · "ask" · "deny" Responses: - `200` — OK - `rule` (object) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/universes/acme/policies/cmtb5p0l3r8w2k01s6x7n9q4 \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"enabled":false}' ``` ### DELETE /universes/{slug}/policies/{ruleId} Delete a workspace policy rule (owner only) (scope: write) Path parameters: - `slug` (string, required) — The universe slug - `ruleId` (string, required) — The policy rule id Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/policies/cmtb5p0l3r8w2k01s6x7n9q4 \ -H "Authorization: Bearer ast_..." ``` ### GET /universes/{slug}/pod-provider Read the elected BYO provider and whether a credential is registered (scope: admin) Returns the workspace's elected BYO pod provider name, its optional label, and `has_credential: true`. 404 when BYO_POD_PROVIDER is not enabled for this workspace, no provider is elected, or no credential row exists. 503 `not_migrated` before the credential table exists. Path parameters: - `slug` (string, required) — The workspace slug. Responses: - `200` — OK - `provider` (enum) — The elected provider. — "e2b" · "sandbox-oss" · "daytona" - `label` (string | null) — The admin-supplied label for this credential. - `has_credential` (boolean) — Always true when the response is present. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/universes/acme/pod-provider' \ -H "Authorization: Bearer ast_..." ``` ### PUT /universes/{slug}/pod-provider Register a BYO provider credential for the workspace (scope: admin) Stores the encrypted provider credential and elects the provider for the workspace. The credential is write-only and never returned by any API. Setting a new credential replaces the old one. 404 when BYO_POD_PROVIDER is not enabled for this workspace. 503 `not_migrated` before the credential table exists. Path parameters: - `slug` (string, required) — The workspace slug. Body (application/json): - `provider` (enum, required) — The provider to elect. — "e2b" · "sandbox-oss" · "daytona" - `credential` (string, required) — The provider API key (write-only; never returned). — max length 10000 - `label` (string) — Optional human-readable label. — max length 128 Responses: - `200` — Credential stored and provider elected. - `provider` (enum) — The elected provider. — "e2b" · "sandbox-oss" · "daytona" - `label` (string | null) — The admin-supplied label for this credential. - `has_credential` (boolean) — Always true when the response is present. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/universes/acme/pod-provider \ -H "Authorization: Bearer ast_..." ``` ### DELETE /universes/{slug}/pod-provider Remove the BYO provider credential and clear the election (scope: admin) Deletes the credential row and clears Universe.podProvider. Future sessions use the platform's provider. 404 when BYO_POD_PROVIDER is not enabled for this workspace. 503 `not_migrated` before the credential table exists. Path parameters: - `slug` (string, required) — The workspace slug. Responses: - `200` — Credential removed. - `deleted` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/universes/acme/pod-provider \ -H "Authorization: Bearer ast_..." ``` ## Products and Runs ### POST /runs Run a product (scope: write) String input is a shortcut. Agent objects preserve one user message and optional system message. Direct Agent Runs prepare a Session with platform billing; an existing Session retains its payer and billing settings. Non-streaming waits up to ten seconds. SSE begins with the durable Run identity. Disconnect never cancels execution. A catalog handle (`@handle`) runs that agent as one turn of a new session of it, charged as that session is: `addons` may set `model`, within the agent's engine, and `instructions`, files added after the agent's own, and nothing else; a `user.message` sent to the Run (POST /runs/{id}/events) is the next turn of that session and a Run of its own. When a Run of a handle ends, each webhook endpoint of the workspace subscribed to `run.completed`, `run.interrupted`, `run.failed` or `run.blocked` receives that event once, signed like every other webhook. A handle whose agent has not passed its checks is refused with `listing_not_checked` or `listing_checks_failed`. Body (application/json): - `product` (string, required) — max length 240 - `input` (any, required) - `addons` (map) - `session_id` (string) - `stream` (boolean) - `metadata` (map) Responses: - `200` — Durable Run identity, status, output and references to authoritative billing. - `run` (object) - `id` (string, required) - `product` (string | null, required) - `session_id` (string | null, required) - `status` (enum, required) — "queued" · "running" · "waiting_input" · "stopping" · "completed" · "interrupted" · "failed" · "blocked" · "reconciling" - `output` (any, required) - `output_available` (boolean) - `usage` (any, required) - `billing` (any, required) - `error` (any, required) - `artifact` (object) - `id` (string, required) - `mediaType` (string, required) - `url` (string, required) - `source` (object) - `goal_id` (string) - `iteration` (integer) - `routine_run_id` (string) - `routine_id` (string) - `created_at` (string, required) - `completed_at` (string | null, required) - `202` — Durable Run identity, status, output and references to authoritative billing. - `run` (object) - `id` (string, required) - `product` (string | null, required) - `session_id` (string | null, required) - `status` (enum, required) — "queued" · "running" · "waiting_input" · "stopping" · "completed" · "interrupted" · "failed" · "blocked" · "reconciling" - `output` (any, required) - `output_available` (boolean) - `usage` (any, required) - `billing` (any, required) - `error` (any, required) - `artifact` (object) - `id` (string, required) - `mediaType` (string, required) - `url` (string, required) - `source` (object) - `goal_id` (string) - `iteration` (integer) - `routine_run_id` (string) - `routine_id` (string) - `created_at` (string, required) - `completed_at` (string | null, required) - `409` — Session busy or conflicting idempotency key - `422` — The product cannot be run: a listed agent that has not passed its checks, or an override that is not allowed - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/runs \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"product":"@claude_code","input":"Summarize the repository."}' ``` ### GET /runs/{id} Read a Run (scope: read) Path parameters: - `id` (string, required) Responses: - `200` — Durable Run identity, status, output and references to authoritative billing. - `run` (object) - `id` (string, required) - `product` (string | null, required) - `session_id` (string | null, required) - `status` (enum, required) — "queued" · "running" · "waiting_input" · "stopping" · "completed" · "interrupted" · "failed" · "blocked" · "reconciling" - `output` (any, required) - `output_available` (boolean) - `usage` (any, required) - `billing` (any, required) - `error` (any, required) - `artifact` (object) - `id` (string, required) - `mediaType` (string, required) - `url` (string, required) - `source` (object) - `goal_id` (string) - `iteration` (integer) - `routine_run_id` (string) - `routine_id` (string) - `created_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/runs/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### GET /runs/{id}/events Read durable Run events, or observe with Accept: text/event-stream (scope: read) A Run of a catalog handle reports `run.running`, each `agent.message` with its text, and how it ended. Path parameters: - `id` (string, required) Query parameters: - `cursor` (string) Responses: - `200` — Opaque Run cursor, separate from the Session transcript cursor - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/runs/sess_cmtb7q2j/events' \ -H "Authorization: Bearer ast_..." ``` ### POST /runs/{id}/events Target steering or an interrupt to this Run (scope: write) For a Run of a catalog handle, one `user.message` starts the next turn of the Run's session as a new Run and is answered with `{ run_id, status }` naming that new Run (also in `Location`); it follows the idempotency rules of starting a Run, and is refused with 409 `session_busy` while a turn of the session is still going and 409 `session_archived` once the session has ended. One `user.interrupt` stops this Run's turn and is answered with `{ run_id, status }` for this Run; a Run that has already ended is answered with the status it ended in. A system message is not accepted. Path parameters: - `id` (string, required) Body (application/json): - `events` (object[], required) - type: "user.message" (object) - `type` ("user.message", required) - `content` (object[], required) - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "image" (object) - `type` ("image", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - type: "document" (object) - `type` ("document", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "text" (object) - `type` ("text", required) - `media_type` ("text/plain") — default: "text/plain" - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - `title` (string | null) - `context` (string | null) - `interaction_mode` (enum | null) — "plan" · "build" - type: "system.message" (object) - `type` ("system.message", required) - `content` (object[], required) - `type` ("text", required) - `text` (string, required) - type: "user.interrupt" (object) - `type` ("user.interrupt", required) Responses: - `202` — Control admitted; runtime confirmation is separate - `409` — Run is terminal or input conflicts - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/runs/sess_cmtb7q2j/events \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"events":[{"type":"user.interrupt"}]}' ``` ## Agents ### GET /agents List agents in the resolved universe (scope: read) Responses: - `200` — OK - `agents` (object[], required) - `id` (string) — The agent id (agent_…) — the only address - `name` (string) - `displayName` (string) - `agentType` (string) — Engine — hermes · claude_code · codex · openclaw · pi · dsh · kimi_code · opencode · jev, or any ACP harness (the full vocabulary is the enum on CreateAgent) - `llm` (string) - `harnessVersion` (string | null) — The engine CLI version this agent's sessions run; PATCH to move to the platform's current version - `reasoningEffort` (string | null) — Harness-native reasoning effort; null = model-compatible fallback for constrained models, otherwise harness default. Applies from the next provision/restart. Allowed values depend on agentType — agoragentic_acp: not supported; amp_acp: not supported; auggie: not supported; autohand: not supported; cline: not supported; codebuddy_code: not supported; cortex_code: not supported; corust_agent: not supported; crow_cli: not supported; cursor: not supported; deepagents: not supported; devin: not supported; dimcode: not supported; dirac: not supported; factory_droid: not supported; fast_agent: not supported; gemini: not supported; github_copilot_cli: not supported; glm_acp_agent: not supported; antigravity_acp: not supported; goose: not supported; grok_build: not supported; harn: not supported; junie: not supported; kilo: not supported; kimchi: not supported; minimax_code: not supported; minion_code: not supported; mistral_vibe: not supported; nova: not supported; poolside: not supported; qoder: not supported; qwen_code: not supported; sigit: not supported; stakpak: not supported; vtcode: not supported; code_assistant: not supported; docker_cagent: not supported; kiro: not supported; muse_code: not supported; openhands: not supported; hermes: none/minimal/low/medium/high/xhigh/max/ultra; claude_code: low/medium/high/xhigh/max; codex: none/minimal/low/medium/high/xhigh/max; openclaw: not supported; jev: not supported; pi: off/minimal/low/medium/high/xhigh/max; dsh: not supported; kimi_code: not supported; opencode: none/minimal/low/medium/high/xhigh/max; workspace_acp: not supported; models: claude-fable-5-1: low/medium/high/xhigh/max; claude-fable-5: low/medium/high/xhigh/max; claude-opus-5-5: low/medium/high/xhigh/max; claude-opus-5: low/medium/high/xhigh/max; claude-opus-4-8: low/medium/high/xhigh/max; claude-sonnet-5-5: low/medium/high/xhigh/max; claude-sonnet-5: low/medium/high/xhigh/max; claude-sonnet-4-6: low/medium/high/max; claude-haiku-5-5: low/medium/high/xhigh/max; claude-haiku-4-5: not supported; gpt-6.1-sol: low/medium/high/xhigh/max; gpt-6-astra: low/medium/high/xhigh/max; gpt-6-sol: none/low/medium/high/xhigh/max; gpt-6-luna: none/low/medium/high/xhigh/max; gpt-5.6-sol: none/low/medium/high/xhigh/max; gpt-5.6-terra: none/low/medium/high/xhigh/max; gpt-5.6-luna: none/low/medium/high/xhigh/max; gpt-5.5: none/low/medium/high/xhigh; gpt-4.1: not supported; deepseek-v4-pro: low/high/max; deepseek-flash: low/high/max; kimi-k3.1-pro: low/high/max; kimi-k3: low/high/max; kimi-k2.7-code: not supported; minimax-m3: not supported; muse-spark-1.1: minimal/low/medium/high/xhigh; muse-spark-1.2: minimal/low/medium/high/xhigh; muse-spark-1.3: minimal/low/medium/high/xhigh/max; minimax-m3.1-flash: low/medium/high/xhigh/max; gemini-4-argon: low/medium/high; gemini-3.5-flash-lite: low/medium/high; gemini-3.8-flash: low/medium/high; gemini-3.5-flash: low/medium/high; glm-5.3: low/high/max; glm-5.3-flash: low/high/max; grok-4.6: low/medium/high; grok-4.7: low/medium/high/xhigh — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" · null - `capabilities` (enum[]) — The built-in capabilities this agent is granted — the exact set its sessions may execute (enforced; empty = none). Ids are listed at /docs/capabilities. — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `version` (integer) — Bumped on every update; see expectedVersion - `archived` (boolean) - `sessionCount` (integer) - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/agents' \ -H "Authorization: Bearer ast_..." ``` ### POST /agents Create an agent (scope: write) An agent is a reusable configuration — engine, prompt, capabilities, secrets. Every field is optional: an empty body creates the default pi agent with no built-in capabilities (capabilities are explicit opt-in; unknown ids → 400). The response's `id` (agent_…) is the agent's only address. One agent can run many sessions; creating the agent provisions nothing — session creation provisions the pod by default (POST /v1/sessions supports deferred provisioning). Optional Idempotency-Key safely replays the original 201 response within the same user and universe; changing the body with the same key returns 409 idempotency_conflict. Keys do not expire. Body (application/json): - `name` (string) — Prompt label only — not an address. An agent is addressed by the `id` in the response (agent_…); names are free and need not be unique — max length 60 - `description` (string) — max length 500 - `agentType` (enum) — "hermes" · "claude_code" · "codex" · "openclaw" · "pi" · "dsh" · "kimi_code" · "opencode" · "jev" · "agoragentic_acp" · "amp_acp" · "auggie" · "autohand" · "cline" · "codebuddy_code" · "cortex_code" · "corust_agent" · "crow_cli" · "cursor" · "deepagents" · "devin" · "dimcode" · "dirac" · "factory_droid" · "fast_agent" · "gemini" · "github_copilot_cli" · "glm_acp_agent" · "antigravity_acp" · "goose" · "grok_build" · "harn" · "junie" · "kilo" · "kimchi" · "minimax_code" · "minion_code" · "mistral_vibe" · "nova" · "poolside" · "qoder" · "qwen_code" · "sigit" · "stakpak" · "vtcode" · "code_assistant" · "docker_cagent" · "kiro" · "openhands" · "muse_code" · "workspace_acp" - `llm` (string) - `reasoningEffort` (enum) — Harness/model-compatible reasoning effort; omitted = model-compatible fallback for constrained models, otherwise harness default. Explicit values must fit both agentType and llm — agoragentic_acp: not supported; amp_acp: not supported; auggie: not supported; autohand: not supported; cline: not supported; codebuddy_code: not supported; cortex_code: not supported; corust_agent: not supported; crow_cli: not supported; cursor: not supported; deepagents: not supported; devin: not supported; dimcode: not supported; dirac: not supported; factory_droid: not supported; fast_agent: not supported; gemini: not supported; github_copilot_cli: not supported; glm_acp_agent: not supported; antigravity_acp: not supported; goose: not supported; grok_build: not supported; harn: not supported; junie: not supported; kilo: not supported; kimchi: not supported; minimax_code: not supported; minion_code: not supported; mistral_vibe: not supported; nova: not supported; poolside: not supported; qoder: not supported; qwen_code: not supported; sigit: not supported; stakpak: not supported; vtcode: not supported; code_assistant: not supported; docker_cagent: not supported; kiro: not supported; muse_code: not supported; openhands: not supported; hermes: none/minimal/low/medium/high/xhigh/max/ultra; claude_code: low/medium/high/xhigh/max; codex: none/minimal/low/medium/high/xhigh/max; openclaw: not supported; jev: not supported; pi: off/minimal/low/medium/high/xhigh/max; dsh: not supported; kimi_code: not supported; opencode: none/minimal/low/medium/high/xhigh/max; workspace_acp: not supported; models: claude-fable-5-1: low/medium/high/xhigh/max; claude-fable-5: low/medium/high/xhigh/max; claude-opus-5-5: low/medium/high/xhigh/max; claude-opus-5: low/medium/high/xhigh/max; claude-opus-4-8: low/medium/high/xhigh/max; claude-sonnet-5-5: low/medium/high/xhigh/max; claude-sonnet-5: low/medium/high/xhigh/max; claude-sonnet-4-6: low/medium/high/max; claude-haiku-5-5: low/medium/high/xhigh/max; claude-haiku-4-5: not supported; gpt-6.1-sol: low/medium/high/xhigh/max; gpt-6-astra: low/medium/high/xhigh/max; gpt-6-sol: none/low/medium/high/xhigh/max; gpt-6-luna: none/low/medium/high/xhigh/max; gpt-5.6-sol: none/low/medium/high/xhigh/max; gpt-5.6-terra: none/low/medium/high/xhigh/max; gpt-5.6-luna: none/low/medium/high/xhigh/max; gpt-5.5: none/low/medium/high/xhigh; gpt-4.1: not supported; deepseek-v4-pro: low/high/max; deepseek-flash: low/high/max; kimi-k3.1-pro: low/high/max; kimi-k3: low/high/max; kimi-k2.7-code: not supported; minimax-m3: not supported; muse-spark-1.1: minimal/low/medium/high/xhigh; muse-spark-1.2: minimal/low/medium/high/xhigh; muse-spark-1.3: minimal/low/medium/high/xhigh/max; minimax-m3.1-flash: low/medium/high/xhigh/max; gemini-4-argon: low/medium/high; gemini-3.5-flash-lite: low/medium/high; gemini-3.8-flash: low/medium/high; gemini-3.5-flash: low/medium/high; glm-5.3: low/high/max; glm-5.3-flash: low/high/max; grok-4.6: low/medium/high; grok-4.7: low/medium/high/xhigh — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" - `prompt` (string) — max length 100000 - `displayName` (string) — What the agent is called in the console and in channels; omitted = an auto-generated asteroid name — max length 60 - `capabilities` (enum[]) — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `instructions` (object[]) - `name` (string, required) — pattern .*\.md$ - `content` (string) — default: "" - `skills` (object[]) — RFC-0073 skill attachments: store references ({type:'skill', skillId, version?: 'latest'|skr_…}) and GitHub shortcuts ({type:'github', url, ref?, tokenSecretRef?}). Max 20. - type: "skill" (object) - `type` ("skill", required) - `skillId` (string, required) - `version` (string) — default: "latest" - type: "github" (object) - `type` ("github", required) - `url` (string, required) - `ref` (string) — Branch or commit SHA; default branch when omitted. - `tokenSecretRef` (string) — Agent secret key holding a clone token for private repos. - `mcpServers` (object[]) — Remote streamable-HTTP MCP servers this agent connects to. - `type` ("url") — default: "url" - `name` (string, required) — max length 255 - `url` (string, required) - `tools` (object) - `include` (string[]) — default: [] - `exclude` (string[]) — default: [] - `hooks` (object[]) — Shell commands run in the session's pod at lifecycle events ({event, command, matcher?, timeout_s?}). Every harness accepts every event; a hook can stop a prompt or a tool call only on a harness whose own hook system fires that event. Max 20. - `event` (enum, required) — "pre_session_start" · "session_start" · "user_prompt_submit" · "pre_tool_use" · "post_tool_use" · "stop" · "session_end" - `command` (string, required) — max length 4000 - `matcher` (string) — max length 255 - `timeout_s` (integer) - `customData` (object[]) - `id` (string, required) - `name` (string, required) — pattern ^[a-z0-9_]+$ - `kind` (enum, required) — "spreadsheet" · "doc" · "private_api" · "other" - `scope` (enum) — "creator" · "user" — default: "creator" - `description` (string) - `uri` (string) - `config` (map) — default: {} - `metadata` (map) - `environmentId` (string) — The workspace environment this agent is built for: a session boots in it when its create names no `environment_id` (ahead of the workspace default), and a create naming an environment that neither is it nor extends it is 409 `environment_mismatch`. Omitted = no link. - `browserProfile` (enum | string) — The browser this agent's sessions open with when they name none: mine, fresh or user:. Applies to sessions created afterwards — "mine" · "fresh" · "workspace" · "private" - `vaultId` (string) — A workspace vault every session of this agent carries: after the vaults the session create names, ahead of the workspace default. Omitted = no agent vault. - `capabilityPolicies` (map>) — "always_allow" · "always_ask" · "auto" - `nativeToolPolicy` (enum) — Owner-only permission for native pod tools; omitted defaults to always_allow. Supported by Claude Code and Codex after native approval rollout. — "always_allow" · "always_ask" - `hiringPolicy` (enum) — Whether this agent's sessions may bring in other agents as helpers; omitted defaults to allow. deny makes the agent work alone. — "allow" · "deny" - `harness` (string) — workspace_acp only: the public id (hrn_…) of the workspace harness to pin. Required when agentType is workspace_acp. - `handle` (string) — Workspace-scoped @handle. Lower-case, 2–40 chars: [a-z0-9_]. Unique within a workspace. Reserves the address handle@agents.agentsky.dev, returned as `email`; that domain does not receive mail yet. — pattern ^[a-z0-9_]{2,40}$ - `channel_settings` (object) — Channel persona settings for Slack/Lark. - `invitable` (boolean, required) - `description` (string) — max length 500 - `runtime` (object) — BYO-code runtime config. {kind:'endpoint',protocol:'a2a'|'acp'|'mcp',url} or {kind:'image',image}. - option 1 (object) - `kind` ("endpoint", required) - `protocol` (enum, required) — "a2a" · "acp" · "mcp" - `url` (string, required) - option 2 (object) - `kind` ("image", required) - `image` (string, required) Responses: - `201` — Created - `agent` (object, required) — Everything in the list item, plus configuration state. - `id` (string) — The agent id (agent_…) — the only address - `name` (string) - `displayName` (string) - `agentType` (string) - `llm` (string) - `harnessVersion` (string | null) - `reasoningEffort` (string | null) — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" · null - `version` (integer) - `archived` (boolean) - `sessionCount` (integer) - `createdAt` (string) - `description` (string | null) - `prompt` (string | null) — The user prompt layer - `promptVersion` (integer | null) - `capabilities` (enum[]) — The built-in capabilities this agent is granted — the exact set its sessions may execute (enforced; empty = none). Ids are listed at /docs/capabilities. — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `capabilityPolicies` (map>) — Per-capability permission policy map; omitted when no entries exist. always_allow: invoke without user confirmation. always_ask: prompt the session owner before every invocation. auto: reserved for future policy evaluation; currently prompts the session owner like always_ask. — "always_allow" · "always_ask" · "auto" - `nativeToolPolicy` (enum) — Native pod-tool permission, independent of capabilities. Returned on create and detail responses; omitted legacy settings mean always_allow. Only the agent owner can change it, while sessions are idle. — "always_allow" · "always_ask" - `hiringPolicy` (enum) — Whether this agent's sessions may bring in other agents as helpers. allow unless set to deny. Returned on create and detail responses. — "allow" · "deny" - `mcpServers` (object[]) — Declared remote MCP server references, including tool filters; runtime authentication fields are never returned - `type` ("url") — default: "url" - `name` (string, required) — max length 255 - `url` (string, required) - `tools` (object) - `include` (string[]) — default: [] - `exclude` (string[]) — default: [] - `hooks` (object[]) — Shell commands run in the session's pod at lifecycle events - `event` (enum, required) — "pre_session_start" · "session_start" · "user_prompt_submit" · "pre_tool_use" · "post_tool_use" · "stop" · "session_end" - `command` (string, required) — max length 4000 - `matcher` (string) — max length 255 - `timeout_s` (integer) - `metadata` (object) — Your key-merged client metadata - `environmentId` (string | null) — The environment sessions of this agent boot in when their create names none; ahead of the workspace default - `vaultId` (string | null) — The vault every session of this agent carries, after the vaults its create names and ahead of the workspace default - `defaultSessionId` (string | null) — The session conversation verbs target when addressed by agent id - `universe` (string) — Resolved universe slug - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/agents \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"displayName":"Research Agent","agentType":"hermes","llm":"deepseek-v4-pro","capabilities":["exa.search"]}' ``` Example response (201 Created): ```json { "agent": { "id": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "name": "Research Agent", "displayName": "Research Agent", "agentType": "hermes", "llm": "deepseek-v4-pro", "version": 1, "capabilities": [ "exa.search" ], "defaultSessionId": null, "universe": "acme" } } ``` ### GET /agents/{id} Agent detail (scope: read) Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `agent` (object, required) — Everything in the list item, plus configuration state. - `id` (string) — The agent id (agent_…) — the only address - `name` (string) - `displayName` (string) - `agentType` (string) - `llm` (string) - `harnessVersion` (string | null) - `reasoningEffort` (string | null) — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" · null - `version` (integer) - `archived` (boolean) - `sessionCount` (integer) - `createdAt` (string) - `description` (string | null) - `prompt` (string | null) — The user prompt layer - `promptVersion` (integer | null) - `capabilities` (enum[]) — The built-in capabilities this agent is granted — the exact set its sessions may execute (enforced; empty = none). Ids are listed at /docs/capabilities. — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `capabilityPolicies` (map>) — Per-capability permission policy map; omitted when no entries exist. always_allow: invoke without user confirmation. always_ask: prompt the session owner before every invocation. auto: reserved for future policy evaluation; currently prompts the session owner like always_ask. — "always_allow" · "always_ask" · "auto" - `nativeToolPolicy` (enum) — Native pod-tool permission, independent of capabilities. Returned on create and detail responses; omitted legacy settings mean always_allow. Only the agent owner can change it, while sessions are idle. — "always_allow" · "always_ask" - `hiringPolicy` (enum) — Whether this agent's sessions may bring in other agents as helpers. allow unless set to deny. Returned on create and detail responses. — "allow" · "deny" - `mcpServers` (object[]) — Declared remote MCP server references, including tool filters; runtime authentication fields are never returned - `type` ("url") — default: "url" - `name` (string, required) — max length 255 - `url` (string, required) - `tools` (object) - `include` (string[]) — default: [] - `exclude` (string[]) — default: [] - `hooks` (object[]) — Shell commands run in the session's pod at lifecycle events - `event` (enum, required) — "pre_session_start" · "session_start" · "user_prompt_submit" · "pre_tool_use" · "post_tool_use" · "stop" · "session_end" - `command` (string, required) — max length 4000 - `matcher` (string) — max length 255 - `timeout_s` (integer) - `metadata` (object) — Your key-merged client metadata - `environmentId` (string | null) — The environment sessions of this agent boot in when their create names none; ahead of the workspace default - `vaultId` (string | null) — The vault every session of this agent carries, after the vaults its create names and ahead of the workspace default - `defaultSessionId` (string | null) — The session conversation verbs target when addressed by agent id - `universe` (string) — Resolved universe slug - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /agents/{id} Update displayName / capabilities / metadata (scope: write) Omitted fields preserved; capabilities replaced whole (explicit [] revokes all; effective on the next capability call, no restart); metadata key-merged (null deletes a key); optional expectedVersion → 409 version_conflict on mismatch. Path parameters: - `id` (string, required) — The agent id (agent_…) Body (application/json): - `displayName` (string) — max length 60 - `agentType` (enum) — Moves the agent to another engine, checked as a new agent's would be. Sessions keep the engine they started on; new sessions use this one. Not for workspace-harness or image agents. — "hermes" · "claude_code" · "codex" · "openclaw" · "pi" · "dsh" · "kimi_code" · "opencode" · "jev" · "agoragentic_acp" · "amp_acp" · "auggie" · "autohand" · "cline" · "codebuddy_code" · "cortex_code" · "corust_agent" · "crow_cli" · "cursor" · "deepagents" · "devin" · "dimcode" · "dirac" · "factory_droid" · "fast_agent" · "gemini" · "github_copilot_cli" · "glm_acp_agent" · "antigravity_acp" · "goose" · "grok_build" · "harn" · "junie" · "kilo" · "kimchi" · "minimax_code" · "minion_code" · "mistral_vibe" · "nova" · "poolside" · "qoder" · "qwen_code" · "sigit" · "stakpak" · "vtcode" · "code_assistant" · "docker_cagent" · "kiro" · "openhands" · "muse_code" · "workspace_acp" - `llm` (string) — The model new sessions run; must be selectable on the agent's (next) agentType. Omitted with a new agentType = that engine's default model. A reasoning effort the new pair does not support is cleared. - `capabilities` (enum[]) — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `metadata` (map) - `environmentId` (string | null) — The environment sessions of this agent boot in when they name none; null clears, omitted preserves. Applies to sessions created afterwards. - `browserProfile` (enum | string | null) — The browser this agent's sessions open with when they name none: mine, fresh or user:; null clears back to mine. Applies to sessions created afterwards - `vaultId` (string | null) — The vault every session of this agent carries; null clears, omitted preserves. Applies to sessions created afterwards. - `capabilityPolicies` (map>) — "always_allow" · "always_ask" · "auto" - `nativeToolPolicy` (enum) — Owner-only permission for native pod tools. Omitted preserves; changing permission requires idle sessions. There is no session override. — "always_allow" · "always_ask" - `hiringPolicy` (enum) — Whether this agent's sessions may bring in other agents as helpers. Omitted preserves. deny refuses the next helper a session tries to start; sessions created afterwards are not told how to hire. — "allow" · "deny" - `harnessVersion` (string) - `reasoningEffort` (enum | null) — Harness-native reasoning effort; null clears the pin and applies model-compatible normalization. Applies from the next provision/restart. Allowed values depend on agentType — agoragentic_acp: not supported; amp_acp: not supported; auggie: not supported; autohand: not supported; cline: not supported; codebuddy_code: not supported; cortex_code: not supported; corust_agent: not supported; crow_cli: not supported; cursor: not supported; deepagents: not supported; devin: not supported; dimcode: not supported; dirac: not supported; factory_droid: not supported; fast_agent: not supported; gemini: not supported; github_copilot_cli: not supported; glm_acp_agent: not supported; antigravity_acp: not supported; goose: not supported; grok_build: not supported; harn: not supported; junie: not supported; kilo: not supported; kimchi: not supported; minimax_code: not supported; minion_code: not supported; mistral_vibe: not supported; nova: not supported; poolside: not supported; qoder: not supported; qwen_code: not supported; sigit: not supported; stakpak: not supported; vtcode: not supported; code_assistant: not supported; docker_cagent: not supported; kiro: not supported; muse_code: not supported; openhands: not supported; hermes: none/minimal/low/medium/high/xhigh/max/ultra; claude_code: low/medium/high/xhigh/max; codex: none/minimal/low/medium/high/xhigh/max; openclaw: not supported; jev: not supported; pi: off/minimal/low/medium/high/xhigh/max; dsh: not supported; kimi_code: not supported; opencode: none/minimal/low/medium/high/xhigh/max; workspace_acp: not supported; models: claude-fable-5-1: low/medium/high/xhigh/max; claude-fable-5: low/medium/high/xhigh/max; claude-opus-5-5: low/medium/high/xhigh/max; claude-opus-5: low/medium/high/xhigh/max; claude-opus-4-8: low/medium/high/xhigh/max; claude-sonnet-5-5: low/medium/high/xhigh/max; claude-sonnet-5: low/medium/high/xhigh/max; claude-sonnet-4-6: low/medium/high/max; claude-haiku-5-5: low/medium/high/xhigh/max; claude-haiku-4-5: not supported; gpt-6.1-sol: low/medium/high/xhigh/max; gpt-6-astra: low/medium/high/xhigh/max; gpt-6-sol: none/low/medium/high/xhigh/max; gpt-6-luna: none/low/medium/high/xhigh/max; gpt-5.6-sol: none/low/medium/high/xhigh/max; gpt-5.6-terra: none/low/medium/high/xhigh/max; gpt-5.6-luna: none/low/medium/high/xhigh/max; gpt-5.5: none/low/medium/high/xhigh; gpt-4.1: not supported; deepseek-v4-pro: low/high/max; deepseek-flash: low/high/max; kimi-k3.1-pro: low/high/max; kimi-k3: low/high/max; kimi-k2.7-code: not supported; minimax-m3: not supported; muse-spark-1.1: minimal/low/medium/high/xhigh; muse-spark-1.2: minimal/low/medium/high/xhigh; muse-spark-1.3: minimal/low/medium/high/xhigh/max; minimax-m3.1-flash: low/medium/high/xhigh/max; gemini-4-argon: low/medium/high; gemini-3.5-flash-lite: low/medium/high; gemini-3.8-flash: low/medium/high; gemini-3.5-flash: low/medium/high; glm-5.3: low/high/max; glm-5.3-flash: low/high/max; grok-4.6: low/medium/high; grok-4.7: low/medium/high/xhigh — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" - `skills` (object[]) — Replaces the whole skills array; omitted preserves. Applies from the next provision. - type: "skill" (object) - `type` ("skill", required) - `skillId` (string, required) - `version` (string) — default: "latest" - type: "github" (object) - `type` ("github", required) - `url` (string, required) - `ref` (string) — Branch or commit SHA; default branch when omitted. - `tokenSecretRef` (string) — Agent secret key holding a clone token for private repos. - `mcpServers` (object[]) — Replaces the whole MCP server list; omitted preserves. Applies from the next provision. - `type` ("url") — default: "url" - `name` (string, required) — max length 255 - `url` (string, required) - `tools` (object) - `include` (string[]) — default: [] - `exclude` (string[]) — default: [] - `hooks` (object[]) — Replaces the whole hook list; omitted preserves. Applies from the next provision. - `event` (enum, required) — "pre_session_start" · "session_start" · "user_prompt_submit" · "pre_tool_use" · "post_tool_use" · "stop" · "session_end" - `command` (string, required) — max length 4000 - `matcher` (string) — max length 255 - `timeout_s` (integer) - `handle` (string | null) — Workspace-scoped @handle for Slack/Lark mentions. Lower-case, 2–40 chars: [a-z0-9_]. Unique within a workspace; null clears. — pattern ^[a-z0-9_]{2,40}$ - `channel_settings` (object | null) — Channel persona settings for Slack/Lark. null resets to defaults (invitable: true, no description). - `invitable` (boolean, required) - `description` (string) — max length 500 - `runtime` (object | null) — BYO-code runtime config. {kind:'endpoint',protocol:'a2a'|'acp'|'mcp',url} or {kind:'image',image}. null clears (agent runs in the standard sandbox). - option 1 (object) - `kind` ("endpoint", required) - `protocol` (enum, required) — "a2a" · "acp" · "mcp" - `url` (string, required) - option 2 (object) - `kind` ("image", required) - `image` (string, required) - `expectedVersion` (integer) Responses: - `200` — OK - `agent` (object, required) — Everything in the list item, plus configuration state. - `id` (string) — The agent id (agent_…) — the only address - `name` (string) - `displayName` (string) - `agentType` (string) - `llm` (string) - `harnessVersion` (string | null) - `reasoningEffort` (string | null) — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" · null - `version` (integer) - `archived` (boolean) - `sessionCount` (integer) - `createdAt` (string) - `description` (string | null) - `prompt` (string | null) — The user prompt layer - `promptVersion` (integer | null) - `capabilities` (enum[]) — The built-in capabilities this agent is granted — the exact set its sessions may execute (enforced; empty = none). Ids are listed at /docs/capabilities. — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `capabilityPolicies` (map>) — Per-capability permission policy map; omitted when no entries exist. always_allow: invoke without user confirmation. always_ask: prompt the session owner before every invocation. auto: reserved for future policy evaluation; currently prompts the session owner like always_ask. — "always_allow" · "always_ask" · "auto" - `nativeToolPolicy` (enum) — Native pod-tool permission, independent of capabilities. Returned on create and detail responses; omitted legacy settings mean always_allow. Only the agent owner can change it, while sessions are idle. — "always_allow" · "always_ask" - `hiringPolicy` (enum) — Whether this agent's sessions may bring in other agents as helpers. allow unless set to deny. Returned on create and detail responses. — "allow" · "deny" - `mcpServers` (object[]) — Declared remote MCP server references, including tool filters; runtime authentication fields are never returned - `type` ("url") — default: "url" - `name` (string, required) — max length 255 - `url` (string, required) - `tools` (object) - `include` (string[]) — default: [] - `exclude` (string[]) — default: [] - `hooks` (object[]) — Shell commands run in the session's pod at lifecycle events - `event` (enum, required) — "pre_session_start" · "session_start" · "user_prompt_submit" · "pre_tool_use" · "post_tool_use" · "stop" · "session_end" - `command` (string, required) — max length 4000 - `matcher` (string) — max length 255 - `timeout_s` (integer) - `metadata` (object) — Your key-merged client metadata - `environmentId` (string | null) — The environment sessions of this agent boot in when their create names none; ahead of the workspace default - `vaultId` (string | null) — The vault every session of this agent carries, after the vaults its create names and ahead of the workspace default - `defaultSessionId` (string | null) — The session conversation verbs target when addressed by agent id - `universe` (string) — Resolved universe slug - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"displayName":"Research Agent","expectedVersion":1}' ``` ### DELETE /agents/{id} Delete the agent — only with zero live sessions (409 agent_has_sessions) (scope: admin) An agent that never had a session is deleted (outcome deleted). One whose sessions were all deleted is archived instead (outcome archived), so their history keeps naming it: it stays readable, is listed with archived: true, and starts no new sessions — the same state POST /agents/{id}/archive leaves. Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `ok` (enum, required) — true - `outcome` (enum, required) — "deleted" · "archived" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d \ -H "Authorization: Bearer ast_..." ``` ### POST /agents/{id}/archive Archive the agent — read-only, sessions keep running, new sessions rejected (scope: admin) Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `agent` (object, required) — Everything in the list item, plus configuration state. - `id` (string) — The agent id (agent_…) — the only address - `name` (string) - `displayName` (string) - `agentType` (string) - `llm` (string) - `harnessVersion` (string | null) - `reasoningEffort` (string | null) — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" · null - `version` (integer) - `archived` (boolean) - `sessionCount` (integer) - `createdAt` (string) - `description` (string | null) - `prompt` (string | null) — The user prompt layer - `promptVersion` (integer | null) - `capabilities` (enum[]) — The built-in capabilities this agent is granted — the exact set its sessions may execute (enforced; empty = none). Ids are listed at /docs/capabilities. — "embedding.openai" · "embedding.voyage" · "rerank.voyage" · "ocr.mistral" · "exa.search" · "exa.contents" · "tinyfish.fetch" · "tinyfish.browser" · "tinyfish.agent" · "dataforseo.serp" · "dataforseo.ai-keywords" · "dataforseo.llm-responses" · "dataforseo.app-info" · "dataforseo.app-search" · "dataforseo.backlinks" · "dataforseo.business-reviews" · "dataforseo.business-pinterest" · "dataforseo.content-analysis" · "dataforseo.technologies" · "dataforseo.keyword-data" · "dataforseo.labs" · "dataforseo.amazon" · "dataforseo.onpage" · "gptimage.generate" · "rembg.remove-background" · "seedance.generate" · "mm.i2v" · "wan.t2v" · "wan.i2v" · "happyhorse.t2v" · "minimax.h3" · "fish-audio.transcribe" · "code-review" · "exa.answer" · "googlemap.geocode" · "googlemap.places" · "agentmail.list-inboxes" · "tinyfish.search" · "tinyfish.profile-list" · "meta.transcribe" · "minimax.tts" · "elevenlabs.sound" · "elevenlabs.tts" · "elevenlabs.music" · "zai.transcribe" · "openai.transcribe" · "fish-audio.tts" · "google.image" · "openai.image-2.5" · "wan.image" · "logo.lookup" · "openmeteo.weather" · "frankfurter.fx" · "dictionary.define" · "openlibrary.search" · "googledns.resolve" · "qrserver.create" · "recall.meeting-record" · "social-boost.catalog" - `capabilityPolicies` (map>) — Per-capability permission policy map; omitted when no entries exist. always_allow: invoke without user confirmation. always_ask: prompt the session owner before every invocation. auto: reserved for future policy evaluation; currently prompts the session owner like always_ask. — "always_allow" · "always_ask" · "auto" - `nativeToolPolicy` (enum) — Native pod-tool permission, independent of capabilities. Returned on create and detail responses; omitted legacy settings mean always_allow. Only the agent owner can change it, while sessions are idle. — "always_allow" · "always_ask" - `hiringPolicy` (enum) — Whether this agent's sessions may bring in other agents as helpers. allow unless set to deny. Returned on create and detail responses. — "allow" · "deny" - `mcpServers` (object[]) — Declared remote MCP server references, including tool filters; runtime authentication fields are never returned - `type` ("url") — default: "url" - `name` (string, required) — max length 255 - `url` (string, required) - `tools` (object) - `include` (string[]) — default: [] - `exclude` (string[]) — default: [] - `hooks` (object[]) — Shell commands run in the session's pod at lifecycle events - `event` (enum, required) — "pre_session_start" · "session_start" · "user_prompt_submit" · "pre_tool_use" · "post_tool_use" · "stop" · "session_end" - `command` (string, required) — max length 4000 - `matcher` (string) — max length 255 - `timeout_s` (integer) - `metadata` (object) — Your key-merged client metadata - `environmentId` (string | null) — The environment sessions of this agent boot in when their create names none; ahead of the workspace default - `vaultId` (string | null) — The vault every session of this agent carries, after the vaults its create names and ahead of the workspace default - `defaultSessionId` (string | null) — The session conversation verbs target when addressed by agent id - `universe` (string) — Resolved universe slug - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/archive \ -H "Authorization: Bearer ast_..." ``` ### POST /agents/{id}/invite Invite the agent into a team chat: with conversation_id it joins that conversation's roster (RFC-0151); without, returns the Slack install URL (scope: write) Path parameters: - `id` (string, required) — The agent id (agent_…) Body (application/json): - `conversation_id` (string) — Channel conversation id, e.g. slack:C0123:1712345678.000100 Responses: - `200` — OK - `session_id` (string) - `agent_ref` (string) - `agent_type` (string) - `install_url` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/invite \ -H "Authorization: Bearer ast_..." ``` ### PUT /agents/{id}/prompt Save the user prompt layer; applies to sessions started after the save (scope: write) Pass expectedVersion from the agent promptVersion to reject a stale save with 409 version_conflict. Null expects no saved prompt. Omit for legacy unconditional saves. Path parameters: - `id` (string, required) — The agent id (agent_…) Body (application/json): - `prompt` (string, required) — max length 100000 - `expectedVersion` (integer | null) Responses: - `200` — OK - `version` (integer) - `unchanged` (boolean) — Content identical to the active version - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/prompt \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"prompt":"You are a meticulous research agent…"}' ``` ### GET /agents/{id}/prompt/versions Prompt version history (scope: read) Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `versions` (object[], required) - `version` (integer) - `note` (string | null) - `createdAt` (string) - `active` (boolean) - `content` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/prompt/versions' \ -H "Authorization: Bearer ast_..." ``` ### GET /agents/{id}/secrets Declared secret keys + set/unset — never values (scope: read) Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `secrets` (object[], required) - `key` (string) - `description` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/secrets' \ -H "Authorization: Bearer ast_..." ``` ### PUT /agents/{id}/secrets/{key} Set a declared secret's value (write-only) (scope: write) Path parameters: - `id` (string, required) — The agent id (agent_…) - `key` (string, required) Body (application/json): - `value` (string, required) — max length 10000 - `description` (string) — max length 500 Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/secrets/NOTION_TOKEN \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"value":"secret_…"}' ``` ### DELETE /agents/{id}/secrets/{key} Unset a secret (scope: write) Path parameters: - `id` (string, required) — The agent id (agent_…) - `key` (string, required) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/secrets/NOTION_TOKEN \ -H "Authorization: Bearer ast_..." ``` ### PUT /agents/{id}/avatar Upload a custom PNG/JPEG avatar for the agent (≤ 1 MB) (scope: write) Accepts `multipart/form-data` with a `file` field, or raw bytes with `Content-Type: image/png | image/jpeg`. Replaces any previously uploaded avatar; the engine-mark / sky-avatar fallback is only shown when no custom upload exists. 413 payload_too_large past 1 MiB. Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `avatar_url` (string, required) — Absolute URL for the PNG avatar — cache-able for 5 minutes. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/avatar \ -H "Authorization: Bearer ast_..." ``` ### DELETE /agents/{id}/avatar Remove the custom avatar and revert to the engine-mark / sky-avatar fallback (scope: write) Path parameters: - `id` (string, required) — The agent id (agent_…) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/agents/agent_cmtb2f0a8m9k7x51q4ez6r0d/avatar \ -H "Authorization: Bearer ast_..." ``` ### GET /agents/{ref}/avatar.png PNG avatar for an agent — public, no auth required Returns the agent's avatar as a square PNG. Resolution order: 1. owner-uploaded image; 2. engine brand mark; 3. deterministic sky avatar. `ref` is the agent id (`agent_…`), an engine type string (e.g. `codex`), or a catalog handle. Optional `?size=N` (default 64, max 512). Path parameters: - `ref` (string, required) — agent id, engine type, or catalog handle Query parameters: - `size` (integer) — Output side length in pixels (square) Responses: - `200` — PNG image - `404` — Unknown ref - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/agents/ref/avatar.png' \ -H "Authorization: Bearer ast_..." ``` ## Skills ### GET /skills List skills in the resolved universe (scope: read) Query parameters: - `cursor` (string) - `limit` (integer) Responses: - `200` — OK - `skills` (object[], required) - `id` (string, required) - `displayName` (string, required) - `latestRevisionId` (string | null, required) — What `latest` references resolve to. - `attachByDefault` (boolean, required) — Whether every new session in the workspace starts with this skill's latest revision. - `createdAt` (string, required) - `updatedAt` (string, required) - `nextCursor` (string | null) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/skills' \ -H "Authorization: Bearer ast_..." ``` ### POST /skills Create a skill (uploads revision 1) (scope: write) A skill is a universe-scoped, immutably-revisioned file bundle agents load on demand (RFC-0073). Attach it to agents via the `skills` array on create/patch. Responses: - `201` — Created - `skill` (object, required) - `id` (string, required) - `displayName` (string, required) - `latestRevisionId` (string | null, required) — What `latest` references resolve to. - `attachByDefault` (boolean, required) — Whether every new session in the workspace starts with this skill's latest revision. - `createdAt` (string, required) - `updatedAt` (string, required) - `revision` (object, required) - `id` (string, required) - `skillId` (string, required) - `name` (string, required) — Frontmatter name — the skill's identity name. - `description` (string, required) - `files` (object[], required) - `path` (string, required) - `size` (integer, required) - `sha256` (string, required) - `executable` (boolean, required) - `createdAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/skills \ -H "Authorization: Bearer ast_..." ``` ### GET /skills/usage Skill lookups and misses in the resolved universe (scope: read) Counts the times an agent asked for a workspace skill by name (`actl skill get`): `views` where it existed, `misses` where it did not, per name, since `since` (default 30 days). A skill read straight from an agent's skills directory is not counted. Query parameters: - `since` (string) Responses: - `200` — OK - `skills` (object[], required) - `name` (string, required) - `views` (integer, required) - `misses` (integer, required) - `lastSeen` (string, required) - `topMissed` (object[], required) - `name` (string, required) - `misses` (integer, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/skills/usage' \ -H "Authorization: Bearer ast_..." ``` ### GET /skills/{skillId} Skill detail (scope: read) Path parameters: - `skillId` (string, required) — The skill id (skill_…) Responses: - `200` — OK - `skill` (object, required) - `id` (string, required) - `displayName` (string, required) - `latestRevisionId` (string | null, required) — What `latest` references resolve to. - `attachByDefault` (boolean, required) — Whether every new session in the workspace starts with this skill's latest revision. - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/skills/skillId' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /skills/{skillId} Make a skill always available, or stop (scope: write) With `attachByDefault: true`, every session created in the workspace from then on is provisioned with the skill's latest revision, unless the session is created with `defaults: "none"`. Sessions of agents that cannot take skills start without it. Path parameters: - `skillId` (string, required) — The skill id (skill_…) Body (application/json): - `attachByDefault` (boolean, required) Responses: - `200` — OK - `skill` (object, required) - `id` (string, required) - `displayName` (string, required) - `latestRevisionId` (string | null, required) — What `latest` references resolve to. - `attachByDefault` (boolean, required) — Whether every new session in the workspace starts with this skill's latest revision. - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/skills/skillId \ -H "Authorization: Bearer ast_..." ``` ### DELETE /skills/{skillId} Delete a skill and all its revisions (scope: write) 409 skill_in_use while any agent references the skill. Path parameters: - `skillId` (string, required) — The skill id (skill_…) Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/skills/skillId \ -H "Authorization: Bearer ast_..." ``` ### GET /skills/{skillId}/revisions List a skill's revisions, newest first (scope: read) Path parameters: - `skillId` (string, required) — The skill id (skill_…) Responses: - `200` — OK - `revisions` (object[], required) - `id` (string, required) - `skillId` (string, required) - `name` (string, required) — Frontmatter name — the skill's identity name. - `description` (string, required) - `files` (object[], required) - `path` (string, required) - `size` (integer, required) - `sha256` (string, required) - `executable` (boolean, required) - `createdAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/skills/skillId/revisions' \ -H "Authorization: Bearer ast_..." ``` ### POST /skills/{skillId}/revisions Create a revision — a complete snapshot (scope: write) Omitted files are not carried over. The new SKILL.md name must match the skill's name (400 name_mismatch). Responses: - `201` — Created - `revision` (object, required) - `id` (string, required) - `skillId` (string, required) - `name` (string, required) — Frontmatter name — the skill's identity name. - `description` (string, required) - `files` (object[], required) - `path` (string, required) - `size` (integer, required) - `sha256` (string, required) - `executable` (boolean, required) - `createdAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/skills/skillId/revisions \ -H "Authorization: Bearer ast_..." ``` ### GET /skills/{skillId}/revisions/{revisionId} Skill revision detail (metadata + file manifest) (scope: read) Path parameters: - `skillId` (string, required) — The skill id (skill_…) - `revisionId` (string, required) — The skill revision id (skr_…) Responses: - `200` — OK - `revision` (object, required) - `id` (string, required) - `skillId` (string, required) - `name` (string, required) — Frontmatter name — the skill's identity name. - `description` (string, required) - `files` (object[], required) - `path` (string, required) - `size` (integer, required) - `sha256` (string, required) - `executable` (boolean, required) - `createdAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/skills/skillId/revisions/revisionId' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /skills/{skillId}/revisions/{revisionId} Delete one revision (scope: write) 400 last_revision for a skill's only revision; 409 revision_in_use while pinned by an agent; deleting the latest repoints latestRevisionId. Path parameters: - `skillId` (string, required) — The skill id (skill_…) - `revisionId` (string, required) — The skill revision id (skr_…) Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/skills/skillId/revisions/revisionId \ -H "Authorization: Bearer ast_..." ``` ### GET /skills/{skillId}/revisions/{revisionId}/content Read a UTF-8 skill file (scope: read) Reads only a file in the authorized immutable revision. Default JSON text mode returns 415 for binary files. raw=1 returns the original bytes as an attachment, including binaries. Absent files return 404. Responses are not cached. Path parameters: - `skillId` (string, required) — The skill id (skill_…) - `revisionId` (string, required) — The skill revision id (skr_…) Query parameters: - `path` (string, required) — Exact relative file path, such as SKILL.md - `raw` (string) — Return original file bytes as an attachment instead of the default JSON text response. Responses: - `200` — OK - `path` (string, required) - `content` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/skills/skillId/revisions/revisionId/content?path=report.md' \ -H "Authorization: Bearer ast_..." ``` ## Capabilities overview ### GET /capabilities Everything your agents can use, in one list (scope: read) Connected apps (yours, shared with the workspace, and the workspace's services), tools from the workspace's default environment and the gateway, plugins and their MCP servers, vault MCP servers and secrets, browser logins, what your Macs lend (folders, browser, screen, apps and files) and your plans — each with where it works (cloud, mac, both), who shared it, its status (on, connected, needs_signin, needs_permission, off) and what it costs (free, credits, plan, provider). Read-only: `manage` names the existing routes that change each one. A part that cannot be read is listed in `unavailable` and the rest still answer. A workspace key sees the workspace's parts; Macs and your own browser need the person's own key. Responses: - `200` — OK - `data` (object[], required) - `kind` (enum, required) — "connector" · "tool" · "plugin" · "mcp_server" · "vault" · "browser" · "mac" · "plan" - `id` (string, required) — Stable within its kind: a connection id, a tool group id, a plugin name, a vault secret id, `:` for a Mac. - `name` (string, required) - `detail` (string | null, required) — One more fact: the account, the address, the variable names, what a Mac lends now. - `where` (enum, required) — "cloud" · "mac" · "both" - `shared_by` (object, required) - `type` (enum, required) — "me" · "workspace" · "member" - `name` (string) — The member, when type is member - `status` (enum, required) — "on" · "connected" · "needs_signin" · "needs_permission" · "off" - `cost` (enum, required) — free, credits from the balance, the person's own plan, or external provider pricing — "free" · "credits" · "plan" · "provider" - `manage` (enum, required) — The existing page and API group that change it — "connectors" · "tools" · "plugins" · "vaults" · "browser-logins" · "mac" · "plans" - `machine` (object) — On a mac capability: the Mac that lends it - `id` (string) - `name` (string) - `online` (boolean) - `unavailable` (object[], required) — Parts that could not be read right now, so a missing kind is not mistaken for none. - `kind` (string, required) - `message` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/capabilities' \ -H "Authorization: Bearer ast_..." ``` ## Plugins ### GET /plugins Installed plugins, each with the skills it installed (scope: read) RFC-0155. A plugin is a Claude Code plugin; an installed plugin is the set of workspace skills named plugin:/, grouped here by plugin. Responses: - `200` — OK - `plugins` (object[]) - `name` (string) - `skills` (object[]) - `id` (string, required) - `displayName` (string, required) - `latestRevisionId` (string | null, required) — What `latest` references resolve to. - `attachByDefault` (boolean, required) — Whether every new session in the workspace starts with this skill's latest revision. - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/plugins' \ -H "Authorization: Bearer ast_..." ``` ### POST /plugins Install or update a plugin from a public GitHub source (scope: write) source is github:owner/repo[/path][@ref] or a github.com URL to a folder in Claude Code's plugin format (.claude-plugin/plugin.json, skills/, commands/, .mcp.json). skills// become workspace skills as they are and commands/.md become skills whose SKILL.md is the command's prompt, all attached to every new session. Installing again updates in place: kept parts get a new revision, dropped parts are deleted (or switched off when a session holds them). Sub-agents, hooks and local MCP servers are listed under skipped. connectors lists each .mcp.json server, the AgentSky connector app it maps to, whether the caller has that connection, and the link to connect it. Body (application/json): - `source` (string, required) - `mcp_tokens` (map) — Bearer tokens for the plugin's remote MCP servers, by server name. Write-only; stored sealed. Responses: - `201` — Installed - `plugin` (object) - `name` (string) - `version` (string | null) - `description` (string) - `source` (string) - `skills` (object[]) - `id` (string, required) - `displayName` (string, required) - `latestRevisionId` (string | null, required) — What `latest` references resolve to. - `attachByDefault` (boolean, required) — Whether every new session in the workspace starts with this skill's latest revision. - `createdAt` (string, required) - `updatedAt` (string, required) - `connectors` (object[]) - `server` (string) - `app` (string | null) - `transport` (enum) — "remote" · "local" - `connected` (boolean) - `connect_url` (string | null) - `skipped` (object[]) - `part` (string) - `reason` (string) - `removed` (object[]) - `part` (string) - `result` (enum) — "deleted" · "switched_off" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/plugins \ -H "Authorization: Bearer ast_..." ``` ### PATCH /plugins/{name} Switch an installed plugin on or off (scope: write) Off: its skills stop attaching to new sessions and plugin sessions refuse to start. A plugin a workspace admin blocked cannot be switched on (403 plugin_blocked). Path parameters: - `name` (string, required) Body (application/json): - `enabled` (boolean, required) Responses: - `200` — OK - `name` (string) - `enabled` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/plugins/name \ -H "Authorization: Bearer ast_..." ``` ### DELETE /plugins/{name} Remove an installed plugin (scope: write) Deletes the plugin's skills; a skill a session still holds is switched off (no longer attached to new sessions) instead. Path parameters: - `name` (string, required) Responses: - `200` — Removed - `name` (string) - `parts` (object[]) - `part` (string) - `result` (enum) — "deleted" · "switched_off" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/plugins/name \ -H "Authorization: Bearer ast_..." ``` ### POST /plugins/{name}/sessions Start a plugin session (scope: write) RFC-0155 §3. The session starts with only the plugin's skills (not the workspace's default skills) and its remote MCP servers, and its calls to connected apps are limited to the apps the plugin declared: any other app is refused (capability_denied, reason plugin_scope). agent defaults to the workspace's Sky. Answers the created session. Path parameters: - `name` (string, required) Body (application/json): - `message` (string, required) - `agent` (string) - `title` (string) Responses: - `201` — Created - `id` (string) — sess_… identifier - `agentId` (string | null) — The agent id (agent_…) - `title` (string | null) - `status` (enum) — provisioning = the sandbox is booting or installing the environment's packages (a turn sent meanwhile waits for it); running = a turn is in flight; terminated = archived or the last runtime was destroyed. Use archived for the durable archive receipt — "provisioning" · "idle" · "running" · "terminated" - `provisioningStage` (string | null) — Which part of its boot the session's computer is in, as the platform observed it: creating_computer until the sandbox provider reports the computer exists, then starting_agent until the agent in it answers. A provider that does not report the computer's creation stays at creating_computer. Null once the computer is up and whenever it is not booting. — "creating_computer" · "starting_agent" · null - `archived` (boolean) — True only after this Session's archive mark was durably written; false for an active Session whose runtime was destroyed - `agentType` (string) - `llm` (string) — The model this session runs on (PATCH to change; the agent's model is unchanged) - `harnessVersion` (string) — Harness release the session runs: the one its serving pod booted, the restored checkpoint's for a native-fork handoff, the agent's pin for ACP harnesses, otherwise the current release - `reasoningEffort` (string | null) — The model-compatible effort resolved from the session pin, then agent pin. Constrained models use an allowed fallback; no-setting models return null. Unpinned unconstrained models return null for the harness default. - `subscriptionApiFallback` (boolean) — Opt-in to paid API continuation after all subscription accounts exhaust, preserving the same supported explicit model and wallet/budget gates - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription — "platform" · "account" - `idleTimeoutSeconds` (integer | null) — Seconds the session's machine stays awake after a turn ends before it is paused; null = the platform default of 60. Set it when people use apps on the session's exposed ports after the agent finishes - `browserProfile` (string) — Resolved browser: the session's choice, then the agent's default, then mine. mine is the browser of the person whose turn it is; fresh is signed out; user: is the sites that person shared with whoever runs the turn. Applied before the next browser turn when browser profiles are enabled. - `browserLoginsSavedAt` (string | null) — Most recent browser login save-back time, or null when this session has not saved changed logins. - `default` (boolean) — Is this the agent's default session - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `metadata` (object) - `budget` (object | null) — Hard spend cap, or null when the session has none. A reached budget pauses the session (402 budget_reached on new work) until the cap is raised above consumed_cost or removed — removal is one-way. - `parentSessionId` (string | null) — The session this one came from: a helper names the session that hired it, a handoff child names the session it continued; null for a root. Every session a task's root produced, with its role, is GET /tasks/{id}?by=session on any member's id. - `parentRelation` (string | null) — How this session relates to parentSessionId: `helper` was hired by it (actl hire or the orchestration API), `handoff` continued its conversation on another agent; null for a root. — "helper" · "handoff" · null - `resources` (object[]) — The files, GitHub repositories, and read-only memory stores mounted into the session, oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `workspaceDefaults` (object | null) — What the session started with because its workspace made it a default; everything else it carries was named at creation. Null when it took nothing. - `contextFiles` (object | null) — The workspace files the session loaded into its instructions when its sandbox was last provisioned (GET /workspace-context describes the set); null before that and for a session without its workspace's store. Every other file in the store is read on demand. - `hostedBySeller` (object) — Present only when the session runs on its agent's maker's own server instead of a sandbox: what is sent to the session goes to this host. `agentType` and `llm` then name a stand-in that never runs. - `host` (string, required) - `attachments` (object[]) — Everything the session started with: its environment, vaults, mounted files and memory stores, and the workspace's always-available skills. The skills of the session's agent are on the agent and are not repeated here. - `type` (enum, required) — "environment" · "vault" · "file" · "skill" · "memory_store" - `id` (string, required) — The id of the environment, vault, file, skill or memory store - `mountPath` (string | null, required) — Where a file or memory store is mounted; null for the other types - `source` (enum, required) — default = the workspace supplied it; explicit = it was named when the session was created. A memory store on a session created before 2026-10-06 reads as explicit — "default" · "explicit" - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/plugins/name/sessions \ -H "Authorization: Bearer ast_..." ``` ### PUT /plugins/{name}/policy Set a plugin's workspace policy (scope: write, owner or admin) recommended, default (installs it now, from source or the plugin's recorded source), blocked (cannot be installed or switched on; an installed one is switched off), or null to clear. Path parameters: - `name` (string, required) Body (application/json): - `policy` (string | null, required) — "recommended" · "default" · "blocked" · null - `source` (string) Responses: - `200` — OK - `name` (string) - `policy` (string | null) - `source` (string | null) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/plugins/name/policy \ -H "Authorization: Bearer ast_..." ``` ### GET /plugins/marketplaces Plugins the workspace's marketplaces list (scope: read) Each Claude Code marketplace (.claude-plugin/marketplace.json) the workspace added, or AgentSky's defaults when it added none, with each plugin's install source, workspace policy and whether it is installed. Responses: - `200` — OK - `marketplaces` (object[]) - `source` (string) - `name` (string) - `workspace` (boolean) - `error` (string) - `plugins` (object[]) - `name` (string) - `description` (string) - `version` (string | null) - `category` (string | null) - `source` (string | null) - `policy` (string | null) - `installed` (boolean) - `enabled` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/plugins/marketplaces' \ -H "Authorization: Bearer ast_..." ``` ### POST /plugins/marketplaces Add a marketplace (scope: write, owner or admin) Body (application/json): - `source` (string, required) Responses: - `201` — Added - `source` (string) - `name` (string) - `plugins` (object[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/plugins/marketplaces \ -H "Authorization: Bearer ast_..." ``` ### DELETE /plugins/marketplaces Remove a marketplace (scope: write, owner or admin) Query parameters: - `source` (string, required) Responses: - `200` — Removed - `source` (string) - `removed` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/plugins/marketplaces \ -H "Authorization: Bearer ast_..." ``` ## Reflection ### GET /reflection_proposals Proposed skill and lesson changes from post-task reflection (scope: read) After a workspace-visible session ends, a reviewer may propose a skill patch, a new skill, or a `/lessons/` note. Nothing changes until a workspace owner or admin approves. Newest first, at most 100. Query parameters: - `status` (string) Responses: - `200` — OK - `reflection_proposals` (object[], required) - `id` (string) - `object` (enum) — "reflection_proposal" - `session_id` (string) - `kind` (enum) — "patch_skill" · "create_skill" · "save_lesson" - `target_skill_id` (string | null) - `target_store_id` (string | null) - `path` (string | null) - `payload` (object) - `evidence` (object[]) - `status` (enum) — "pending" · "approved" · "rejected" · "superseded" - `decided_by` (string | null) - `decided_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/reflection_proposals' \ -H "Authorization: Bearer ast_..." ``` ### POST /reflection_proposals/{id}/approve Approve a proposal: write it through the skill store or memory store (scope: write, owner or admin) A target that changed since the proposal was made answers 409 `stale_target` and marks the proposal superseded. Path parameters: - `id` (string, required) Responses: - `200` — OK - `reflection_proposal` (object) - `id` (string) - `object` (enum) — "reflection_proposal" - `session_id` (string) - `kind` (enum) — "patch_skill" · "create_skill" · "save_lesson" - `target_skill_id` (string | null) - `target_store_id` (string | null) - `path` (string | null) - `payload` (object) - `evidence` (object[]) - `status` (enum) — "pending" · "approved" · "rejected" · "superseded" - `decided_by` (string | null) - `decided_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/reflection_proposals/sess_cmtb7q2j/approve \ -H "Authorization: Bearer ast_..." ``` ### POST /reflection_proposals/{id}/reject Reject a proposal; nothing is written (scope: write, owner or admin) Path parameters: - `id` (string, required) Responses: - `200` — OK - `reflection_proposal` (object) - `id` (string) - `object` (enum) — "reflection_proposal" - `session_id` (string) - `kind` (enum) — "patch_skill" · "create_skill" · "save_lesson" - `target_skill_id` (string | null) - `target_store_id` (string | null) - `path` (string | null) - `payload` (object) - `evidence` (object[]) - `status` (enum) — "pending" · "approved" · "rejected" · "superseded" - `decided_by` (string | null) - `decided_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/reflection_proposals/sess_cmtb7q2j/reject \ -H "Authorization: Bearer ast_..." ``` ## Files ### POST /files Upload a file — universe-scoped; images are readable back, other uploads are write-once (scope: write) multipart/form-data with a `file` part and an optional `expires_in_seconds` (3600–7776000). 32 MiB per file (413 file_too_large); upload larger files through POST /files/uploads. Reference the returned id from a message as `{"type":"image","source":{"type":"file","file_id":"file_…"}}` or a `document` block. Responses: - `200` — OK - `type` (enum, required) — "file" - `id` (string, required) — file_… - `created_at` (string, required) - `filename` (string, required) - `mime_type` (string, required) - `size_bytes` (integer, required) - `downloadable` (boolean, required) — true for image uploads (readable within the workspace) and for the per-session copies minted when a file rides a message; false for other uploads (write-once) - `attach_by_default` (boolean, required) — Whether every new session in the workspace starts with a read-only copy under /workspace/shared/ - `expires_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/files \ -H "Authorization: Bearer ast_..." \ -F "file=@report.pdf" ``` Example response (200 OK): ```json { "type": "file", "id": "file_cmtb9x4p", "created_at": "2026-09-10T18:00:00.000Z", "filename": "design.pdf", "mime_type": "application/pdf", "size_bytes": 102400, "downloadable": false, "expires_at": null } ``` ### GET /files List files — your universe's uploads, or one session's files (scope: read) Without `scope_id`, the universe's uploads. `scope_id=sess_…` lists that session's files: the copies minted from message content, channel attachments, and agent-stored files. `ids[]` (≤100) returns exactly those files in one page and cannot be combined with page/limit. Query parameters: - `limit` (integer) - `page` (string) — A next_page cursor - `ids[]` (array) - `scope_id` (string) — sess_… Responses: - `200` — OK - `data` (object[], required) — Newest first - `type` (enum, required) — "file" - `id` (string, required) — file_… - `created_at` (string, required) - `filename` (string, required) - `mime_type` (string, required) - `size_bytes` (integer, required) - `downloadable` (boolean, required) — true for image uploads (readable within the workspace) and for the per-session copies minted when a file rides a message; false for other uploads (write-once) - `attach_by_default` (boolean, required) — Whether every new session in the workspace starts with a read-only copy under /workspace/shared/ - `expires_at` (string | null, required) - `next_page` (string | null, required) — Pass back as ?page= for the next page - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/files' \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "data": [ { "type": "file", "id": "file_cmtb9x4p", "created_at": "2026-09-10T18:00:00.000Z", "filename": "design.pdf", "mime_type": "application/pdf", "size_bytes": 102400, "downloadable": false, "expires_at": null } ], "next_page": null } ``` ### GET /files/usage Storage your workspace's uploads take (scope: read) Counts the universe's uploads and sums their bytes. There is no storage cap (`storage_limit_bytes` is null); each file is bounded by `max_file_bytes`. Responses: - `200` — OK - `type` (enum, required) — "files_usage" - `file_count` (integer, required) — The workspace's uploads, expired and deleted ones left out - `size_bytes` (integer, required) — The bytes those uploads take - `max_file_bytes` (integer, required) — The largest single file an upload accepts (50 GiB) - `storage_limit_bytes` (null, required) — Always null: a workspace has no storage cap - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/files/usage' \ -H "Authorization: Bearer ast_..." ``` ### POST /files/uploads Start a resumable upload — for a file of any size up to 50 GiB (scope: write) JSON `{filename, size_bytes, mime_type?, expires_in_seconds?}` (expires_in_seconds 3600–7776000; omitted, the file never expires). Send the bytes to `upload_url` as `PUT` requests carrying `Content-Range: bytes -/`, `chunk_size` bytes at a time: each answers 308 with `Range: bytes=0-` until the final chunk answers 200. After a dropped connection, `PUT` with no body and `Content-Range: bytes */` answers the same 308 or 200, and the upload continues from the byte after the `Range`. The session lasts a week. 413 file_too_large past 50 GiB. Body (application/json): - `filename` (string, required) — max length 255 - `size_bytes` (integer, required) - `mime_type` (string) - `expires_in_seconds` (integer) Responses: - `200` — OK - `type` (enum, required) — "file_upload" - `id` (string, required) — fupl_…; pass to /files/uploads/{upload_id}/complete - `upload_url` (string, required) — Where the bytes go, in Content-Range chunks. Needs no credentials: the URL is the capability, so keep it private. - `chunk_size` (integer, required) — The chunk size to send; every chunk but the last must be exactly this long - `size_bytes` (integer, required) - `filename` (string, required) - `mime_type` (string, required) - `expires_at` (string, required) — When the upload session lapses; start a new one after - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/files/uploads \ -H "Authorization: Bearer ast_..." ``` ### POST /files/uploads/{upload_id}/complete Finish a resumable upload into a file (scope: write) Once every byte is in, answers the File; retrying answers the same File. 409 upload_incomplete while bytes are missing; 410 upload_expired once the session has lapsed. Large files can take a while to complete: allow a few minutes. Path parameters: - `upload_id` (string, required) — The upload id (fupl_…) Responses: - `200` — OK - `type` (enum, required) — "file" - `id` (string, required) — file_… - `created_at` (string, required) - `filename` (string, required) - `mime_type` (string, required) - `size_bytes` (integer, required) - `downloadable` (boolean, required) — true for image uploads (readable within the workspace) and for the per-session copies minted when a file rides a message; false for other uploads (write-once) - `attach_by_default` (boolean, required) — Whether every new session in the workspace starts with a read-only copy under /workspace/shared/ - `expires_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/files/uploads/upload_id/complete \ -H "Authorization: Bearer ast_..." ``` ### GET /files/{file_id} File metadata (scope: read) Path parameters: - `file_id` (string, required) — The file id (file_…) Responses: - `200` — OK - `type` (enum, required) — "file" - `id` (string, required) — file_… - `created_at` (string, required) - `filename` (string, required) - `mime_type` (string, required) - `size_bytes` (integer, required) - `downloadable` (boolean, required) — true for image uploads (readable within the workspace) and for the per-session copies minted when a file rides a message; false for other uploads (write-once) - `attach_by_default` (boolean, required) — Whether every new session in the workspace starts with a read-only copy under /workspace/shared/ - `expires_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/files/file_cmtb9x4p' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /files/{file_id} Make an upload always available, or stop (scope: write) With `attach_by_default: true`, every session created in the workspace from then on starts with a read-only copy at `/workspace/shared/`, unless the session is created with `defaults: "none"`. Only a workspace upload that does not expire can carry the flag (400 otherwise). A file whose name is already taken in a session, or that would take the set past 64 MiB or 50 files, is skipped for that session; the session still starts. Path parameters: - `file_id` (string, required) — The file id (file_…) Body (application/json): - `attach_by_default` (boolean, required) Responses: - `200` — OK - `type` (enum, required) — "file" - `id` (string, required) — file_… - `created_at` (string, required) - `filename` (string, required) - `mime_type` (string, required) - `size_bytes` (integer, required) - `downloadable` (boolean, required) — true for image uploads (readable within the workspace) and for the per-session copies minted when a file rides a message; false for other uploads (write-once) - `attach_by_default` (boolean, required) — Whether every new session in the workspace starts with a read-only copy under /workspace/shared/ - `expires_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/files/file_cmtb9x4p \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"attach_by_default":true}' ``` ### DELETE /files/{file_id} Delete a file (scope: write) Soft delete: the file stops resolving everywhere; sessions that already received a copy keep it. Path parameters: - `file_id` (string, required) — The file id (file_…) Responses: - `200` — OK - `type` (enum, required) — "file_deleted" - `id` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/files/file_cmtb9x4p \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "type": "file_deleted", "id": "file_cmtb9x4p" } ``` ### GET /files/{file_id}/content Download a file's bytes (scope: read) Only `downloadable` files (an image upload, readable within the workspace, and every session-scoped file: message copies, channel attachments, agent outputs); other uploads answer 400 not_downloadable. 404 once the file has expired. A file over 32 MiB answers 302 to a short-lived storage URL that serves the bytes and its own byte ranges. Otherwise a single `Range: bytes=…` answers 206 with that span, so an interrupted download resumes; `ETag` is the file's SHA-256, and an `If-Range` naming any other validator answers the whole file. Path parameters: - `file_id` (string, required) — The file id (file_…) Responses: - `200` — The bytes, with content-type, content-disposition, Accept-Ranges and ETag - `206` — The requested span, with Content-Range - `302` — A file over 32 MiB: Location is a short-lived storage URL that serves its bytes - `416` — The range starts past the end of the file; Content-Range names its size - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/files/file_cmtb9x4p/content' \ -H "Authorization: Bearer ast_..." ``` ## Environments ### GET /environments List the universe's environments, oldest first (scope: read) Responses: - `200` — OK - `environments` (object[], required) - `id` (string, required) — env_… - `name` (string, required) - `config` (object, required) — `packages` are installed into the image at build time — an environment is built when it is created and rebuilt when its config changes, so a session starting from it pays no install cost. `networking` is the egress policy its sessions run under; `limited` needs `allow_package_managers` when packages are declared, or the install could never succeed. - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false - `is_default` (boolean, required) — Whether a new session that names no environment uses this one. At most one per workspace; setting it on one clears it on the others. - `extends` (string | null, required) — The env_… id of the parent environment this one extends, or null. Immutable after create. Child packages, tools.disabled and networking compose on the parent's at write time. - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/environments' \ -H "Authorization: Bearer ast_..." ``` ### POST /environments Create an environment (scope: write) The image build starts immediately; the environment is usable as `environment_id` on POST /sessions as soon as it exists, and a session created against a still-building environment waits for the build. Supplying `extends` (an `env_…` id in the same workspace) composes packages, tools.disabled and networking from the parent at write time and records the lineage. `extends` is immutable after create; chain depth is capped at 16. Body (application/json): - `name` (string, required) — max length 128 - `config` (object) — default: {"type":"cloud"} - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false - `extends` (string) Responses: - `201` — Created - `environment` (object, required) - `id` (string, required) — env_… - `name` (string, required) - `config` (object, required) — `packages` are installed into the image at build time — an environment is built when it is created and rebuilt when its config changes, so a session starting from it pays no install cost. `networking` is the egress policy its sessions run under; `limited` needs `allow_package_managers` when packages are declared, or the install could never succeed. - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false - `is_default` (boolean, required) — Whether a new session that names no environment uses this one. At most one per workspace; setting it on one clears it on the others. - `extends` (string | null, required) — The env_… id of the parent environment this one extends, or null. Immutable after create. Child packages, tools.disabled and networking compose on the parent's at write time. - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/environments \ -H "Authorization: Bearer ast_..." ``` ### GET /environments/{id} One environment (scope: read) Path parameters: - `id` (string, required) — The environment id (env_…) Responses: - `200` — OK - `environment` (object, required) - `id` (string, required) — env_… - `name` (string, required) - `config` (object, required) — `packages` are installed into the image at build time — an environment is built when it is created and rebuilt when its config changes, so a session starting from it pays no install cost. `networking` is the egress policy its sessions run under; `limited` needs `allow_package_managers` when packages are declared, or the install could never succeed. - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false - `is_default` (boolean, required) — Whether a new session that names no environment uses this one. At most one per workspace; setting it on one clears it on the others. - `extends` (string | null, required) — The env_… id of the parent environment this one extends, or null. Immutable after create. Child packages, tools.disabled and networking compose on the parent's at write time. - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/environments/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /environments/{id} Rename an environment or change its config (scope: write) Omitted fields are left as they are. A changed `config` rebuilds the image; sessions already running keep the image they started on. Path parameters: - `id` (string, required) — The environment id (env_…) Body (application/json): - `name` (string) — max length 128 - `config` (object) - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false Responses: - `200` — OK - `environment` (object, required) - `id` (string, required) — env_… - `name` (string, required) - `config` (object, required) — `packages` are installed into the image at build time — an environment is built when it is created and rebuilt when its config changes, so a session starting from it pays no install cost. `networking` is the egress policy its sessions run under; `limited` needs `allow_package_managers` when packages are declared, or the install could never succeed. - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false - `is_default` (boolean, required) — Whether a new session that names no environment uses this one. At most one per workspace; setting it on one clears it on the others. - `extends` (string | null, required) — The env_… id of the parent environment this one extends, or null. Immutable after create. Child packages, tools.disabled and networking compose on the parent's at write time. - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/environments/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /environments/{id} Delete an environment (scope: write) Path parameters: - `id` (string, required) — The environment id (env_…) Responses: - `204` — Deleted — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/environments/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### PUT /environments/{id}/listing Publish one of your own environments as a listing (scope: write) Creates or replaces the listing for this environment on the shelf, visible to the caller's workspace until a submission of it is approved. The environment must belong to the caller's workspace. A public listing's handle cannot change (409 `listing_public`). Answers 404 when the shelf is not enabled for this workspace or the environment is not found. Answers 503 `not_migrated` until the shelf migration is applied. Path parameters: - `id` (string, required) — The environment id (env_…) Body (application/json): - `handle` (string, required) — 2 to 40 lower-case letters, digits or underscores; unique across environment listings - `name` (string, required) — max length 128 - `tagline` (string) — max length 140 - `description` (string) — max length 4000 - `categories` (enum[]) — Defaults to general — "websites" · "content" · "video" · "code" · "research" · "general" Responses: - `200` — OK - `listing` (object, required) - `id` (string, required) — The listing id (lst_…) - `handle` (string, required) — Globally unique within environment listings; lower-case letters, digits and underscores - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `packages` (object | null, required) — The listed environment's package configuration, or null when it has none - `tools` (object | null, required) — The listed environment's tool policy, or null when it has none - `networking` (object | null, required) — The listed environment's networking configuration, or null when it has none - `installs` (integer, required) — How many workspaces have installed this listing - `seller` (object | null, required) — Who published it to other workspaces, by the name they chose; null while it is visible only to its own workspace - `visibility` (enum, required) — workspace = visible only to the owning workspace; public = approved, and visible to every workspace the shelf is open to. Other workspaces read and install the approved version; the owner's later edits wait for a new approval — "workspace" · "public" - `conformance` (object | null, required) — The smoke run the approved version passed; null for a listing that is not public - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/environments/sess_cmtb7q2j/listing \ -H "Authorization: Bearer ast_..." ``` ### DELETE /environments/{id}/listing Remove the listing for one of your own environments (scope: write) Delists the environment, withdrawing a waiting submission and ending a public one; the environment itself and any installed copies in other workspaces are not touched. Answers 404 when the shelf is not enabled, the environment is not found, or it has no listing. Answers 503 `not_migrated` until the shelf migration is applied. Path parameters: - `id` (string, required) — The environment id (env_…) Responses: - `204` — Delisted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/environments/sess_cmtb7q2j/listing \ -H "Authorization: Bearer ast_..." ``` ### GET /environments/{id}/listing/submission Where your environment's listing stands with the public shelf (scope: read) What is missing before it can be submitted, the latest submission and its smoke run, and the approved version other workspaces see. Answers 404 when the shelf is not enabled for this workspace or the environment has no listing. Path parameters: - `id` (string, required) — The environment id (env_…) Responses: - `200` — OK - `missing` (string[], required) — What still has to be true before it can be submitted, in words; empty when it can - `submission` (object | null, required) — The latest submission; null when it has never been submitted - `published` (object | null, required) — The approved version other workspaces read and install; null when it is not public - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and nothing has been approved since - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/environments/sess_cmtb7q2j/listing/submission' \ -H "Authorization: Bearer ast_..." ``` ### POST /environments/{id}/listing/submission Submit your environment's listing for the public shelf (scope: write) Stores the listing and the environment as they are now and starts a smoke run, paid from your wallet: the platform's default engine starts a session on the environment, checks that every listed package is installed, and the session is deleted. A failed run sends the submission back with the failed checks as the reason; a passed one waits for a reviewer, whose approval makes the listing public. Answers 422 `not_submittable` naming what is missing, 409 `already_in_review` while an earlier submission is waiting, 404 when the shelf is not enabled for this workspace or the environment has no listing. Path parameters: - `id` (string, required) — The environment id (env_…) Body (application/json): - `seller_name` (string, required) — The name the shelf shows as the maker: 2 to 60 characters, not an email address Responses: - `201` — Submitted - `missing` (string[], required) — What still has to be true before it can be submitted, in words; empty when it can - `submission` (object | null, required) — The latest submission; null when it has never been submitted - `published` (object | null, required) — The approved version other workspaces read and install; null when it is not public - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and nothing has been approved since - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/environments/sess_cmtb7q2j/listing/submission \ -H "Authorization: Bearer ast_..." ``` ### DELETE /environments/{id}/listing/submission Withdraw your environment's waiting submission (scope: write) Answers 404 when nothing is waiting for review. Path parameters: - `id` (string, required) — The environment id (env_…) Responses: - `200` — OK - `missing` (string[], required) — What still has to be true before it can be submitted, in words; empty when it can - `submission` (object | null, required) — The latest submission; null when it has never been submitted - `published` (object | null, required) — The approved version other workspaces read and install; null when it is not public - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and nothing has been approved since - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/environments/sess_cmtb7q2j/listing/submission \ -H "Authorization: Bearer ast_..." ``` ### DELETE /environments/{id}/listing/publication Stop serving your environment's listing to other workspaces (scope: write) The listing stays visible to your workspace and copies already installed are not touched. Answers 404 when it is not public, or when the shelf is not enabled for this workspace. Path parameters: - `id` (string, required) — The environment id (env_…) Responses: - `200` — OK - `missing` (string[], required) — What still has to be true before it can be submitted, in words; empty when it can - `submission` (object | null, required) — The latest submission; null when it has never been submitted - `published` (object | null, required) — The approved version other workspaces read and install; null when it is not public - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and nothing has been approved since - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/environments/sess_cmtb7q2j/listing/publication \ -H "Authorization: Bearer ast_..." ``` ## Vaults ### GET /vaults List the universe's vaults, oldest first (scope: read) Responses: - `200` — OK - `vaults` (object[], required) - `type` (enum, required) — "vault" - `id` (string, required) — vlt_… - `display_name` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/vaults' \ -H "Authorization: Bearer ast_..." ``` ### POST /vaults Create a vault (scope: write) Pass its id in `vault_ids` on POST /sessions to grant a session its secrets. Body (application/json): - `display_name` (string, required) — max length 128 Responses: - `201` — Created - `vault` (object, required) - `type` (enum, required) — "vault" - `id` (string, required) — vlt_… - `display_name` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/vaults \ -H "Authorization: Bearer ast_..." ``` ### GET /vaults/{id} One vault (scope: read) Path parameters: - `id` (string, required) — The vault id (vlt_…) Responses: - `200` — OK - `vault` (object, required) - `type` (enum, required) — "vault" - `id` (string, required) — vlt_… - `display_name` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /vaults/{id} Delete a vault and every secret in it (scope: write) The revocation is pushed to the pods of any sessions the vault was attached to — they lose the credentials without restarting. Path parameters: - `id` (string, required) — The vault id (vlt_…) Responses: - `204` — Deleted — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /vaults/{id}/secrets List a vault's secrets (scope: read) Path parameters: - `id` (string, required) — The vault id (vlt_…) Responses: - `200` — OK - `secrets` (object[], required) - `type` (enum, required) — "vault_secret" - `id` (string, required) — vsec_… - `display_name` (string, required) - `auth_type` (enum, required) — "static_bearer" · "mcp_oauth" · "environment_variable" - `mcp_server_url` (string) - `secret_name` (string) — The environment variable the pod sees - `status` (string, required) - `expires_at` (string) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j/secrets' \ -H "Authorization: Bearer ast_..." ``` ### POST /vaults/{id}/secrets Add a secret to a vault (scope: write) `auth.type` picks the kind: static_bearer and mcp_oauth authenticate an MCP server at `mcp_server_url`; environment_variable exposes `secret_name` to the pod. The value is encrypted at rest and never returned. Path parameters: - `id` (string, required) — The vault id (vlt_…) Body (application/json): - `display_name` (string, required) — max length 128 - `auth` (object, required) - type: "static_bearer" (object) - `type` ("static_bearer", required) - `mcp_server_url` (string, required) — https, public host - `token` (string, required) - type: "mcp_oauth" (object) - `type` ("mcp_oauth", required) - `mcp_server_url` (string, required) — https, public host - `access_token` (string, required) - `scope` (string) - `expires_at` (string) - `refresh` (object) - `token_endpoint` (string, required) - `client_id` (string, required) - `client_secret` (string) - `refresh_token` (string, required) - `token_endpoint_auth` (enum) — "none" · "client_secret_basic" · "client_secret_post" — default: "none" - type: "environment_variable" (object) - `type` ("environment_variable", required) - `secret_name` (string, required) — A valid environment variable name — max length 128 - `value` (string, required) - `allowed_hosts` (string[]) — default: [] - `injection_location` (object) — default: {"header":true,"body":false} - `header` (boolean) - `body` (boolean) - `header_name` (string) - `value_template` (string) — max length 256 Responses: - `201` — Created - `secret` (object, required) — Metadata only — the secret value is never returned after it is written. `secret_name` is set for environment_variable secrets, `mcp_server_url` for the two MCP kinds. - `type` (enum, required) — "vault_secret" - `id` (string, required) — vsec_… - `display_name` (string, required) - `auth_type` (enum, required) — "static_bearer" · "mcp_oauth" · "environment_variable" - `mcp_server_url` (string) - `secret_name` (string) — The environment variable the pod sees - `status` (string, required) - `expires_at` (string) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j/secrets \ -H "Authorization: Bearer ast_..." ``` ### POST /vaults/{id}/secrets/move Move the secrets of other vaults into this one (scope: write) Moves every secret of the `from` vaults of the same workspace into this vault and answers what happened to each, by name; a value is never returned. A secret is copied and confirmed stored before its source copy is touched, and the source copy is removed only when no session that is not archived still uses that vault without this one (`source` says which). A name this vault already holds with another value is a `clash` and is left alone unless it is listed in `replace`. A secret past this vault's limit, or one that could not be applied together with this vault's secrets, is `blocked` with a `reason`. Calling again moves nothing twice: what is already here answers `already`. With `dry_run` nothing is written and the outcomes are what a real call would do. Path parameters: - `id` (string, required) — The vault id (vlt_…) Body (application/json): - `from` (string[], required) — The vaults to move out of (vlt_…) — max length 128 - `replace` (object[]) — Clashing names to overwrite in this vault with the named source's secret - `from` (string, required) — max length 128 - `name` (string, required) — max length 256 - `dry_run` (boolean) Responses: - `200` — OK - `dry_run` (boolean, required) - `secrets` (object[], required) - `from` (string, required) — The vault it was in (vlt_…) - `name` (string, required) — The environment variable name, or the display name of an MCP secret - `auth_type` (enum, required) — "static_bearer" · "mcp_oauth" · "environment_variable" - `outcome` (enum, required) — "moved" · "already" · "clash" · "blocked" - `source` (enum, required) — Whether the copy in the source vault was removed — "removed" · "kept" - `reason` (string) — Why a secret is blocked, e.g. limit_reached or egress_header_conflict - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j/secrets/move \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"from":["vlt_cmtb7q2j"],"dry_run":true}' ``` ### PATCH /vaults/{id}/secrets/{secretId} Rotate or re-scope a secret in place (scope: write) The secret keeps its id and its placeholder, so attached pods need no restart — the recompiled policy is pushed to them. Fields not sent are left as they are; `injection_location` merges per field. Path parameters: - `id` (string, required) — The vault id (vlt_…) - `secretId` (string, required) — The secret id (vsec_…) Body (application/json): - `display_name` (string) — max length 128 - `value` (string) - `token` (string) - `allowed_hosts` (string[]) - `injection_location` (object) - `header` (boolean) - `body` (boolean) - `header_name` (string) - `value_template` (string) — max length 256 Responses: - `200` — OK - `secret` (object, required) — Metadata only — the secret value is never returned after it is written. `secret_name` is set for environment_variable secrets, `mcp_server_url` for the two MCP kinds. - `type` (enum, required) — "vault_secret" - `id` (string, required) — vsec_… - `display_name` (string, required) - `auth_type` (enum, required) — "static_bearer" · "mcp_oauth" · "environment_variable" - `mcp_server_url` (string) - `secret_name` (string) — The environment variable the pod sees - `status` (string, required) - `expires_at` (string) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j/secrets/secretId \ -H "Authorization: Bearer ast_..." ``` ### DELETE /vaults/{id}/secrets/{secretId} Delete a secret (scope: write) Path parameters: - `id` (string, required) — The vault id (vlt_…) - `secretId` (string, required) — The secret id (vsec_…) Responses: - `204` — Deleted — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j/secrets/secretId \ -H "Authorization: Bearer ast_..." ``` ### POST /vaults/{id}/secrets/{secretId}/validate Exercise an mcp_oauth grant and report whether it still works (scope: write) Path parameters: - `id` (string, required) — The vault id (vlt_…) - `secretId` (string, required) — The secret id (vsec_…) Responses: - `200` — OK - `result` (enum, required) — "valid" · "invalid" · "unknown" - `detail` (string) - `secret` (object, required) — Metadata only — the secret value is never returned after it is written. `secret_name` is set for environment_variable secrets, `mcp_server_url` for the two MCP kinds. - `type` (enum, required) — "vault_secret" - `id` (string, required) — vsec_… - `display_name` (string, required) - `auth_type` (enum, required) — "static_bearer" · "mcp_oauth" · "environment_variable" - `mcp_server_url` (string) - `secret_name` (string) — The environment variable the pod sees - `status` (string, required) - `expires_at` (string) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/vaults/sess_cmtb7q2j/secrets/secretId/validate \ -H "Authorization: Bearer ast_..." ``` ### GET /workspace-secrets/move Preview moving earlier conversations' secrets into the workspace vault What moving the secrets the caller saved in earlier conversations into their workspace's vault would do, by name only: each name and whether it moves, or clashes with a secret the workspace already holds under that name. Nothing moves by reading it. 404 when the workspace vault is not available. Responses: - `200` — OK - `secrets` (object[]) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-secrets/move' \ -H "Authorization: Bearer ast_..." ``` ### POST /workspace-secrets/move Move earlier conversations' secrets into the workspace vault Moves them, as the Mac's Vaults page does. A secret whose name the workspace already uses is left alone unless `replace` names it (`{vault, name}`). Answers names and outcomes only, never values. Body (application/json): - `replace` (object[]) - `vault` (string, required) - `name` (string, required) Responses: - `200` — OK - `secrets` (object[]) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/workspace-secrets/move \ -H "Authorization: Bearer ast_..." ``` ## Browser profiles ### GET /browser_profiles Your browser and the browsers shared with you (scope: read) Your browser is yours in every workspace: sessions whose turn you send open signed in to its sites and save new sign-ins back to it. A browser someone shared with you here lists only the sites they shared. A workspace key gets what is shared with the whole workspace. Cookies are never returned. Responses: - `200` — OK - `data` (object[], required) - `scope` (enum, required) — mine: your browser. shared: someone's browser shared with you here, with only the sites they shared. workspace (a workspace key): what is shared with the whole workspace — "mine" · "shared" · "workspace" - `revision` (integer, required) — Changes whenever the browser, or for workspace what is shared, changes - `owner` (object) — Whose browser it is, for shared - `id` (string) - `email` (string | null) - `name` (string | null) - `sites` (object[], required) - `site` (string, required) - `site_id` (string) — Stable reference for removal with by=id, on your own browser's sites. Present when new removals are enabled, and always present for incomplete removals. - `status` (enum, required) — needs_signin: sign in again. removing: cleanup is incomplete; retry Remove using site_id. The site cannot be used or imported while removal is pending. — "ready" · "needs_signin" · "removing" - `signed_in_by` (object | null, required) - `signed_in_at` (string, required) - `last_used_at` (string | null, required) - `shareable` (boolean) — false for a Google login: it stays in its owner's own sessions and is never shared, so a share never includes it. - `shares` (object[]) — For mine: whom you shared it with in this workspace - `id` (string, required) - `grantee` (object, required) — Who may use it: everyone in this workspace, or one member - `type` (enum, required) — "workspace" · "member" - `user` (object) - `id` (string) - `email` (string | null) - `name` (string | null) - `sites` (array | null, required) — The shared sites; null means every site, including ones signed in later - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/browser_profiles' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /browser_profiles/{scope}/sites/{site} Sign your browser out of a site (scope: write) Signs the site out of running sessions that use your browser, yours and the ones it is shared into. Only the browser's owner can. Cleanup failure retains the listed site as removing, so it can be retried after a refresh. New removals return 503 during the compatibility rollout; existing removing sites remain retryable. Path parameters: - `scope` (string, required) — mine: your browser - `site` (string, required) — The site domain, or its listed site_id when by=id Query parameters: - `by` (string) — Use the listed site_id to address a specific saved site, including legacy names that cannot be used in a URL. The identifier must belong to this scope. Responses: - `204` — Removed — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/browser_profiles/scope/sites/site \ -H "Authorization: Bearer ast_..." ``` ### POST /browser_profiles/{scope}/shares Share your browser in this workspace (scope: write) With one member or everyone here, for every site or only some. Sharing again with the same grantee replaces its sites. Their agents use the sites from their next turn and never see the stored state; new sign-ins in their turns are saved to their own browser. Path parameters: - `scope` (string, required) — mine: your browser Body (application/json): - `grantee` (enum | object, required) — workspace for everyone here, or { user_id } for one member - option 1 (enum) — "workspace" - option 2 (object) - `user_id` (string, required) - `sites` (array | null) — Only these sites; omitted or null shares every site Responses: - `200` — Shared - `share` (object, required) - `id` (string, required) - `grantee` (object, required) — Who may use it: everyone in this workspace, or one member - `type` (enum, required) — "workspace" · "member" - `user` (object) - `id` (string) - `email` (string | null) - `name` (string | null) - `sites` (array | null, required) — The shared sites; null means every site, including ones signed in later - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/browser_profiles/scope/shares \ -H "Authorization: Bearer ast_..." ``` ### DELETE /browser_profiles/{scope}/shares/{share} Stop sharing your browser (scope: write) The grantee's sessions lose it from their next turn. Path parameters: - `scope` (string, required) — mine: your browser - `share` (string, required) — The share's id from the list Responses: - `204` — Stopped — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/browser_profiles/scope/shares/share \ -H "Authorization: Bearer ast_..." ``` ### GET /browser_profiles/{scope}/export What is shared with the whole workspace, with its state, for a registered service runtime (scope: read) Cookies and localStorage of every ready site shared with the whole workspace, the counterpart of delivery into a session pod, for the one machine registered to hold them outside the pods (BROWSER_PROFILE_SERVICE_RUNTIMES, by workspace and workspace key). Any other caller gets 403. A site shared with one member is never exported. Path parameters: - `scope` (string, required) — mine (your browser; private and, for a person, workspace are accepted as mine) or workspace (a workspace key: what is shared with the whole workspace) Responses: - `200` — OK - `scope` (enum, required) — "workspace" - `runtime` (string, required) — The registered runtime's name - `revision` (integer, required) — Changes whenever what is shared changes; poll the list route and pull again when it does - `sites` (string[], required) - `state` (object, required) — Playwright storage state: cookies and per-origin localStorage - `cookies` (object[]) - `origins` (object[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/browser_profiles/scope/export' \ -H "Authorization: Bearer ast_..." ``` ### POST /browser_profiles/{scope}/imports Bring sites signed in elsewhere into your browser (scope: write) Merges per site like a session's save: new and changed cookies win, nothing is deleted. Used by AgentSky for Mac and `sky login import`. Only a workspace key registered as that workspace's service runtime may use the workspace scope; it writes into the workspace's service browser, never into a person's browser, and never changes a share. Any other workspace key gets 403. Cookie domains are normalized as hostnames (including international and internal hosts); malformed domains return 400. An import cannot restore a site whose removal is incomplete. Path parameters: - `scope` (string, required) — mine (your browser; private and, for a person, workspace are accepted as mine) or workspace (a workspace key: what is shared with the whole workspace) Body (application/json): - `state` (object, required) — Playwright storage state: cookies (name, value, domain, path, expires, httpOnly, secure, sameSite) and per-origin localStorage - `cookies` (object[], required) - `origins` (object[]) Responses: - `200` — Merged - `sites` (string[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/browser_profiles/scope/imports \ -H "Authorization: Bearer ast_..." ``` ## Worker pools ### GET /worker-pools List worker pools in the workspace (scope: read) Responses: - `200` — OK - `worker_pools` (object[], required) - `id` (string, required) — wpool_… - `name` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/worker-pools' \ -H "Authorization: Bearer ast_..." ``` ### POST /worker-pools Create a worker pool (scope: write) Returns the pool and a one-time `wpt_…` bearer token the worker machine passes to `GET /worker-pools/{id}/connect`. Store it securely — it is never shown again; call POST /worker-pools/{id}/tokens to rotate. Body (application/json): - `name` (string, required) — max length 128 Responses: - `201` — Created - `worker_pool` (object, required) - `id` (string, required) — wpool_… - `name` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `token` (string, required) — A wpt_… bearer token the worker machine passes to the connect endpoint. Shown only once — rotate it from /worker-pools/{id}/tokens if lost. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/worker-pools \ -H "Authorization: Bearer ast_..." ``` ### GET /worker-pools/{id} One worker pool (scope: read) Path parameters: - `id` (string, required) — The worker pool id (wpool_…) Responses: - `200` — OK - `id` (string, required) — wpool_… - `name` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/worker-pools/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /worker-pools/{id} Delete a worker pool (scope: write) Disconnects every worker currently connected to the pool. Path parameters: - `id` (string, required) — The worker pool id (wpool_…) Responses: - `204` — Deleted — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/worker-pools/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /worker-pools/{id}/tokens Rotate the pool's connect token (scope: write) Issues a new `wpt_…` token and immediately invalidates the previous one. Workers currently connected keep their session; they will need the new token on reconnect. Path parameters: - `id` (string, required) — The worker pool id (wpool_…) Responses: - `201` — Created - `token` (string, required) — The new wpt_… token. The previous token is immediately invalidated. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/worker-pools/sess_cmtb7q2j/tokens \ -H "Authorization: Bearer ast_..." ``` ### GET /worker-pools/{id}/workers List workers connected to a pool (scope: read) Path parameters: - `id` (string, required) — The worker pool id (wpool_…) Responses: - `200` — OK - `workers` (object[], required) - `id` (string, required) — wrk_… - `pool_id` (string, required) — wpool_… - `label` (string, required) — Human-readable label the worker sent at connect time. - `image_digest` (string) - `status` (enum, required) — `connected` — idle and ready; `busy` — running a session; `offline` — disconnected. — "connected" · "busy" · "offline" - `last_seen_at` (string | null) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/worker-pools/sess_cmtb7q2j/workers' \ -H "Authorization: Bearer ast_..." ``` ### GET /worker-pools/{id}/connect Open a worker's control WebSocket (scope: worker) Workers upgrade this HTTP connection to a WebSocket using `Upgrade: websocket`. Pass the `wpt_…` bearer token in the `Authorization` header. Served by chat-proxy, not asteroids. Path parameters: - `id` (string, required) — The worker pool id (wpool_…) Responses: - `101` — Switching Protocols — WebSocket upgrade accepted - `401` — Missing or invalid wpt_… token - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/worker-pools/sess_cmtb7q2j/connect' \ -H "Authorization: Bearer ast_..." ``` ## Sessions ### GET /sessions List sessions, newest first, one page at a time; ?agent= filters to one spec (scope: read) Answers at most `limit` sessions (default 100, max 500) and `next_cursor` for the rest; null on the last page. A limit that is not an integer from 1 to 500, or a cursor that is not valid, is 400 invalid_request. Query parameters: - `agent` (string) — The agent id (agent_…) - `limit` (integer) - `cursor` (string) — next_cursor from the previous page Responses: - `200` — OK - `sessions` (object[], required) - `id` (string) — sess_… identifier - `agentId` (string | null) — The agent id (agent_…) - `title` (string | null) - `status` (enum) — provisioning = the sandbox is booting or installing the environment's packages (a turn sent meanwhile waits for it); running = a turn is in flight; terminated = archived or the last runtime was destroyed. Use archived for the durable archive receipt — "provisioning" · "idle" · "running" · "terminated" - `provisioningStage` (string | null) — Which part of its boot the session's computer is in, as the platform observed it: creating_computer until the sandbox provider reports the computer exists, then starting_agent until the agent in it answers. A provider that does not report the computer's creation stays at creating_computer. Null once the computer is up and whenever it is not booting. — "creating_computer" · "starting_agent" · null - `archived` (boolean) — True only after this Session's archive mark was durably written; false for an active Session whose runtime was destroyed - `agentType` (string) - `llm` (string) — The model this session runs on (PATCH to change; the agent's model is unchanged) - `harnessVersion` (string) — Harness release the session runs: the one its serving pod booted, the restored checkpoint's for a native-fork handoff, the agent's pin for ACP harnesses, otherwise the current release - `reasoningEffort` (string | null) — The model-compatible effort resolved from the session pin, then agent pin. Constrained models use an allowed fallback; no-setting models return null. Unpinned unconstrained models return null for the harness default. - `subscriptionApiFallback` (boolean) — Opt-in to paid API continuation after all subscription accounts exhaust, preserving the same supported explicit model and wallet/budget gates - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription — "platform" · "account" - `idleTimeoutSeconds` (integer | null) — Seconds the session's machine stays awake after a turn ends before it is paused; null = the platform default of 60. Set it when people use apps on the session's exposed ports after the agent finishes - `browserProfile` (string) — Resolved browser: the session's choice, then the agent's default, then mine. mine is the browser of the person whose turn it is; fresh is signed out; user: is the sites that person shared with whoever runs the turn. Applied before the next browser turn when browser profiles are enabled. - `browserLoginsSavedAt` (string | null) — Most recent browser login save-back time, or null when this session has not saved changed logins. - `default` (boolean) — Is this the agent's default session - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `metadata` (object) - `budget` (object | null) — Hard spend cap, or null when the session has none. A reached budget pauses the session (402 budget_reached on new work) until the cap is raised above consumed_cost or removed — removal is one-way. - `parentSessionId` (string | null) — The session this one came from: a helper names the session that hired it, a handoff child names the session it continued; null for a root. Every session a task's root produced, with its role, is GET /tasks/{id}?by=session on any member's id. - `parentRelation` (string | null) — How this session relates to parentSessionId: `helper` was hired by it (actl hire or the orchestration API), `handoff` continued its conversation on another agent; null for a root. — "helper" · "handoff" · null - `resources` (object[]) — The files, GitHub repositories, and read-only memory stores mounted into the session, oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `workspaceDefaults` (object | null) — What the session started with because its workspace made it a default; everything else it carries was named at creation. Null when it took nothing. - `contextFiles` (object | null) — The workspace files the session loaded into its instructions when its sandbox was last provisioned (GET /workspace-context describes the set); null before that and for a session without its workspace's store. Every other file in the store is read on demand. - `hostedBySeller` (object) — Present only when the session runs on its agent's maker's own server instead of a sandbox: what is sent to the session goes to this host. `agentType` and `llm` then name a stand-in that never runs. - `host` (string, required) - `attachments` (object[]) — Everything the session started with: its environment, vaults, mounted files and memory stores, and the workspace's always-available skills. The skills of the session's agent are on the agent and are not repeated here. - `type` (enum, required) — "environment" · "vault" · "file" · "skill" · "memory_store" - `id` (string, required) — The id of the environment, vault, file, skill or memory store - `mountPath` (string | null, required) — Where a file or memory store is mounted; null for the other types - `source` (enum, required) — default = the workspace supplied it; explicit = it was named when the session was created. A memory store on a session created before 2026-10-06 reads as explicit — "default" · "explicit" - `createdAt` (string) — ISO 8601 - `next_cursor` (string) — Pass back as ?cursor= for the next page; null on the last page - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions Create a session — starts provisioning by default; optionally defer until the first turn (scope: write) Optional llm and reasoningEffort override the agent's defaults for this session before billing selection or provisioning. A model must be current for the agent's harness; omitted or null reasoningEffort inherits the agent's setting normalized for the target model. Explicit effort must fit both harness and target model. A non-empty initial_events list (user.message only, all-or-nothing) starts the first turn in the same call and provisions in either mode. Without initial events, provision: "deferred" skips the boot: the session reads idle with no pod and costs nothing until its first turn — for a session that waits on a gate or a schedule. Machine shape (vcpus/memoryMb) is per-session. Body (application/json): - `agent` (string, required) — An agent id, or "@handle" for an agent from the catalog (GET /catalog/agents). A handle that names a platform engine uses the workspace's agent for that engine, created on first use. - `llm` (string) — Model for this session (a current model for the agent's harness). Omitted = the agent's model - `reasoningEffort` (enum | null) — Reasoning effort for this session; omitted or null inherits the agent's setting normalized for the target model. Explicit values must fit both harness and target model - agoragentic_acp: not supported; amp_acp: not supported; auggie: not supported; autohand: not supported; cline: not supported; codebuddy_code: not supported; cortex_code: not supported; corust_agent: not supported; crow_cli: not supported; cursor: not supported; deepagents: not supported; devin: not supported; dimcode: not supported; dirac: not supported; factory_droid: not supported; fast_agent: not supported; gemini: not supported; github_copilot_cli: not supported; glm_acp_agent: not supported; antigravity_acp: not supported; goose: not supported; grok_build: not supported; harn: not supported; junie: not supported; kilo: not supported; kimchi: not supported; minimax_code: not supported; minion_code: not supported; mistral_vibe: not supported; nova: not supported; poolside: not supported; qoder: not supported; qwen_code: not supported; sigit: not supported; stakpak: not supported; vtcode: not supported; code_assistant: not supported; docker_cagent: not supported; kiro: not supported; muse_code: not supported; openhands: not supported; hermes: none/minimal/low/medium/high/xhigh/max/ultra; claude_code: low/medium/high/xhigh/max; codex: none/minimal/low/medium/high/xhigh/max; openclaw: not supported; jev: not supported; pi: off/minimal/low/medium/high/xhigh/max; dsh: not supported; kimi_code: not supported; opencode: none/minimal/low/medium/high/xhigh/max; workspace_acp: not supported; models: claude-fable-5-1: low/medium/high/xhigh/max; claude-fable-5: low/medium/high/xhigh/max; claude-opus-5-5: low/medium/high/xhigh/max; claude-opus-5: low/medium/high/xhigh/max; claude-opus-4-8: low/medium/high/xhigh/max; claude-sonnet-5-5: low/medium/high/xhigh/max; claude-sonnet-5: low/medium/high/xhigh/max; claude-sonnet-4-6: low/medium/high/max; claude-haiku-5-5: low/medium/high/xhigh/max; claude-haiku-4-5: not supported; gpt-6.1-sol: low/medium/high/xhigh/max; gpt-6-astra: low/medium/high/xhigh/max; gpt-6-sol: none/low/medium/high/xhigh/max; gpt-6-luna: none/low/medium/high/xhigh/max; gpt-5.6-sol: none/low/medium/high/xhigh/max; gpt-5.6-terra: none/low/medium/high/xhigh/max; gpt-5.6-luna: none/low/medium/high/xhigh/max; gpt-5.5: none/low/medium/high/xhigh; gpt-4.1: not supported; deepseek-v4-pro: low/high/max; deepseek-flash: low/high/max; kimi-k3.1-pro: low/high/max; kimi-k3: low/high/max; kimi-k2.7-code: not supported; minimax-m3: not supported; muse-spark-1.1: minimal/low/medium/high/xhigh; muse-spark-1.2: minimal/low/medium/high/xhigh; muse-spark-1.3: minimal/low/medium/high/xhigh/max; minimax-m3.1-flash: low/medium/high/xhigh/max; gemini-4-argon: low/medium/high; gemini-3.5-flash-lite: low/medium/high; gemini-3.8-flash: low/medium/high; gemini-3.5-flash: low/medium/high; glm-5.3: low/high/max; glm-5.3-flash: low/high/max; grok-4.6: low/medium/high; grok-4.7: low/medium/high/xhigh — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" - `title` (string) — max length 120 - `metadata` (map) - `idleTimeoutSeconds` (integer) — Seconds this session's machine stays awake after a turn ends before it is paused (60-86400; omitted = the platform default of 60). A paused machine wakes on the next message; apps the agent serves on exposed ports are unreachable while it is paused, so set this to how long people keep using them after the agent finishes - `browserProfile` (enum | string) — Which saved browser the agent's browser opens with: mine (the browser of the person whose turn it is; new sign-ins are saved there), fresh (nothing loaded or saved) or user: (the sites that person shared with whoever runs the turn; used, never saved). workspace and private are accepted as mine. Omitted = the agent's default, then mine. Only harnesses with a persistent browser use it — "mine" · "fresh" · "workspace" · "private" - `instructions` (object[]) - `name` (string, required) — pattern .*\.md$ - `content` (string) — default: "" - `mcp_servers` (object[]) — Per-session MCP server overlay (RFC-0149 §3): up to 20 streamable-HTTP servers layered over the agent spec's declared servers at provision time. Names must not collide with the spec or with each other (→ 409), URLs must be HTTPS (→ 422), total spec + overlay ≤ 20 (→ 422). authorization_token is write-only. - `name` (string, required) — max length 255 - `url` (string, required) — HTTPS URL of a streamable-HTTP MCP server. - `authorization_token` (string) — Bearer token for the server. Write-only: stored encrypted, stamped as Authorization: Bearer at provision, never returned in reads. - `tools` (object) - `include` (string[]) - `exclude` (string[]) - `initial_events` (object[]) - `type` ("user.message", required) - `content` (object[], required) - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "image" (object) - `type` ("image", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - type: "document" (object) - `type` ("document", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "text" (object) - `type` ("text", required) - `media_type` ("text/plain") — default: "text/plain" - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - `title` (string | null) - `context` (string | null) - `interaction_mode` (enum | null) — "plan" · "build" - `provision` (enum) — eager (default) boots the sandbox as soon as the row exists; deferred leaves the session idle with no pod until its first turn, so a session waiting on a gate or a schedule costs nothing while it waits. Ignored when initial_events start the first turn — "eager" · "deferred" - `environment_id` (string) - `vault_ids` (string[]) - `vcpus` (integer) - `memoryMb` (integer) - `subscriptionApiFallback` (boolean) — After every subscription account is exhausted, continue on the same model using the paid platform API; wallet and session budget still apply - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription. Omitted = auto-apply (a connected subscription with "use for new agents" powers eligible compositions) — "platform" · "account" - `budget` (object) — Hard spend cap for this session. Attachable only at create; changeable or removable later via PATCH (removal is one-way) - `type` ("limit", required) - `max_list_cost` (object, required) - `amount` (string, required) — Whole US cents as an integer decimal string, e.g. "125" for $1.25 — max length 15 - `currency` ("USD", required) - `payer` (enum) — Which balance pays for the session, in a team workspace that has a wallet of its own: "workspace" (the default there) or "personal", the caller's own. Decided once, here, and shown as `payer` on the session and its task. "workspace" where the workspace has no wallet is refused with 409 workspace_wallet_unavailable. — "personal" · "workspace" - `defaults` (enum) — Whether the session also starts with its workspace's defaults: the default environment when none is named, the default vault when it holds a secret, the team memory stores that attach by default, and the always-available files and skills. "none" attaches only what this request names. Omitted means "workspace". — "workspace" · "none" - `resources` (object[]) — Files, GitHub repositories, and team Memory Stores attached before the first turn (max 100 create-time resources; max 8 memory stores). `{type:"file", file_id, mount_path?}` copies an upload read-only to `/workspace` (default `/workspace/`); `{type:"github_repository", url, authorization_token?, checkout?: {type:"branch", name} | {type:"commit", sha}, mount_path?}` clones `https://github.com/{owner}/{repo}` to `/workspace` (default `/workspace/`); the token never enters the sandbox. `{type:"memory_store", memory_store_id, access:"read_only"|"read_write", mount_path?, instructions?}` attaches a creation-time snapshot under `/mnt/memory/` (default `/mnt/memory/`; 8 MiB total per Session). `read_write` explicitly grants the session-scoped Agent API permission to update this store; the mounted snapshot remains read-only. - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) — max length 128 - `mount_path` (string | null) — max length 4096 - type: "github_repository" (object) - `type` ("github_repository", required) - `url` (string, required) — max length 2048 - `authorization_token` (string) — max length 1024 - `checkout` (object | null) - type: "branch" (object) - `type` ("branch", required) - `name` (string, required) — max length 255 - type: "commit" (object) - `type` ("commit", required) - `sha` (string, required) — max length 64 - `mount_path` (string | null) — max length 4096 - type: "memory_store" (object) - `type` ("memory_store", required) - `memory_store_id` (string, required) — max length 255 - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — max length 4096 - `mount_path` (string | null) — max length 4096 - `local_mirror` (object) — Mark this session as a local mirror running on the user's Mac. No sandbox is provisioned; no billing gate is applied. Messages are ingested via PUT /v1/sessions/{id}/mirror. Idempotent: a second create with the same harness+source_session_id returns the existing session. - `harness` (enum, required) — "claude_code" · "codex" - `source_session_id` (string, required) — max length 200 - `origin` (enum, required) — "app" · "terminal" · "import" - `folder` (string) — max length 200 - `machine` (string) — max length 200 Responses: - `201` — Created - `session` (object, required) - `id` (string) — sess_… identifier - `agentId` (string | null) — The agent id (agent_…) - `title` (string | null) - `status` (enum) — provisioning = the sandbox is booting or installing the environment's packages (a turn sent meanwhile waits for it); running = a turn is in flight; terminated = archived or the last runtime was destroyed. Use archived for the durable archive receipt — "provisioning" · "idle" · "running" · "terminated" - `provisioningStage` (string | null) — Which part of its boot the session's computer is in, as the platform observed it: creating_computer until the sandbox provider reports the computer exists, then starting_agent until the agent in it answers. A provider that does not report the computer's creation stays at creating_computer. Null once the computer is up and whenever it is not booting. — "creating_computer" · "starting_agent" · null - `archived` (boolean) — True only after this Session's archive mark was durably written; false for an active Session whose runtime was destroyed - `agentType` (string) - `llm` (string) — The model this session runs on (PATCH to change; the agent's model is unchanged) - `harnessVersion` (string) — Harness release the session runs: the one its serving pod booted, the restored checkpoint's for a native-fork handoff, the agent's pin for ACP harnesses, otherwise the current release - `reasoningEffort` (string | null) — The model-compatible effort resolved from the session pin, then agent pin. Constrained models use an allowed fallback; no-setting models return null. Unpinned unconstrained models return null for the harness default. - `subscriptionApiFallback` (boolean) — Opt-in to paid API continuation after all subscription accounts exhaust, preserving the same supported explicit model and wallet/budget gates - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription — "platform" · "account" - `idleTimeoutSeconds` (integer | null) — Seconds the session's machine stays awake after a turn ends before it is paused; null = the platform default of 60. Set it when people use apps on the session's exposed ports after the agent finishes - `browserProfile` (string) — Resolved browser: the session's choice, then the agent's default, then mine. mine is the browser of the person whose turn it is; fresh is signed out; user: is the sites that person shared with whoever runs the turn. Applied before the next browser turn when browser profiles are enabled. - `browserLoginsSavedAt` (string | null) — Most recent browser login save-back time, or null when this session has not saved changed logins. - `default` (boolean) — Is this the agent's default session - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `metadata` (object) - `budget` (object | null) — Hard spend cap, or null when the session has none. A reached budget pauses the session (402 budget_reached on new work) until the cap is raised above consumed_cost or removed — removal is one-way. - `parentSessionId` (string | null) — The session this one came from: a helper names the session that hired it, a handoff child names the session it continued; null for a root. Every session a task's root produced, with its role, is GET /tasks/{id}?by=session on any member's id. - `parentRelation` (string | null) — How this session relates to parentSessionId: `helper` was hired by it (actl hire or the orchestration API), `handoff` continued its conversation on another agent; null for a root. — "helper" · "handoff" · null - `resources` (object[]) — The files, GitHub repositories, and read-only memory stores mounted into the session, oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `workspaceDefaults` (object | null) — What the session started with because its workspace made it a default; everything else it carries was named at creation. Null when it took nothing. - `contextFiles` (object | null) — The workspace files the session loaded into its instructions when its sandbox was last provisioned (GET /workspace-context describes the set); null before that and for a session without its workspace's store. Every other file in the store is read on demand. - `hostedBySeller` (object) — Present only when the session runs on its agent's maker's own server instead of a sandbox: what is sent to the session goes to this host. `agentType` and `llm` then name a stand-in that never runs. - `host` (string, required) - `attachments` (object[]) — Everything the session started with: its environment, vaults, mounted files and memory stores, and the workspace's always-available skills. The skills of the session's agent are on the agent and are not repeated here. - `type` (enum, required) — "environment" · "vault" · "file" · "skill" · "memory_store" - `id` (string, required) — The id of the environment, vault, file, skill or memory store - `mountPath` (string | null, required) — Where a file or memory store is mounted; null for the other types - `source` (enum, required) — default = the workspace supplied it; explicit = it was named when the session was created. A memory store on a session created before 2026-10-06 reads as explicit — "default" · "explicit" - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"agent":"agent_cmtb2f0a8m9k7x51q4ez6r0d","llm":"deepseek-flash","reasoningEffort":"high","title":"Signups digest","resources":[{"type":"file","file_id":"file_cmtb9x4p","mount_path":"/data/signups.csv"},{"type":"github_repository","url":"https://github.com/acme/growth","checkout":{"type":"branch","name":"main"}},{"type":"memory_store","memory_store_id":"memstore_cmtb9x4p","access":"read_only"}],"initial_events":[{"type":"user.message","content":[{"type":"text","text":"Summarize the signups from today"}]}]}' ``` Example response (201 Created): ```json { "session": { "id": "sess-8c41f0…", "agent": "research-agent", "status": "idle", "default": true } } ``` ### GET /sessions/{id} Session detail — status is provisioning (sandbox booting or installing packages) | idle | running | terminated (scope: read) Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `session` (object, required) - `id` (string) — sess_… identifier - `agentId` (string | null) — The agent id (agent_…) - `title` (string | null) - `status` (enum) — provisioning = the sandbox is booting or installing the environment's packages (a turn sent meanwhile waits for it); running = a turn is in flight; terminated = archived or the last runtime was destroyed. Use archived for the durable archive receipt — "provisioning" · "idle" · "running" · "terminated" - `provisioningStage` (string | null) — Which part of its boot the session's computer is in, as the platform observed it: creating_computer until the sandbox provider reports the computer exists, then starting_agent until the agent in it answers. A provider that does not report the computer's creation stays at creating_computer. Null once the computer is up and whenever it is not booting. — "creating_computer" · "starting_agent" · null - `archived` (boolean) — True only after this Session's archive mark was durably written; false for an active Session whose runtime was destroyed - `agentType` (string) - `llm` (string) — The model this session runs on (PATCH to change; the agent's model is unchanged) - `harnessVersion` (string) — Harness release the session runs: the one its serving pod booted, the restored checkpoint's for a native-fork handoff, the agent's pin for ACP harnesses, otherwise the current release - `reasoningEffort` (string | null) — The model-compatible effort resolved from the session pin, then agent pin. Constrained models use an allowed fallback; no-setting models return null. Unpinned unconstrained models return null for the harness default. - `subscriptionApiFallback` (boolean) — Opt-in to paid API continuation after all subscription accounts exhaust, preserving the same supported explicit model and wallet/budget gates - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription — "platform" · "account" - `idleTimeoutSeconds` (integer | null) — Seconds the session's machine stays awake after a turn ends before it is paused; null = the platform default of 60. Set it when people use apps on the session's exposed ports after the agent finishes - `browserProfile` (string) — Resolved browser: the session's choice, then the agent's default, then mine. mine is the browser of the person whose turn it is; fresh is signed out; user: is the sites that person shared with whoever runs the turn. Applied before the next browser turn when browser profiles are enabled. - `browserLoginsSavedAt` (string | null) — Most recent browser login save-back time, or null when this session has not saved changed logins. - `default` (boolean) — Is this the agent's default session - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `metadata` (object) - `budget` (object | null) — Hard spend cap, or null when the session has none. A reached budget pauses the session (402 budget_reached on new work) until the cap is raised above consumed_cost or removed — removal is one-way. - `parentSessionId` (string | null) — The session this one came from: a helper names the session that hired it, a handoff child names the session it continued; null for a root. Every session a task's root produced, with its role, is GET /tasks/{id}?by=session on any member's id. - `parentRelation` (string | null) — How this session relates to parentSessionId: `helper` was hired by it (actl hire or the orchestration API), `handoff` continued its conversation on another agent; null for a root. — "helper" · "handoff" · null - `resources` (object[]) — The files, GitHub repositories, and read-only memory stores mounted into the session, oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `workspaceDefaults` (object | null) — What the session started with because its workspace made it a default; everything else it carries was named at creation. Null when it took nothing. - `contextFiles` (object | null) — The workspace files the session loaded into its instructions when its sandbox was last provisioned (GET /workspace-context describes the set); null before that and for a session without its workspace's store. Every other file in the store is read on demand. - `hostedBySeller` (object) — Present only when the session runs on its agent's maker's own server instead of a sandbox: what is sent to the session goes to this host. `agentType` and `llm` then name a stand-in that never runs. - `host` (string, required) - `attachments` (object[]) — Everything the session started with: its environment, vaults, mounted files and memory stores, and the workspace's always-available skills. The skills of the session's agent are on the agent and are not repeated here. - `type` (enum, required) — "environment" · "vault" · "file" · "skill" · "memory_store" - `id` (string, required) — The id of the environment, vault, file, skill or memory store - `mountPath` (string | null, required) — Where a file or memory store is mounted; null for the other types - `source` (enum, required) — default = the workspace supplied it; explicit = it was named when the session was created. A memory store on a session created before 2026-10-06 reads as explicit — "default" · "explicit" - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /sessions/{id} Update title / metadata / model / reasoning effort / billing / budget (scope: write) llm and reasoningEffort are session settings: they change this session only, never the agent or its other sessions. The model must fit the harness; explicit effort must fit both harness and target model. Inherited effort is normalized for that model. An idle session restarts its engine in place; a running session finishes its turn and applies the change at the next turn (the response is 200 either way). A parked session applies it at its next wake. Path parameters: - `id` (string, required) — The session id Body (application/json): - `mcp_servers_mode` (enum) — How mcp_servers changes the session overlay; default replace. Append preserves existing servers and credentials and refuses a duplicate name. — "replace" · "append" - `title` (string | null) — max length 120 - `metadata` (map) - `idleTimeoutSeconds` (integer | null) — Seconds this session's machine stays awake after a turn ends before it is paused (60-86400; omitted = the platform default of 60). A paused machine wakes on the next message; apps the agent serves on exposed ports are unreachable while it is paused, so set this to how long people keep using them after the agent finishes; null clears back to the platform default. Applies from the next time the session goes idle - `browserProfile` (enum | string | null) — Which saved browser the agent's browser opens with: mine (the browser of the person whose turn it is; new sign-ins are saved there), fresh (nothing loaded or saved) or user: (the sites that person shared with whoever runs the turn; used, never saved). workspace and private are accepted as mine. Omitted = the agent's default, then mine. Only harnesses with a persistent browser use it; null clears back to the agent's default. Applies from the next turn - `llm` (string) — Switch this session's model (a current model for its harness; the agent's model is unchanged). Applies from the next turn - `reasoningEffort` (enum | null) — Session-level reasoning effort; null clears to the agent's setting normalized for the target model. Applies from the next turn. Explicit values must fit both harness and target model — agoragentic_acp: not supported; amp_acp: not supported; auggie: not supported; autohand: not supported; cline: not supported; codebuddy_code: not supported; cortex_code: not supported; corust_agent: not supported; crow_cli: not supported; cursor: not supported; deepagents: not supported; devin: not supported; dimcode: not supported; dirac: not supported; factory_droid: not supported; fast_agent: not supported; gemini: not supported; github_copilot_cli: not supported; glm_acp_agent: not supported; antigravity_acp: not supported; goose: not supported; grok_build: not supported; harn: not supported; junie: not supported; kilo: not supported; kimchi: not supported; minimax_code: not supported; minion_code: not supported; mistral_vibe: not supported; nova: not supported; poolside: not supported; qoder: not supported; qwen_code: not supported; sigit: not supported; stakpak: not supported; vtcode: not supported; code_assistant: not supported; docker_cagent: not supported; kiro: not supported; muse_code: not supported; openhands: not supported; hermes: none/minimal/low/medium/high/xhigh/max/ultra; claude_code: low/medium/high/xhigh/max; codex: none/minimal/low/medium/high/xhigh/max; openclaw: not supported; jev: not supported; pi: off/minimal/low/medium/high/xhigh/max; dsh: not supported; kimi_code: not supported; opencode: none/minimal/low/medium/high/xhigh/max; workspace_acp: not supported; models: claude-fable-5-1: low/medium/high/xhigh/max; claude-fable-5: low/medium/high/xhigh/max; claude-opus-5-5: low/medium/high/xhigh/max; claude-opus-5: low/medium/high/xhigh/max; claude-opus-4-8: low/medium/high/xhigh/max; claude-sonnet-5-5: low/medium/high/xhigh/max; claude-sonnet-5: low/medium/high/xhigh/max; claude-sonnet-4-6: low/medium/high/max; claude-haiku-5-5: low/medium/high/xhigh/max; claude-haiku-4-5: not supported; gpt-6.1-sol: low/medium/high/xhigh/max; gpt-6-astra: low/medium/high/xhigh/max; gpt-6-sol: none/low/medium/high/xhigh/max; gpt-6-luna: none/low/medium/high/xhigh/max; gpt-5.6-sol: none/low/medium/high/xhigh/max; gpt-5.6-terra: none/low/medium/high/xhigh/max; gpt-5.6-luna: none/low/medium/high/xhigh/max; gpt-5.5: none/low/medium/high/xhigh; gpt-4.1: not supported; deepseek-v4-pro: low/high/max; deepseek-flash: low/high/max; kimi-k3.1-pro: low/high/max; kimi-k3: low/high/max; kimi-k2.7-code: not supported; minimax-m3: not supported; muse-spark-1.1: minimal/low/medium/high/xhigh; muse-spark-1.2: minimal/low/medium/high/xhigh; muse-spark-1.3: minimal/low/medium/high/xhigh/max; minimax-m3.1-flash: low/medium/high/xhigh/max; gemini-4-argon: low/medium/high; gemini-3.5-flash-lite: low/medium/high; gemini-3.8-flash: low/medium/high; gemini-3.5-flash: low/medium/high; glm-5.3: low/high/max; glm-5.3-flash: low/high/max; grok-4.6: low/medium/high; grok-4.7: low/medium/high/xhigh — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" - `subscriptionApiFallback` (boolean) — After every subscription account is exhausted, continue on the same model using the paid platform API; wallet and session budget still apply - `modelBilling` (enum) — Who pays for model calls; switching restarts the agent's engine in place — "platform" · "account" - `budget` (object | null) — Change the spend cap (must be strictly above consumed cost) or null to remove it — removal is one-way - `type` ("limit", required) - `max_list_cost` (object, required) - `amount` (string, required) — Whole US cents as an integer decimal string, e.g. "125" for $1.25 — max length 15 - `currency` ("USD", required) - `mcp_servers` (object[]) — Change the session's MCP server overlay. Default replaces the entire overlay; mcp_servers_mode append preserves existing servers and credentials atomically and refuses duplicate names; omit mcp_servers to leave unchanged. Same rules as at create (HTTPS, no spec-name collisions, ≤ 20 total). Takes effect at the next provision. - `name` (string, required) — max length 255 - `url` (string, required) — HTTPS URL of a streamable-HTTP MCP server. - `authorization_token` (string) — Bearer token for the server. Write-only: stored encrypted, stamped as Authorization: Bearer at provision, never returned in reads. - `tools` (object) - `include` (string[]) - `exclude` (string[]) Responses: - `200` — OK - `session` (object, required) - `id` (string) — sess_… identifier - `agentId` (string | null) — The agent id (agent_…) - `title` (string | null) - `status` (enum) — provisioning = the sandbox is booting or installing the environment's packages (a turn sent meanwhile waits for it); running = a turn is in flight; terminated = archived or the last runtime was destroyed. Use archived for the durable archive receipt — "provisioning" · "idle" · "running" · "terminated" - `provisioningStage` (string | null) — Which part of its boot the session's computer is in, as the platform observed it: creating_computer until the sandbox provider reports the computer exists, then starting_agent until the agent in it answers. A provider that does not report the computer's creation stays at creating_computer. Null once the computer is up and whenever it is not booting. — "creating_computer" · "starting_agent" · null - `archived` (boolean) — True only after this Session's archive mark was durably written; false for an active Session whose runtime was destroyed - `agentType` (string) - `llm` (string) — The model this session runs on (PATCH to change; the agent's model is unchanged) - `harnessVersion` (string) — Harness release the session runs: the one its serving pod booted, the restored checkpoint's for a native-fork handoff, the agent's pin for ACP harnesses, otherwise the current release - `reasoningEffort` (string | null) — The model-compatible effort resolved from the session pin, then agent pin. Constrained models use an allowed fallback; no-setting models return null. Unpinned unconstrained models return null for the harness default. - `subscriptionApiFallback` (boolean) — Opt-in to paid API continuation after all subscription accounts exhaust, preserving the same supported explicit model and wallet/budget gates - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription — "platform" · "account" - `idleTimeoutSeconds` (integer | null) — Seconds the session's machine stays awake after a turn ends before it is paused; null = the platform default of 60. Set it when people use apps on the session's exposed ports after the agent finishes - `browserProfile` (string) — Resolved browser: the session's choice, then the agent's default, then mine. mine is the browser of the person whose turn it is; fresh is signed out; user: is the sites that person shared with whoever runs the turn. Applied before the next browser turn when browser profiles are enabled. - `browserLoginsSavedAt` (string | null) — Most recent browser login save-back time, or null when this session has not saved changed logins. - `default` (boolean) — Is this the agent's default session - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `metadata` (object) - `budget` (object | null) — Hard spend cap, or null when the session has none. A reached budget pauses the session (402 budget_reached on new work) until the cap is raised above consumed_cost or removed — removal is one-way. - `parentSessionId` (string | null) — The session this one came from: a helper names the session that hired it, a handoff child names the session it continued; null for a root. Every session a task's root produced, with its role, is GET /tasks/{id}?by=session on any member's id. - `parentRelation` (string | null) — How this session relates to parentSessionId: `helper` was hired by it (actl hire or the orchestration API), `handoff` continued its conversation on another agent; null for a root. — "helper" · "handoff" · null - `resources` (object[]) — The files, GitHub repositories, and read-only memory stores mounted into the session, oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `workspaceDefaults` (object | null) — What the session started with because its workspace made it a default; everything else it carries was named at creation. Null when it took nothing. - `contextFiles` (object | null) — The workspace files the session loaded into its instructions when its sandbox was last provisioned (GET /workspace-context describes the set); null before that and for a session without its workspace's store. Every other file in the store is read on demand. - `hostedBySeller` (object) — Present only when the session runs on its agent's maker's own server instead of a sandbox: what is sent to the session goes to this host. `agentType` and `llm` then name a stand-in that never runs. - `host` (string, required) - `attachments` (object[]) — Everything the session started with: its environment, vaults, mounted files and memory stores, and the workspace's always-available skills. The skills of the session's agent are on the agent and are not repeated here. - `type` (enum, required) — "environment" · "vault" · "file" · "skill" · "memory_store" - `id` (string, required) — The id of the environment, vault, file, skill or memory store - `mountPath` (string | null, required) — Where a file or memory store is mounted; null for the other types - `source` (enum, required) — default = the workspace supplied it; explicit = it was named when the session was created. A memory store on a session created before 2026-10-06 reads as explicit — "default" · "explicit" - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"title":"Signups digest","metadata":{"team":"growth"},"llm":"claude-sonnet-5","reasoningEffort":"high"}' ``` ### DELETE /sessions/{id} Retire a Session and tear down its runtime (scope: write) Marks the Session deleted and removes operational bindings; durable transcripts and accounting records are retained. GET Session answers 404 afterward. Active execution must stop before cleanup. Works on idle and archived Sessions. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/access Who may use the session, and what you are to it (scope: read) Members of the session's workspace may read and prompt a workspace session; a private session is the owner's alone. People whose verified email is listed may read its message history from any workspace. Viewer emails are returned to the owner only. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `session_id` (string, required) - `title` (string, required) - `agent_type` (string, required) - `llm` (string, required) - `visibility` (enum, required) — "workspace" · "private" - `access` (enum, required) — "owner" · "member" · "viewer" - `owner` (object, required) - `id` (string) - `name` (string) - `email` (string) - `workspace` (object, required) - `slug` (string) - `name` (string) - `personal` (boolean) - `viewers` (object[], required) - `email` (string) - `added_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/access' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /sessions/{id}/access Make the session private or workspace-visible, and edit its viewers (scope: write, owner) Path parameters: - `id` (string, required) — The session id Body (application/json): - `visibility` (enum) — "workspace" · "private" - `add_viewers` (string[]) - `remove_viewers` (string[]) Responses: - `200` — OK - `session_id` (string, required) - `title` (string, required) - `agent_type` (string, required) - `llm` (string, required) - `visibility` (enum, required) — "workspace" · "private" - `access` (enum, required) — "owner" · "member" · "viewer" - `owner` (object, required) - `id` (string) - `name` (string) - `email` (string) - `workspace` (object, required) - `slug` (string) - `name` (string) - `personal` (boolean) - `viewers` (object[], required) - `email` (string) - `added_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/access \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"visibility":"private","add_viewers":["reviewer@acme.com"]}' ``` ### GET /shared-sessions Sessions other people shared with you to read, newest first (scope: read) Viewing is a person's access, so a credential limited to one workspace lists none. A limit that is not an integer from 1 to 100, or a cursor that is not valid, is 400 invalid_request. Query parameters: - `limit` (integer) - `cursor` (string) — next_cursor from the previous page Responses: - `200` — OK - `sessions` (object[], required) - `id` (string) - `title` (string) - `agent_type` (string) - `owner` (object) - `name` (string) - `email` (string) - `workspace` (object) - `name` (string) - `shared_at` (string) - `next_cursor` (string, required) — Pass back as ?cursor= for the next page; null on the last page - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/shared-sessions' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/share The session's share link, or {"token": null} when sharing is off (owner only) (scope: read) Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `token` (string, required) - `access` (enum) — "prompt" · "view" - `shareUrl` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/share' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/share Turn sharing on and get the share link — idempotent; {"access": "view"} asks for a view-only link; {"rotate": true} replaces it (scope: write) A guest link (access prompt, the default) has a shareUrl that opens the session's guest page for anyone holding it, with no account; turns sent through it bill the session's owner. A view-only link (access view) has no shareUrl: anyone holding its token reads the conversation's text at GET /links/{token}/events and can do nothing else. A session has one link; asking for the other access level replaces it with a new token, so the old URL dies. Rotating keeps the level and kills the old URL immediately; rotate cannot be combined with access. Path parameters: - `id` (string, required) — The session id Body (application/json): - `rotate` (boolean) — default: false - `access` (enum) — "prompt" · "view" — default: "prompt" Responses: - `200` — OK - `token` (string, required) - `access` (enum) — "prompt" · "view" - `shareUrl` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/share \ -H "Authorization: Bearer ast_..." ``` ### DELETE /sessions/{id}/share Turn sharing off — the link stops working immediately (scope: write) Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/share \ -H "Authorization: Bearer ast_..." ``` ### GET /links/{token}/events Read the conversation a share link opens, as any signed-in caller holding the link Answers the session as its reader sees it and one page of people's turns and the agent's replies, text only (no attachments, tool calls, system messages or marks), oldest first, with the same opaque cursor as /sessions/{id}/events. Works for view-only and guest links. Unknown, rotated, disabled and deleted links all answer 404 not_found; reads are rate-limited per token (429 rate_limited with Retry-After). Needs a token with read scope from any workspace; the link is the grant, so the caller need not belong to the session's workspace. Path parameters: - `token` (string, required) Query parameters: - `cursor` (string) — cursor from the previous page - `limit` (integer) - `order` (string) — desc reads newest-first; the cursor then continues older Responses: - `200` — OK - `session` (object, required) - `id` (string, required) - `title` (string, required) - `agent_type` (string, required) - `owner` (object, required) - `name` (string) - `workspace` (object, required) - `name` (string) - `events` (object[], required) — user.message and agent.message events, text parts only - `cursor` (string, required) - `hasMore` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0/events' \ -H "Authorization: Bearer ast_..." ``` ### POST /links/{token}/messages Send a message through a guest share link, as any signed-in caller holding it (scope: write) What the guest page's composer does: the turn runs and bills as the session's owner, and the caller does not become a member. Body {text}. Only a guest link (access prompt) can send: a view-only link is 403 link_view_only. Unknown, rotated, disabled and deleted links are 404 not_found. An Idempotency-Key header (up to 200 characters) makes retries of one message send it once. 402 owner_out_of_credits when the owner's balance is empty; sends are rate-limited per link (429 rate_limited with Retry-After). Needs a token with write scope from any workspace; the link is the grant. Output is read with GET /links/{token}/events. Path parameters: - `token` (string, required) Body (application/json): - `text` (string, required) — max length 100000 Responses: - `202` — Accepted — body is {} - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0/messages \ -H "Authorization: Bearer ast_..." ``` ### POST /links/{token}/interrupt Stop the running turn through a guest share link (scope: write) What the guest page's Stop button does. Only a guest link can stop: a view-only link is 403 link_view_only; a link that does not resolve is 404 not_found. Never rate-limited. 502 interrupt_failed when the conversation plane refused. Path parameters: - `token` (string, required) Responses: - `200` — OK - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0/interrupt \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/archive Archive the session — irreversible; blocks new events, history stays readable (scope: write) Durably marks archived=true and sets status to terminated, tears down the pod and stored context, and refuses further messages (400 session_archived); the record and its events remain readable. A running session is refused with 400 session_running: interrupt and wait for idle first. A changed active Session whose archive mark did not commit returns 409 session_changed. Idempotent when another request already archived it. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `session` (object, required) - `id` (string) — sess_… identifier - `agentId` (string | null) — The agent id (agent_…) - `title` (string | null) - `status` (enum) — provisioning = the sandbox is booting or installing the environment's packages (a turn sent meanwhile waits for it); running = a turn is in flight; terminated = archived or the last runtime was destroyed. Use archived for the durable archive receipt — "provisioning" · "idle" · "running" · "terminated" - `provisioningStage` (string | null) — Which part of its boot the session's computer is in, as the platform observed it: creating_computer until the sandbox provider reports the computer exists, then starting_agent until the agent in it answers. A provider that does not report the computer's creation stays at creating_computer. Null once the computer is up and whenever it is not booting. — "creating_computer" · "starting_agent" · null - `archived` (boolean) — True only after this Session's archive mark was durably written; false for an active Session whose runtime was destroyed - `agentType` (string) - `llm` (string) — The model this session runs on (PATCH to change; the agent's model is unchanged) - `harnessVersion` (string) — Harness release the session runs: the one its serving pod booted, the restored checkpoint's for a native-fork handoff, the agent's pin for ACP harnesses, otherwise the current release - `reasoningEffort` (string | null) — The model-compatible effort resolved from the session pin, then agent pin. Constrained models use an allowed fallback; no-setting models return null. Unpinned unconstrained models return null for the harness default. - `subscriptionApiFallback` (boolean) — Opt-in to paid API continuation after all subscription accounts exhaust, preserving the same supported explicit model and wallet/budget gates - `modelBilling` (enum) — Who pays for model calls: platform wallet or the user's connected subscription — "platform" · "account" - `idleTimeoutSeconds` (integer | null) — Seconds the session's machine stays awake after a turn ends before it is paused; null = the platform default of 60. Set it when people use apps on the session's exposed ports after the agent finishes - `browserProfile` (string) — Resolved browser: the session's choice, then the agent's default, then mine. mine is the browser of the person whose turn it is; fresh is signed out; user: is the sites that person shared with whoever runs the turn. Applied before the next browser turn when browser profiles are enabled. - `browserLoginsSavedAt` (string | null) — Most recent browser login save-back time, or null when this session has not saved changed logins. - `default` (boolean) — Is this the agent's default session - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `metadata` (object) - `budget` (object | null) — Hard spend cap, or null when the session has none. A reached budget pauses the session (402 budget_reached on new work) until the cap is raised above consumed_cost or removed — removal is one-way. - `parentSessionId` (string | null) — The session this one came from: a helper names the session that hired it, a handoff child names the session it continued; null for a root. Every session a task's root produced, with its role, is GET /tasks/{id}?by=session on any member's id. - `parentRelation` (string | null) — How this session relates to parentSessionId: `helper` was hired by it (actl hire or the orchestration API), `handoff` continued its conversation on another agent; null for a root. — "helper" · "handoff" · null - `resources` (object[]) — The files, GitHub repositories, and read-only memory stores mounted into the session, oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `workspaceDefaults` (object | null) — What the session started with because its workspace made it a default; everything else it carries was named at creation. Null when it took nothing. - `contextFiles` (object | null) — The workspace files the session loaded into its instructions when its sandbox was last provisioned (GET /workspace-context describes the set); null before that and for a session without its workspace's store. Every other file in the store is read on demand. - `hostedBySeller` (object) — Present only when the session runs on its agent's maker's own server instead of a sandbox: what is sent to the session goes to this host. `agentType` and `llm` then name a stand-in that never runs. - `host` (string, required) - `attachments` (object[]) — Everything the session started with: its environment, vaults, mounted files and memory stores, and the workspace's always-available skills. The skills of the session's agent are on the agent and are not repeated here. - `type` (enum, required) — "environment" · "vault" · "file" · "skill" · "memory_store" - `id` (string, required) — The id of the environment, vault, file, skill or memory store - `mountPath` (string | null, required) — Where a file or memory store is mounted; null for the other types - `source` (enum, required) — default = the workspace supplied it; explicit = it was named when the session was created. A memory store on a session created before 2026-10-06 reads as explicit — "default" · "explicit" - `createdAt` (string) — ISO 8601 - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/archive \ -H "Authorization: Bearer ast_..." ``` ### PUT /sessions/{id}/mirror Write a local mirror's messages — the copy of a conversation that ran on the person's own computer (scope: write) Only for a session created with local_mirror, and only by its owner using a personal API key (a workspace key is 403). Body {messages: [{id, role: user|agent, created_at, parts}] (up to 200), title?, status?: running|idle}. Each message is upserted by its id, so a growing agent turn is sent again whole; values shaped like a secret are masked before storing. Limits: 512 KiB per message (413 message_too_large naming it), 4 MiB per body (413), 20,000 messages per session (422 mirror_quota_exceeded). A session that is not a mirror answers 409 not_a_mirror_session, an archived one 410. Nothing runs and nothing is billed. Path parameters: - `id` (string, required) — The session id Body (application/json): - `messages` (object[], required) - `id` (string, required) - `role` (enum, required) — "user" · "agent" - `created_at` (string, required) - `parts` (object[], required) - `title` (string) — max length 120 - `status` (enum) — "running" · "idle" Responses: - `200` — Stored - `stored` (integer) - `syncedAt` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/mirror \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/mirror/environment Store the working folder of a local mirror, so its chat can continue in the cloud without the computer (scope: write) Only for a session created with local_mirror, and only by its owner. Body {file_id, name, files}: file_id is a gzipped tar laid out as `/…` that the owner uploaded to their personal files (POST /files/uploads without a workspace). It is copied into the session's private storage, checked against the upload's size and sha256 (422 environment_mismatch), and replaces the folder stored before; the same folder again is a no-op. The folder may hold secrets: it is never public, and it is restored into /workspace/ only in a continuation its owner starts. A file the caller did not upload personally answers 404; a session that is not a mirror 409 not_a_mirror_session, one already continued 409 continued_in_cloud, an archived one 410. Up to 2 GiB (413 archive_too_large). Nothing runs and nothing is billed. Path parameters: - `id` (string, required) — The session id Body (application/json): - `file_id` (string, required) - `name` (string, required) — The folder's name: 1 to 80 letters, digits, spaces, dots, dashes or underscores — max length 80 - `files` (integer, required) Responses: - `200` — Stored - `sha256` (string) - `size_bytes` (integer) - `name` (string) - `files` (integer) - `staged_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/mirror/environment \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/mirror/environment The working folder stored for a local mirror (scope: read) Answers the folder its owner's computer last stored for this chat, to that owner only; 404 when none is stored. Path parameters: - `id` (string, required) — The session id Responses: - `200` — The stored folder - `sha256` (string) - `size_bytes` (integer) - `name` (string) - `files` (integer) - `staged_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/mirror/environment' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/mirror/checkpoint Store the native session of a local mirror, so its owner's continuation resumes that exact conversation (scope: write) Only for a session created with local_mirror, and only by its owner. Body {file_id, synced_at?}: synced_at is the syncedAt receipt from PUT /mirror before capturing the archive. Without it, continuation uses the synced messages. The file is an asteroids.native-checkpoint v1 ZIP of at most 16 MiB (the files Claude Code or Codex keep for the conversation) that the owner uploaded to their personal files. It is checked whole (400 checkpoint_invalid), must be this chat's own conversation on its own agent (409 checkpoint_mismatch), and replaces the checkpoint stored before. A continuation the owner starts on the same agent resumes it natively while nothing was synced after it; otherwise it continues from the synced messages. A file the caller did not upload personally answers 404; a session that is not a mirror 409 not_a_mirror_session, one already continued 409 continued_in_cloud, an archived one 410. Nothing runs and nothing is billed. Path parameters: - `id` (string, required) — The session id Body (application/json): - `file_id` (string, required) - `synced_at` (string) Responses: - `200` — Stored - `sha256` (string) - `size_bytes` (integer) - `harness` (enum) — "claude_code" · "codex" - `harness_version` (string | null) - `staged_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/mirror/checkpoint \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/mirror/checkpoint The native session stored for a local mirror (scope: read) Answers the checkpoint its owner's computer last stored for this chat, to that owner only; 404 when none is stored. Path parameters: - `id` (string, required) — The session id Responses: - `200` — The stored checkpoint - `sha256` (string) - `size_bytes` (integer) - `harness` (enum) — "claude_code" · "codex" - `harness_version` (string | null) - `staged_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/mirror/checkpoint' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/usage The session's billed usage, newest first (scope: read) Totals plus one row per billed event — model turns, runtime, capability and connector calls — with the token breakdown, list and billed dollars, basis and duration. `limit` (default 50) pages with an opaque `cursor`; keep following `cursor` until it is null. A row whose `coveredBy` is `subscription` ran on the reader's own Claude or ChatGPT plan: `listCostUsd` is what it was worth, `billedCostUsd` is zero. Dollar amounts here are numbers in USD, not the `{amount, currency}` cents object used elsewhere. Path parameters: - `id` (string, required) — The session id Query parameters: - `cursor` (string) — A cursor from a previous page - `limit` (integer) — 1–500, default 50 Responses: - `200` — OK - `session` (string, required) — The session's public id (sess_…) - `totals` (object, required) — Summed over the whole session, not just this page. `durationComplete` is false while a turn is still running. `tokenCostsUsd` is only present when every model charge carried its original breakdown. - `listCostUsd` (number) - `billedCostUsd` (number) - `inputTokens` (integer) - `outputTokens` (integer) - `cachedInputTokens` (integer) - `cacheCreationTokens` (integer) - `modelBilledCostUsd` (number) - `runtimeCostUsd` (number) - `capabilityCallCostUsd` (number) - `connectorCallCostUsd` (number) - `turns` (integer) - `durationMs` (integer) - `durationComplete` (boolean) - `coveredBy` (string | null) — "subscription" · null - `tokenCostsUsd` (object | null) - `entries` (object[], required) - `kind` (string, required) - `at` (string, required) - `listCostUsd` (number | null) - `billedCostUsd` (number, required) - `coveredBy` (string | null) — Why a metered turn was booked at $0, when that is known — "subscription" · null - `model` (string | null) - `provider` (string | null) - `basis` (string | null) - `durationMs` (integer | null) - `interrupted` (boolean | null) - `tokens` (object, required) - `input` (integer) - `output` (integer) - `cachedInput` (integer) - `cacheCreation` (integer) - `budget` (object | null) — The session's budget summary — the same shape as `budget` on Session — or null when it has none - `cursor` (string | null, required) — Pass as ?cursor= for the next page; null on the last - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/usage' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/preview The session's live site preview (scope: read) When the session's agent has published a site preview: the URL the web's Preview tab opens, its version (publish time) and when it expires. 404 when there is none. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `url` (string, required) - `version` (string, required) - `expires_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/preview' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/assets/{asset_id} Download media an agent delivered in the session (scope: read) An image or file an agent posted in the session's transcript, which events carry as `blob://assets/`: the original bytes with their media type, as the web chat shows them. The asset must belong to the session's agent (or be the caller's own upload not yet adopted); anything else is 404. Path parameters: - `id` (string, required) — The session id - `asset_id` (string, required) — The id after blob://assets/ Responses: - `200` — The asset's bytes - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/assets/asset_id' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/subscription-recovery Where a session blocked by its plan account stands (scope: read) When a session's turn stops because the person's own Claude or ChatGPT plan account is exhausted, the platform tries their other accounts. This is the recovery generation, the account it selected, and the `revision` a paid continuation pins to. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/subscription-recovery' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/subscription-recovery Continue this one session on AgentSky credits (scope: write) Consent to continue this session on platform credits from `revision` (from the GET), as the chat's blocked notice does. Only this session moves; `POST /model-subscriptions/{provider}/switch-to-credits` moves every agent on a plan. 402 `insufficient_credits` when the balance cannot cover it; 503 `continuation_pending` means the consent is saved and the continuation retries on its own. A workspace key is refused: this consent is the person's. Path parameters: - `id` (string, required) — The session id Body (application/json): - `revision` (string, required) Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/subscription-recovery \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/policies List session-level policy rules (scope: read) Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `rules` (object[]) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/policies' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/policies Create a session-level policy rule (owner only) (scope: write) Path parameters: - `id` (string, required) — The session id Body (application/json): - option 1 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("TOOL_CALL", required) - `config` (object, required) - `action` (enum, required) — "allow" · "ask" · "deny" - `matcher` (string) — max length 255 - `commandPatterns` (string[]) — max length 500 - option 2 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("SPEND_CAP", required) - `config` (object, required) - `warnUsdMicros` (integer | null) - `hardCapUsdMicros` (integer, required) - option 3 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("CALL_COUNT", required) - `config` (object, required) - `limitPerSession` (integer, required) - option 4 (object) - `name` (string, required) — max length 128 - `enabled` (boolean) — default: true - `position` (integer) — default: 0 - `kind` ("CAPABILITY", required) - `config` (map>, required) — "allow" · "ask" · "deny" Responses: - `201` — Created - `rule` (object) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/policies \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"name":"Spend cap","kind":"SPEND_CAP","config":{"hardCapUsdMicros":5000000,"warnUsdMicros":4000000}}' ``` ### GET /sessions/{id}/policies/{ruleId} Get a session-level policy rule (scope: read) Path parameters: - `id` (string, required) — The session id - `ruleId` (string, required) Responses: - `200` — OK - `rule` (object) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/policies/cmtb5p0l3r8w2k01s6x7n9q4' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /sessions/{id}/policies/{ruleId} Update a session-level policy rule (owner only) (scope: write) Path parameters: - `id` (string, required) — The session id - `ruleId` (string, required) Body (application/json): - `name` (string) — max length 128 - `enabled` (boolean) - `position` (integer) - `config` (object | map>) - option 1 (object) - `action` (enum, required) — "allow" · "ask" · "deny" - `matcher` (string) — max length 255 - `commandPatterns` (string[]) — max length 500 - option 2 (object) - `warnUsdMicros` (integer | null) - `hardCapUsdMicros` (integer, required) - option 3 (object) - `limitPerSession` (integer, required) - option 4 (map>) — "allow" · "ask" · "deny" Responses: - `200` — OK - `rule` (object) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/policies/cmtb5p0l3r8w2k01s6x7n9q4 \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"config":{"hardCapUsdMicros":10000000,"warnUsdMicros":8000000}}' ``` ### DELETE /sessions/{id}/policies/{ruleId} Delete a session-level policy rule (owner only) (scope: write) Path parameters: - `id` (string, required) — The session id - `ruleId` (string, required) Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/policies/cmtb5p0l3r8w2k01s6x7n9q4 \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/effective-policy Effective merged policy for a session across all layers (scope: read) Returns all policy rules for workspace → agent → session, with a note when native tool policies prevent an unsupported runtime from starting. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `rules` (object[]) - `id` (string, required) - `name` (string, required) - `kind` (enum, required) — "TOOL_CALL" · "SPEND_CAP" · "CALL_COUNT" · "CAPABILITY" - `enabled` (boolean, required) - `config` (object, required) - `layer` (enum, required) — "workspace" · "agent" · "session" - `position` (integer, required) - `createdAt` (string, required) - `updatedAt` (string, required) - `note` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/effective-policy' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/studio Deprecated: the address of the web app running inside the session's pod (scope: write) Replaced by `PUT /sessions/{id}/ports/{port}` and `POST …/ports/{port}/grants`; kept for one release while the consumer app moves. Minted once per session and stable afterwards. `port` is what the app inside the pod listens on — 1024–65535, defaulting to the studio's own port. An archived session answers 400 session_archived. Path parameters: - `id` (string, required) — The session id Body (application/json): - `port` (integer) — Omitted = the studio's default port Responses: - `200` — OK - `url` (string, required) — https://studio-.agentsky.dev — the token is the whole address - `port` (integer, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/studio \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/ports The session's exposed ports, oldest first (scope: read) Every local server the agent runs inside the pod that has been exposed on an origin of its own. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `ports` (object[], required) - `port` (integer, required) — What the app listens on inside the pod - `id` (string, required) — Names the origin; not a credential - `url` (string, required) — https://app-.agentsky.dev/ — stable for the port's life - `visibility` (enum, required) — "private" · "public" - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/ports' \ -H "Authorization: Bearer ast_..." ``` ### PUT /sessions/{id}/ports/{port} Expose a port the agent's local server listens on (scope: write) Create-or-get: the port gets a stable origin `https://app-.agentsky.dev/` relayed to the pod (HTTP and WebSocket) while the pod runs; calling again returns the same record, and a `visibility` in the body replaces the stored one in place. `port` is 1024–65535 and never the pod's own (400 invalid_request). A private port (the default) admits a browser only through an entry link from `POST …/grants`; a public port admits anyone with the address. A parked pod answers a paused page and is never woken by a visit. An archived session answers 400 session_archived. Path parameters: - `id` (string, required) — The session id - `port` (integer, required) — The port the app listens on inside the pod Body (application/json): - `visibility` (enum) — Omitted = keep the stored value (private for a new port) — "private" · "public" Responses: - `200` — OK - `port` (integer, required) — What the app listens on inside the pod - `id` (string, required) — Names the origin; not a credential - `url` (string, required) — https://app-.agentsky.dev/ — stable for the port's life - `visibility` (enum, required) — "private" · "public" - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/ports/port \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/ports/{port} One exposed port (scope: read) Path parameters: - `id` (string, required) — The session id - `port` (integer, required) — The port the app listens on inside the pod Responses: - `200` — OK - `port` (integer, required) — What the app listens on inside the pod - `id` (string, required) — Names the origin; not a credential - `url` (string, required) — https://app-.agentsky.dev/ — stable for the port's life - `visibility` (enum, required) — "private" · "public" - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/ports/port' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /sessions/{id}/ports/{port} Stop exposing a port (scope: write) The origin answers 404 from then on (within a few seconds); browsers that had entered are signed out with it. Path parameters: - `id` (string, required) — The session id - `port` (integer, required) — The port the app listens on inside the pod Responses: - `204` — Revoked - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/ports/port \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/ports/{port}/grants An entry link for an exposed port (scope: write) For a private port: a single-use link, good for a minute, that signs the browser opening it in to the port's origin (a cookie that lives twelve hours) and lands on `path`; minting again supersedes the previous unused link. For a public port: the plain address. An archived session answers 400 session_archived. Path parameters: - `id` (string, required) — The session id - `port` (integer, required) — The port the app listens on inside the pod Body (application/json): - `path` (string) — Where on the app to land after entering; a path on the port's origin, default / Responses: - `200` — OK - `url` (string, required) — A private port: a single-use entry link, good for a minute; a public port: the plain address - `expires_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/ports/port/grants \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/resources List the session's resources, oldest first (scope: read) `limit` (1–1000) pages the list with an opaque `page` cursor; omitted lists every resource in one answer. Path parameters: - `id` (string, required) — The session id (sess_…) Query parameters: - `limit` (integer) - `page` (string) — A next_page cursor Responses: - `200` — OK - `data` (object[], required) — Oldest first - option 1 (object) - `type` (enum, required) — "file" - `id` (string, required) — sesrsc_… - `file_id` (string, required) — The per-session copy (file_…), read-only at mount_path - `mount_path` (string, required) — Absolute, under /workspace - `created_at` (string, required) - `updated_at` (string, required) - option 2 (object) - `type` (enum, required) — "github_repository" - `id` (string, required) — sesrsc_… - `url` (string, required) — https://github.com/{owner}/{repo} - `mount_path` (string, required) — Absolute, under /workspace; the clone's root - `checkout` (object) — {type:"branch", name} or {type:"commit", sha}; absent = the default branch - `type` (enum) — "branch" · "commit" - `name` (string) - `sha` (string) - `created_at` (string, required) - `updated_at` (string, required) - option 3 (object) - `type` (enum, required) — "memory_store" - `id` (string, required) — smsr_… - `memory_store_id` (string, required) — memstore_… - `mount_path` (string, required) — Absolute, under /mnt/memory - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — Optional per-session guidance - `created_at` (string, required) - `updated_at` (string, required) - `next_page` (string | null, required) — Pass back as ?page= for the next page; null when `limit` was omitted or the list ended - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/resources' \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "data": [ { "type": "file", "id": "sesrsc_cmtc1a2b", "file_id": "file_cmtc1a2c", "mount_path": "/workspace/data/signups.csv", "created_at": "2026-09-11T18:00:00.000Z", "updated_at": "2026-09-11T18:00:00.000Z" }, { "type": "github_repository", "id": "sesrsc_cmtc1a2d", "url": "https://github.com/acme/growth", "mount_path": "/workspace/growth", "checkout": { "type": "branch", "name": "main" }, "created_at": "2026-09-11T18:00:00.000Z", "updated_at": "2026-09-11T18:00:00.000Z" }, { "type": "memory_store", "id": "smsr_cmtc1a2e", "memory_store_id": "memstore_cmtb9x4p", "mount_path": "/mnt/memory/memstore_cmtb9x4p", "access": "read_only", "created_at": "2026-09-11T18:00:00.000Z", "updated_at": "2026-09-11T18:00:00.000Z" } ], "next_page": null } ``` ### POST /sessions/{id}/resources Add a file to a running session (scope: write) Files only — repositories and memory stores can only be attached at session create. The file is copied read-only to `/workspace` before the session's next turn; `mount_path` follows the create rules (rooted under /workspace, no escape, no overlap with another resource). 400 too_many_resources past 500 per session; 400 session_archived on an archived session. Path parameters: - `id` (string, required) — The session id (sess_…) Body (application/json): - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) — max length 128 - `mount_path` (string | null) — max length 4096 - type: "github_repository" (object) - `type` ("github_repository", required) - `url` (string, required) — max length 2048 - `authorization_token` (string) — max length 1024 - `checkout` (object | null) - type: "branch" (object) - `type` ("branch", required) - `name` (string, required) — max length 255 - type: "commit" (object) - `type` ("commit", required) - `sha` (string, required) — max length 64 - `mount_path` (string | null) — max length 4096 - type: "memory_store" (object) - `type` ("memory_store", required) - `memory_store_id` (string, required) — max length 255 - `access` (enum, required) — "read_only" · "read_write" - `instructions` (string) — max length 4096 - `mount_path` (string | null) — max length 4096 Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/resources \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"type":"file","file_id":"file_cmtb9x4p","mount_path":"/data/signups.csv"}' ``` Example response (200 OK): ```json { "type": "file", "id": "sesrsc_cmtc1a2b", "file_id": "file_cmtc1a2c", "mount_path": "/workspace/data/signups.csv", "created_at": "2026-09-11T18:00:00.000Z", "updated_at": "2026-09-11T18:00:00.000Z" } ``` ### GET /sessions/{id}/resources/{rid} One resource (scope: read) Path parameters: - `id` (string, required) — The session id (sess_…) - `rid` (string, required) — The resource id (sesrsc_… or smsr_…) Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/resources/sesrsc_cmtc1a2d' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/resources/{rid} Rotate a repository's token (scope: write) Only `github_repository` resources accept an update, and the token is the only field: it is re-encrypted and the session's egress policy is recompiled and pushed to its live pod — nothing changes inside the sandbox. A file or memory store answers 400. A token that conflicts with another secret's Authorization header on github.com answers 400 egress_header_conflict. Path parameters: - `id` (string, required) — The session id (sess_…) - `rid` (string, required) — The resource id (sesrsc_… or smsr_…) Body (application/json): - `authorization_token` (string, required) — max length 1024 Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/resources/sesrsc_cmtc1a2d \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"authorization_token":"github_pat_…"}' ``` ### DELETE /sessions/{id}/resources/{rid} Remove a file from the session (scope: write) Files only — the resource and its per-session copy are deleted and the pod unlinks the file before the session's next turn. A repository or memory store answers 400: neither can be removed mid-session. Path parameters: - `id` (string, required) — The session id (sess_…) - `rid` (string, required) — The resource id (sesrsc_… or smsr_…) Responses: - `200` — OK - `type` (enum, required) — "session_resource_deleted" - `id` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/resources/sesrsc_cmtc1a2d \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "type": "session_resource_deleted", "id": "sesrsc_cmtc1a2b" } ``` ## Conversation ### POST /sessions/{id}/messages Deprecated — use POST /sessions/{id}/events (scope: write) The legacy input shape ({parts}); accepted for one release and translated to a single user.message, then 410. Optional Idempotency-Key header dedupes retries. Path parameters: - `id` (string, required) — The session id Body (application/json): - `parts` (object[], required) - type: "text" (object) - `index` (integer, required) - `type` ("text", required) - `text` (string) — default: "" - `stream_id` (string | null) - `stream_index` (integer | null) - type: "reasoning" (object) - `index` (integer, required) - `type` ("reasoning", required) - `text` (string) — default: "" - `redacted` (boolean) — default: false - `stream_id` (string | null) - `stream_index` (integer | null) - type: "tool_call" (object) - `index` (integer, required) - `type` ("tool_call", required) - `call_id` (string, required) - `tool_name` (string, required) - `args` (map) — default: {} - `args_partial` (string | null) - type: "tool_result" (object) - `index` (integer, required) - `type` ("tool_result", required) - `call_id` (string, required) - `tool_name` (string, required) - `status` (enum) — "ok" · "error" — default: "ok" - `result` (map) — default: {} - type: "file" (object) - `index` (integer, required) - `type` ("file", required) - `name` (string, required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `size_bytes` (integer | null) - type: "image" (object) - `index` (integer, required) - `type` ("image", required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `alt` (string | null) - `width` (integer | null) - `height` (integer | null) - type: "video" (object) - `index` (integer, required) - `type` ("video", required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `alt` (string | null) - `width` (integer | null) - `height` (integer | null) - `duration_ms` (integer | null) - `size_bytes` (integer | null) - `thumbnail_uri` (string | null) - type: "status" (object) - `index` (integer, required) - `type` ("status", required) - `level` (enum, required) — "thinking" · "working" · "waiting" · "idle" · "done" - `text` (string | null) - type: "error" (object) - `index` (integer, required) - `type` ("error", required) - `code` (string, required) - `message` (string, required) - `retryable` (boolean) — default: false Responses: - `202` — Accepted — body is {}; output arrives on the stream - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/messages \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"parts":[{"type":"text","index":0,"text":"Summarize the signups from today"}]}' ``` Example response (202 Accepted): ```json {} ``` ### POST /sessions/{id}/events Send events — the one input: user.message, user.interrupt, system.message (scope: write) The conversation plane (edge-routed to the chat-proxy). Body {events: [...]} (1–50), optionally requireIdle: true for one user.message (and optional trailing system.message). Exclusive admission refuses a busy session with 409 before persisting or delivering the message; a successful echo's user id names the admitted run. Its admission field is created only when this POST committed the exclusive input; an idempotent replay returns replayed. This field is not part of history or SSE. This is a Session admission receipt, not proof that another client's earlier queued joined input has finished on the Pod. Ordinary sends may join an open run. validated all-or-nothing. A user.message carries content blocks: text, image and document with base64 / url / file sources (inline base64 ≤ 20 MiB per request; larger content goes through POST /files). A user.message may set interaction_mode: "plan" (the agent inspects and answers with a reviewable plan, then waits for a choice to implement) or "build"; omitted, the session's own metadata.interaction_mode applies (build when unset). A system.message (text only) must be last and follow the user.message it accompanies: privileged context for that turn and, through the agent's transcript, later turns. user.interrupt stops the in-flight turn; with mode: "save" it instead asks the run to start no new work, save what it has (files plus a progress note in the working directory) and end its turn with a reply listing what was saved, and stops it only if it is still running after a 10 minute grace window (echo status saving, or no_turn when nothing is running). A later user.message continues in the same session. Every event echoes back in the history shape with a stable id; sources are echoed as file sources naming per-session copies (never base64). Optional Idempotency-Key header: a replay returns the original echo. 402 insufficient_credits / budget_reached, 503 egress_policy_failed, 400 session_archived refuse the whole request before any row is written. Path parameters: - `id` (string, required) — The session id Body (application/json): - `events` (object[], required) - type: "user.message" (object) - `type` ("user.message", required) - `content` (object[], required) - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "image" (object) - `type` ("image", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - type: "document" (object) - `type` ("document", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "text" (object) - `type` ("text", required) - `media_type` ("text/plain") — default: "text/plain" - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - `title` (string | null) - `context` (string | null) - `interaction_mode` (enum | null) — "plan" · "build" - type: "system.message" (object) - `type` ("system.message", required) - `content` (object[], required) - `type` ("text", required) - `text` (string, required) - type: "user.interrupt" (object) - `type` ("user.interrupt", required) - `mode` (enum) — "immediate" · "save" - `requireIdle` (boolean) Responses: - `200` — OK — one echo per event, in order - `data` (any[], required) — One echo per event, in order, in the events-history shape - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/events \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"requireIdle":true,"events":[{"type":"user.message","content":[{"type":"text","text":"What is in this screenshot?"},{"type":"image","source":{"type":"file","file_id":"file_cmtb9x4p"}}]},{"type":"system.message","content":[{"type":"text","text":"Answer in one paragraph."}]}]}' ``` Example response (200 OK): ```json { "data": [ { "id": "api-user-3f2c…", "type": "user.message", "admission": "created", "sessionId": "sess_cmtb7q2j", "agentId": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "at": "2026-09-10T18:00:00.000Z", "content": [ { "type": "text", "text": "What is in this screenshot?" }, { "type": "image", "source": { "type": "file", "file_id": "file_cmtb9y1q" } } ] }, { "id": "api-user-3f2c…#system", "type": "system.message", "sessionId": "sess_cmtb7q2j", "agentId": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "at": "2026-09-10T18:00:00.000Z", "content": [ { "type": "text", "text": "Answer in one paragraph." } ] } ] } ``` ### GET /sessions/{id}/events The event history — the same event objects the stream delivers (scope: read) The session's history, one event per message: user.message and system.message (with content), agent.message (with parts), user.interrupt, and the turn marks session.status_running, session.status_idle (stop_reason.type is end_turn, retries_exhausted, provider_lost or budget_reached) and session.error. context.input_observed is an authenticated history-only bound context-presence fact, not model usage or task success. agent.reasoning, agent.tool_use, agent.tool_result and agent.status appear on the live stream only, not in history. Oldest-first with an opaque numeric cursor; order=desc reads newest-first and its cursor continues older, so a reader can open on the end of a long session. Every event carries rowId, the number the stream frames carry, so a reader resumes the stream after the newest one it holds. Reconnect = open the stream, list events, dedupe by event id. types[] filters (e.g. types[]=user.message&types[]=agent.message is the transcript view). Path parameters: - `id` (string, required) — The session id Query parameters: - `cursor` (string) - `limit` (integer) - `types[]` (array) - `order` (string) Responses: - `200` — OK - `events` (object[], required) — Oldest-first; newest-first with order=desc - `id` (string, required) — Stable event id — the dedupe key across stream ∪ events - `rowId` (integer) — Position in the session's history; the stream resumes after the newest one a reader holds - `type` (string, required) — user.message · system.message (both history only — never on the live stream; they carry `content`) · agent.message · agent.reasoning · agent.tool_use · agent.tool_result · agent.status · user.interrupt · session.status_running · session.status_idle · session.error · session.status_terminated · session.deleted · context.input_observed (authenticated history only: host-admitted input/client UUID/response/gateway identifiers, scope digests and UTF-8 byte coverage; no pod-declared native session authority, raw capture or private pointer) - `sessionId` (string, required) - `agentId` (string | null) — The agent id (agent_…) - `at` (string) — ISO 8601 - `messageId` (string) — agent.message - `parts` (object[]) — agent.message - type: "text" (object) - `index` (integer, required) - `type` ("text", required) - `text` (string) — default: "" - `stream_id` (string | null) - `stream_index` (integer | null) - type: "reasoning" (object) - `index` (integer, required) - `type` ("reasoning", required) - `text` (string) — default: "" - `redacted` (boolean) — default: false - `stream_id` (string | null) - `stream_index` (integer | null) - type: "tool_call" (object) - `index` (integer, required) - `type` ("tool_call", required) - `call_id` (string, required) - `tool_name` (string, required) - `args` (map) — default: {} - `args_partial` (string | null) - type: "tool_result" (object) - `index` (integer, required) - `type` ("tool_result", required) - `call_id` (string, required) - `tool_name` (string, required) - `status` (enum) — "ok" · "error" — default: "ok" - `result` (map) — default: {} - type: "file" (object) - `index` (integer, required) - `type` ("file", required) - `name` (string, required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `size_bytes` (integer | null) - type: "image" (object) - `index` (integer, required) - `type` ("image", required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `alt` (string | null) - `width` (integer | null) - `height` (integer | null) - type: "video" (object) - `index` (integer, required) - `type` ("video", required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `alt` (string | null) - `width` (integer | null) - `height` (integer | null) - `duration_ms` (integer | null) - `size_bytes` (integer | null) - `thumbnail_uri` (string | null) - type: "status" (object) - `index` (integer, required) - `type` ("status", required) - `level` (enum, required) — "thinking" · "working" · "waiting" · "idle" · "done" - `text` (string | null) - type: "error" (object) - `index` (integer, required) - `type` ("error", required) - `code` (string, required) - `message` (string, required) - `retryable` (boolean) — default: false - `text` (string) — agent.message — parts flattened to plain text - `content` (object[]) — user.message / system.message — the CMA content blocks as sent (text · image · document); every image/document source is a `file` source naming a per-session copy readable through GET /files/{file_id}/content - `part` (object) — reasoning / tool_use / tool_result / status events - type: "text" (object) - `index` (integer, required) - `type` ("text", required) - `text` (string) — default: "" - `stream_id` (string | null) - `stream_index` (integer | null) - type: "reasoning" (object) - `index` (integer, required) - `type` ("reasoning", required) - `text` (string) — default: "" - `redacted` (boolean) — default: false - `stream_id` (string | null) - `stream_index` (integer | null) - type: "tool_call" (object) - `index` (integer, required) - `type` ("tool_call", required) - `call_id` (string, required) - `tool_name` (string, required) - `args` (map) — default: {} - `args_partial` (string | null) - type: "tool_result" (object) - `index` (integer, required) - `type` ("tool_result", required) - `call_id` (string, required) - `tool_name` (string, required) - `status` (enum) — "ok" · "error" — default: "ok" - `result` (map) — default: {} - type: "file" (object) - `index` (integer, required) - `type` ("file", required) - `name` (string, required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `size_bytes` (integer | null) - type: "image" (object) - `index` (integer, required) - `type` ("image", required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `alt` (string | null) - `width` (integer | null) - `height` (integer | null) - type: "video" (object) - `index` (integer, required) - `type` ("video", required) - `media_type` (string, required) - `uri` (string | null) - `data` (string | null) - `alt` (string | null) - `width` (integer | null) - `height` (integer | null) - `duration_ms` (integer | null) - `size_bytes` (integer | null) - `thumbnail_uri` (string | null) - type: "status" (object) - `index` (integer, required) - `type` ("status", required) - `level` (enum, required) — "thinking" · "working" · "waiting" · "idle" · "done" - `text` (string | null) - type: "error" (object) - `index` (integer, required) - `type` ("error", required) - `code` (string, required) - `message` (string, required) - `retryable` (boolean) — default: false - `stop_reason` (object) — session.status_idle — discriminate on type; never break on bare idle - `type` (string) — "end_turn" (also after an interrupt) | "retries_exhausted" (preceded by an exhausted engine error) | "provider_lost" (confirmed runtime Pod loss, preceded by an exhausted session.error) | "budget_reached"; more reserved - `error` (object) — session.error — the stream stays open; a session.status_idle follows once the turn is over - `type` (string) — e.g. "pod_unavailable" - `message` (string) - `retry_status` (string) — "retrying" (the turn continues) | "exhausted" (the turn is over) - `by` (string) — user.interrupt — who requested the interrupt - `mode` (enum) — user.interrupt — present on a save interrupt: the run was asked to save its work and end itself — "save" - `deadline_at` (string) — user.interrupt with mode save — when the run is stopped if it has not ended by then - `cause` (enum) — user.interrupt — present when the platform stopped a run whose save grace window ran out — "save_timeout" - `actor` (object) — user.message only, in history and in the send echo: who sent it, present only when that was not the session's owner - `id` (string) - `name` (string | null) - `cursor` (string | null, required) — Pass back as ?cursor= for the next page; null when the session has no events - `hasMore` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/events' \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "events": [ { "id": "api-user-8c41f0…#idle", "rowId": 42, "type": "session.status_idle", "sessionId": "sess-8c41f0…", "agent": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "stop_reason": { "type": "end_turn" } } ], "cursor": "42", "hasMore": false } ``` ### GET /sessions/{id}/stream The standing event stream (SSE) (scope: read) text/event-stream, stays open across turns. If a run is in flight when the stream opens, its status marks so far (session.status_running, session.error, user.interrupt) are replayed from history under their own ids and timestamps, so a stream opened mid-run still sees the boundary its duration is measured from; a run that has already ended is not replayed — read it from the event history. Everything else is live. Dedupe by event id. Events: agent.message, agent.reasoning, agent.tool_use, agent.tool_result, agent.status, session.status_running, session.status_idle (carries stop_reason — never break on bare idle), session.error (the stream stays open; an idle follows), user.interrupt, session.status_terminated (terminal), session.deleted (terminal). User messages are not sent on the live stream — they appear in the event history only. Reconnect = reopen + list events + dedupe by event id. Path parameters: - `id` (string, required) — The session id Responses: - `200` — SSE stream - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -N 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/stream' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/interrupt Abort the in-flight turn (scope: write) Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `status` (enum, required) — no_turn = nothing in flight; otherwise user.interrupt echoes on the stream and the pod's session.status_idle follows — "interrupting" · "no_turn" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/interrupt \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "status": "interrupting" } ``` ## Goals ### GET /sessions/{id}/goal The session's standing goal, or null (scope: read) A goal is an objective the session works toward on its own: after each turn settles, the platform grades the work against the goal's criteria and either takes another turn or stops. At most one goal per session. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `goal` (object | null, required) - `id` (string, required) — goal_… - `session` (string, required) — sess_… - `objective` (string, required) - `criteria` (object[], required) - `id` (string, required) — Stable within the goal, e.g. c1 - `text` (string, required) - `command` (string) — Present when a shell command decides this criterion: it runs in the session's workspace after each iteration, and exit 0 means met. The grader cannot overturn it. - `criteria_source` (enum, required) — Whether the caller supplied the criteria or the platform derived them from the objective — "user" · "derived" - `status` (enum, required) — "active" · "paused" · "completed" · "stopped" - `stop_reason` (string | null, required) — Why a stopped goal stopped; null in every other status — "failed" · "max_iterations" · "spend_budget" · "token_budget" · "time_budget" · "no_progress" · "cleared" · "session_ended" · null - `iteration` (integer, required) — Self-started turns taken so far - `max_iterations` (integer, required) - `max_spend` (any | null, required) - `consumed_spend` (any, required) — What this session has spent since the goal started - `max_tokens` (integer | null, required) — Token ceiling for this goal; null = none - `consumed_tokens` (integer, required) — Uncached input plus output tokens over the session's metered model calls since the goal started, grading included. Calls on the session's own model subscription are not metered and not counted. - `deadline_at` (string | null, required) - `last_evaluation` (any | null, required) - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/goal' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/goal Set the session's goal, replacing any existing one (scope: write) Work starts immediately — no separate message is needed, and sending one is not how a goal begins. Criteria are optional: omitted, they are derived from the objective so completion is always judged against an explicit list, which the response returns and PATCH can correct. A criterion given as { text, command } is decided by running the command in the session's workspace (exit 0 = met) instead of by the grader. A person's message always outranks the loop: while a turn is running the goal waits, and takes the turn after it. Grading is a model call billed to the session; with an empty wallet deriving criteria is refused with 402 insufficient_credits. Setting a goal stops the run the goal it replaces had started. Path parameters: - `id` (string, required) — The session id Body (application/json): - `objective` (string, required) — What the agent should work toward, in the user's words — max length 4000 - `criteria` ((string | object)[]) — Independently checkable requirements; omitted = derived from the objective - option 1 (string) — max length 500 - option 2 (object) - `text` (string, required) — What the criterion requires, in words — max length 500 - `command` (string) — Shell command run in the session's workspace; exit 0 means the criterion is met — max length 1000 - `max_iterations` (integer | null) — Self-started turns this goal may take before it stops. Omitted = 20; null = no iteration ceiling - `max_no_progress` (integer | null) — Consecutive turns without a tool call before stopping. Omitted = 3; null = no no-progress ceiling - `max_spend` (object | null) — Spend ceiling for this goal; null = none - `amount` (string, required) — Whole US cents as an integer decimal string, e.g. "500" for $5.00 — max length 15 - `currency` ("USD", required) - `max_tokens` (integer | null) - `max_duration_minutes` (integer | null) — Wall-clock ceiling from the goal's start, extended by any time it spends paused Responses: - `201` — Created - `goal` (object, required) - `id` (string, required) — goal_… - `session` (string, required) — sess_… - `objective` (string, required) - `criteria` (object[], required) - `id` (string, required) — Stable within the goal, e.g. c1 - `text` (string, required) - `command` (string) — Present when a shell command decides this criterion: it runs in the session's workspace after each iteration, and exit 0 means met. The grader cannot overturn it. - `criteria_source` (enum, required) — Whether the caller supplied the criteria or the platform derived them from the objective — "user" · "derived" - `status` (enum, required) — "active" · "paused" · "completed" · "stopped" - `stop_reason` (string | null, required) — Why a stopped goal stopped; null in every other status — "failed" · "max_iterations" · "spend_budget" · "token_budget" · "time_budget" · "no_progress" · "cleared" · "session_ended" · null - `iteration` (integer, required) — Self-started turns taken so far - `max_iterations` (integer, required) - `max_spend` (object | null, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `consumed_spend` (any, required) — What this session has spent since the goal started - `max_tokens` (integer | null, required) — Token ceiling for this goal; null = none - `consumed_tokens` (integer, required) — Uncached input plus output tokens over the session's metered model calls since the goal started, grading included. Calls on the session's own model subscription are not metered and not counted. - `deadline_at` (string | null, required) - `last_evaluation` (object | null, required) - `result` (enum, required) — "satisfied" · "needs_revision" · "failed" - `explanation` (string, required) - `criteria` (object[], required) - `id` (string, required) - `met` (boolean, required) - `reason` (string, required) - `judged_by` (enum) — What decided this criterion on this iteration — "grader" · "command" - `iteration` (integer, required) - `at` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/goal \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"objective":"Get the failing integration tests in packages/api passing without weakening them.","criteria":[{"text":"The integration tests pass","command":"cd packages/api && npm test"},"No test assertion was deleted, skipped, or loosened","The fix is in source, not in the tests"],"max_iterations":15,"max_spend":{"amount":"500","currency":"USD"},"max_tokens":2000000}' ``` ### PATCH /sessions/{id}/goal Edit the goal in place (scope: write) A course correction for an active or paused goal, not a restart: the iteration count and spend already consumed carry over. Editing criteria marks them as the caller's. A completed or stopped goal returns 409; POST a new goal to start another outcome. Path parameters: - `id` (string, required) — The session id Body (application/json): - `objective` (string) — max length 4000 - `criteria` ((string | object)[]) - option 1 (string) — max length 500 - option 2 (object) - `text` (string, required) — What the criterion requires, in words — max length 500 - `command` (string) — Shell command run in the session's workspace; exit 0 means the criterion is met — max length 1000 - `max_iterations` (integer | null) - `max_no_progress` (integer | null) - `max_spend` (object | null) - `amount` (string, required) — Whole US cents as an integer decimal string, e.g. "500" for $5.00 — max length 15 - `currency` ("USD", required) - `max_tokens` (integer | null) - `max_duration_minutes` (integer | null) Responses: - `200` — OK - `goal` (object, required) - `id` (string, required) — goal_… - `session` (string, required) — sess_… - `objective` (string, required) - `criteria` (object[], required) - `id` (string, required) — Stable within the goal, e.g. c1 - `text` (string, required) - `command` (string) — Present when a shell command decides this criterion: it runs in the session's workspace after each iteration, and exit 0 means met. The grader cannot overturn it. - `criteria_source` (enum, required) — Whether the caller supplied the criteria or the platform derived them from the objective — "user" · "derived" - `status` (enum, required) — "active" · "paused" · "completed" · "stopped" - `stop_reason` (string | null, required) — Why a stopped goal stopped; null in every other status — "failed" · "max_iterations" · "spend_budget" · "token_budget" · "time_budget" · "no_progress" · "cleared" · "session_ended" · null - `iteration` (integer, required) — Self-started turns taken so far - `max_iterations` (integer, required) - `max_spend` (object | null, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `consumed_spend` (any, required) — What this session has spent since the goal started - `max_tokens` (integer | null, required) — Token ceiling for this goal; null = none - `consumed_tokens` (integer, required) — Uncached input plus output tokens over the session's metered model calls since the goal started, grading included. Calls on the session's own model subscription are not metered and not counted. - `deadline_at` (string | null, required) - `last_evaluation` (object | null, required) - `result` (enum, required) — "satisfied" · "needs_revision" · "failed" - `explanation` (string, required) - `criteria` (object[], required) - `id` (string, required) - `met` (boolean, required) - `reason` (string, required) - `judged_by` (enum) — What decided this criterion on this iteration — "grader" · "command" - `iteration` (integer, required) - `at` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/goal \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"criteria":[{"text":"The integration tests pass","command":"cd packages/api && npm test"},"No test assertion was deleted, skipped, or loosened"]}' ``` ### DELETE /sessions/{id}/goal Clear the goal (scope: write) Stops the loop for good with stop_reason "cleared", and interrupts the run the goal itself started if one is going. The record stays readable so the session can still report what it was working on; setting a new goal replaces it. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `goal` (object, required) - `id` (string, required) — goal_… - `session` (string, required) — sess_… - `objective` (string, required) - `criteria` (object[], required) - `id` (string, required) — Stable within the goal, e.g. c1 - `text` (string, required) - `command` (string) — Present when a shell command decides this criterion: it runs in the session's workspace after each iteration, and exit 0 means met. The grader cannot overturn it. - `criteria_source` (enum, required) — Whether the caller supplied the criteria or the platform derived them from the objective — "user" · "derived" - `status` (enum, required) — "active" · "paused" · "completed" · "stopped" - `stop_reason` (string | null, required) — Why a stopped goal stopped; null in every other status — "failed" · "max_iterations" · "spend_budget" · "token_budget" · "time_budget" · "no_progress" · "cleared" · "session_ended" · null - `iteration` (integer, required) — Self-started turns taken so far - `max_iterations` (integer, required) - `max_spend` (object | null, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `consumed_spend` (any, required) — What this session has spent since the goal started - `max_tokens` (integer | null, required) — Token ceiling for this goal; null = none - `consumed_tokens` (integer, required) — Uncached input plus output tokens over the session's metered model calls since the goal started, grading included. Calls on the session's own model subscription are not metered and not counted. - `deadline_at` (string | null, required) - `last_evaluation` (object | null, required) - `result` (enum, required) — "satisfied" · "needs_revision" · "failed" - `explanation` (string, required) - `criteria` (object[], required) - `id` (string, required) - `met` (boolean, required) - `reason` (string, required) - `judged_by` (enum) — What decided this criterion on this iteration — "grader" · "command" - `iteration` (integer, required) - `at` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/goal \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/goal/pause Stop driving the goal (scope: write) Stops taking turns and interrupts the run the goal itself started, if one is going. A run a person started is left to finish; POST /sessions/{id}/interrupt stops that. Refused with 409 invalid_state unless the goal is active. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `goal` (object, required) - `id` (string, required) — goal_… - `session` (string, required) — sess_… - `objective` (string, required) - `criteria` (object[], required) - `id` (string, required) — Stable within the goal, e.g. c1 - `text` (string, required) - `command` (string) — Present when a shell command decides this criterion: it runs in the session's workspace after each iteration, and exit 0 means met. The grader cannot overturn it. - `criteria_source` (enum, required) — Whether the caller supplied the criteria or the platform derived them from the objective — "user" · "derived" - `status` (enum, required) — "active" · "paused" · "completed" · "stopped" - `stop_reason` (string | null, required) — Why a stopped goal stopped; null in every other status — "failed" · "max_iterations" · "spend_budget" · "token_budget" · "time_budget" · "no_progress" · "cleared" · "session_ended" · null - `iteration` (integer, required) — Self-started turns taken so far - `max_iterations` (integer, required) - `max_spend` (object | null, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `consumed_spend` (any, required) — What this session has spent since the goal started - `max_tokens` (integer | null, required) — Token ceiling for this goal; null = none - `consumed_tokens` (integer, required) — Uncached input plus output tokens over the session's metered model calls since the goal started, grading included. Calls on the session's own model subscription are not metered and not counted. - `deadline_at` (string | null, required) - `last_evaluation` (object | null, required) - `result` (enum, required) — "satisfied" · "needs_revision" · "failed" - `explanation` (string, required) - `criteria` (object[], required) - `id` (string, required) - `met` (boolean, required) - `reason` (string, required) - `judged_by` (enum) — What decided this criterion on this iteration — "grader" · "command" - `iteration` (integer, required) - `at` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/goal/pause \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/goal/resume Drive the goal again (scope: write) Any wall-clock deadline moves out by the time the pause consumed. Refused with 409 invalid_state unless the goal is paused. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `goal` (object, required) - `id` (string, required) — goal_… - `session` (string, required) — sess_… - `objective` (string, required) - `criteria` (object[], required) - `id` (string, required) — Stable within the goal, e.g. c1 - `text` (string, required) - `command` (string) — Present when a shell command decides this criterion: it runs in the session's workspace after each iteration, and exit 0 means met. The grader cannot overturn it. - `criteria_source` (enum, required) — Whether the caller supplied the criteria or the platform derived them from the objective — "user" · "derived" - `status` (enum, required) — "active" · "paused" · "completed" · "stopped" - `stop_reason` (string | null, required) — Why a stopped goal stopped; null in every other status — "failed" · "max_iterations" · "spend_budget" · "token_budget" · "time_budget" · "no_progress" · "cleared" · "session_ended" · null - `iteration` (integer, required) — Self-started turns taken so far - `max_iterations` (integer, required) - `max_spend` (object | null, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `consumed_spend` (any, required) — What this session has spent since the goal started - `max_tokens` (integer | null, required) — Token ceiling for this goal; null = none - `consumed_tokens` (integer, required) — Uncached input plus output tokens over the session's metered model calls since the goal started, grading included. Calls on the session's own model subscription are not metered and not counted. - `deadline_at` (string | null, required) - `last_evaluation` (object | null, required) - `result` (enum, required) — "satisfied" · "needs_revision" · "failed" - `explanation` (string, required) - `criteria` (object[], required) - `id` (string, required) - `met` (boolean, required) - `reason` (string, required) - `judged_by` (enum) — What decided this criterion on this iteration — "grader" · "command" - `iteration` (integer, required) - `at` (string, required) - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/goal/resume \ -H "Authorization: Bearer ast_..." ``` ## Participants ### GET /sessions/{id}/people Everyone who may send the session turns (scope: read) The owner, the members of its workspace when the session is visible to the workspace, and people listed with the right to prompt. These are the people a turn can name with @ and the session can be assigned to. Requires the right to prompt the session. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `people` (object[], required) - `user_id` (string, required) - `name` (string, required) - `email` (string, required) - `avatar_url` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/people' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/presence People who have the session open right now (scope: read) Everyone whose open view refreshed its presence within the last 30 seconds, most recent first. Requires the right to prompt the session. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `people` (any[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/presence' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/presence Say that you have the session open (scope: read) Repeat about every 15 seconds while the session is on screen. Answers who else has it open. Requires the right to prompt the session. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `people` (object[], required) - `user_id` (string, required) - `name` (string, required) - `email` (string, required) - `avatar_url` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/presence \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/mentions Record the people your turn named with @ (scope: write) Send the text of a turn you sent. Each "@Name" — a person's name or their email's local part — of someone who may use the session is recorded, asks for that person's attention in their task list until they open the session, and queues them a notice. Naming yourself is ignored. Requires the right to prompt the session. Path parameters: - `id` (string, required) — The session id Body (application/json): - `text` (string, required) — max length 100000 Responses: - `200` — OK - `mentioned` (object[], required) - `user_id` (string, required) - `name` (string, required) - `email` (string, required) - `avatar_url` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/mentions \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/assign Who the session is assigned to (scope: read) Requires the right to prompt the session. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `assignee` (any | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/assign' \ -H "Authorization: Bearer ast_..." ``` ### PUT /sessions/{id}/assign Assign the session to someone who may use it, or clear it with null (scope: write) Replaces any earlier assignment. The assignee sees the session as waiting on them until they open it and is sent a notice, unless they assigned it to themselves. Requires the right to prompt the session. 400 invalid_request when user_id is not someone who may use it; 503 not_available before the platform supports assignments. Path parameters: - `id` (string, required) — The session id Body (application/json): - `user_id` (string, required) Responses: - `200` — OK - `assignee` (any | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/assign \ -H "Authorization: Bearer ast_..." ``` ### DELETE /sessions/{id}/assign Clear the session's assignment (scope: write) Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `assignee` (any | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/assign \ -H "Authorization: Bearer ast_..." ``` ## Handoff ### POST /sessions/{id}/handoff Hand this session's context to another agent (scope: write) Accepted immediately; the move runs in the background. Poll the `status_url` in the response (also sent as the `Location` header) until `status` is terminal, then talk to `target_session`. `target_agent` is an agent id or a catalog `@handle`; `llm` and `reasoningEffort` choose the new session's model and thinking level. `last_message_id` bounds how much history is carried; omitted = all of it. The `Idempotency-Key` header is required and dedupes retries. Path parameters: - `id` (string, required) — The source session id (sess_…) Body (application/json): - `target_agent` (string, required) — The agent to continue with: an agent id, or "@handle" for an agent from the catalog — max length 256 - `last_message_id` (string) — max length 256 - `llm` (string) — The new session's model; omitted = the target agent's model — max length 256 - `reasoningEffort` (enum | null) — The new session's thinking level; omitted or null = the target agent's — "none" · "off" · "minimal" · "low" · "medium" · "high" · "xhigh" · "max" · "ultra" Responses: - `202` — Accepted — poll status_url - `handoff` (object, required) — `resolved_mode` is how the move was actually made: `native_fork` when source and target share a harness and the pod's own state could be forked, else `summary` — a written handover the target reads as history, never as instructions. `target_session` is null until the move completes. - `id` (string, required) — handoff_… - `source_session` (string | null) - `target_session` (string | null) - `last_message_id` (string | null) - `target_agent` (string | null) - `status` (string, required) — Lowercased HandoffStatus: pending · checkpointing · provisioning · restoring · completed · retry_wait · failed · cancelled. Terminal: completed, failed, cancelled. - `resolved_mode` (enum, required) — "native_fork" · "summary" - `resolution_reason` (string | null) - `hands` (string | null) — `carry` copies the source's workspace files into the new session's sandbox; `attach` keeps the source's sandbox under the new agent — "carry" · "attach" · null - `workspace_carried` (boolean | null) — For `carry`: whether the workspace files were copied. False when the source sandbox was too old to export them; the conversation still moved - `workspace_folder` (string | null) — For a Mac-synced chat its owner continued: where the folder the Mac stored was restored in the new session. Null otherwise - `checkpoint` (object | null) — What was carried across, without its content - `summary` (object | null) — What was carried across, without its content - `error` (object | null) - `status_url` (string, required) — GET it to poll the move - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/handoff \ -H "Authorization: Bearer ast_..." ``` ### GET /session-handoffs/{id} The progress of one handoff (scope: read) Path parameters: - `id` (string, required) — The handoff id (handoff_…), as returned by POST /sessions/{id}/handoff Responses: - `200` — OK - `handoff` (object, required) — `resolved_mode` is how the move was actually made: `native_fork` when source and target share a harness and the pod's own state could be forked, else `summary` — a written handover the target reads as history, never as instructions. `target_session` is null until the move completes. - `id` (string, required) — handoff_… - `source_session` (string | null) - `target_session` (string | null) - `last_message_id` (string | null) - `target_agent` (string | null) - `status` (string, required) — Lowercased HandoffStatus: pending · checkpointing · provisioning · restoring · completed · retry_wait · failed · cancelled. Terminal: completed, failed, cancelled. - `resolved_mode` (enum, required) — "native_fork" · "summary" - `resolution_reason` (string | null) - `hands` (string | null) — `carry` copies the source's workspace files into the new session's sandbox; `attach` keeps the source's sandbox under the new agent — "carry" · "attach" · null - `workspace_carried` (boolean | null) — For `carry`: whether the workspace files were copied. False when the source sandbox was too old to export them; the conversation still moved - `workspace_folder` (string | null) — For a Mac-synced chat its owner continued: where the folder the Mac stored was restored in the new session. Null otherwise - `checkpoint` (object | null) — What was carried across, without its content - `summary` (object | null) — What was carried across, without its content - `error` (object | null) - `status_url` (string, required) — GET it to poll the move - `created_at` (string, required) - `updated_at` (string, required) - `completed_at` (string | null) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/session-handoffs/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ## Session record ### GET /sessions/{id}/threads The threads of this session's task (scope: read) The root session first (relation primary, parent_id null), then every member the caller may see, oldest first: helper (a pod created it), handoff (a handoff branched it), child (a client recorded it). Each thread names its source (cloud, local_mirror, imported, tycoon), its agent's harness and model, its status, and what it has spent. A session nothing created is a task of one and lists itself. See docs/asteroids/architecture/session-record.md. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `threads` (object[]) - `id` (string) - `parent_id` (string) - `root_id` (string) - `relation` (enum) — "primary" · "helper" · "handoff" · "child" - `source` (enum) — "cloud" · "local_mirror" · "imported" · "tycoon" - `agent` (object) - `id` (string) - `harness` (string) - `model` (string) - `status` (string) - `title` (string) - `usage` (object) - `spent_usd_micros` (string) - `tool_calls` (integer) - `environment` (object) — repo, commit, branch, instructions_hash, digest, cwd — as the creating client recorded them in metadata.environment - `repo` (string) - `commit` (string) - `branch` (string) - `instructions_hash` (string) - `digest` (string) - `cwd` (string) - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/threads' \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "threads": [ { "id": "sess_cmtb7q2j", "parent_id": null, "root_id": "sess_cmtb7q2j", "relation": "primary", "source": "cloud", "agent": { "id": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "harness": "claude_code", "model": "claude-opus-5-5" }, "status": "idle", "title": "Fix the billing flow", "usage": { "spent_usd_micros": "184000", "tool_calls": 42 }, "environment": { "repo": "https://github.com/acme/site", "commit": "9f3c2a1", "branch": "main", "instructions_hash": "sha256:1c8e…" }, "created_at": "2026-10-20T10:00:00.000Z" }, { "id": "sess_cmtb8r3k", "parent_id": "sess_cmtb7q2j", "root_id": "sess_cmtb7q2j", "relation": "helper", "source": "cloud", "agent": { "id": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "harness": "codex", "model": "gpt-6" }, "status": "idle", "title": "Review the patch", "usage": { "spent_usd_micros": "21000", "tool_calls": 7 }, "environment": null, "created_at": "2026-10-20T10:04:12.000Z" } ] } ``` ### GET /sessions/{id}/record The session record: decisions and outcomes written about this session (scope: read) The human.decision and outcome.observed events of this session, oldest first — the same rows GET /sessions/{id}/events returns with types[]=human.decision&types[]=outcome.observed, without the rest of the transcript. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `events` (object[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/record' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/record Record a decision about this session's result, or what became of it (scope: write) Writes one mark on the session; never a turn, never a status change. `type` human.decision takes payload {kind: adopt|rerun|revise|discard|vote|did_the_job|approve, target_session_id?, value? (required for did_the_job and approve), reason? (≤2000 chars), surface?}; For a Mac Create PR adoption, optional `payload.github_pr` takes {url, head_sha, export_receipt}; the receipt comes from workspace/patch with record_provenance: true and is verified against the exact GitHub result before storing provenance. `type` outcome.observed takes {kind: pr_merged|deployed|reopened|later_failure|shipped, ref? (≤500 chars), observer?}. An Idempotency-Key header makes a repeat answer 200 with the same event; without one, the same decision by the same caller within a minute lands once. 404 when the session, or a named target_session_id, is not one the caller can see. Nothing writes a decision on a person's behalf: a session without one is undecided. Path parameters: - `id` (string, required) — The session id Body (application/json): - `type` (enum, required) — "human.decision" · "outcome.observed" - `payload` (object, required) Responses: - `200` — Already recorded - `event` (object) - `201` — Created - `event` (object) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/record \ -H "Authorization: Bearer ast_..." ``` Example response (201 Created): ```json { "event": { "id": "record:human.decision:pr-9", "rowId": 57, "sessionId": "sess_cmtb7q2j", "type": "human.decision", "at": "2026-10-20T10:31:02.000Z", "kind": "adopt", "target_session_id": null, "value": null, "reason": "Used this patch as is", "surface": "mac", "actor": { "userId": "cmtb3u5r7k2p9x01s6q8n4w2" } } } ``` ### GET /record/agents Agent performance as the session record tells it (scope: read) For every harness and model the caller's visible sessions ran on, created between `since` and `until` (default the last 30 days): sessions and their sources; decisions people recorded (adopted; votes won, lost and undecided; rerun, revise, discard; did_the_job and approve yes/no); outcomes by kind; sessions nobody decided on; total and median spend in USD micros. A read model rebuilt on each request from the record marks — nothing is stored, nothing is inferred: a session without a human.decision is undecided. See docs/asteroids/architecture/session-record.md. Query parameters: - `since` (string) — Default: 30 days before until - `until` (string) — Default: now Responses: - `200` — OK - `since` (string) - `until` (string) - `agents` (object[]) - `key` (string) - `agent_id` (string) - `harness` (string) - `model` (string) - `sessions` (integer) - `by_source` (object) - `decisions` (object) - `outcomes` (object) - `undecided_sessions` (integer) - `spend` (object) - `total_usd_micros` (string) - `median_usd_micros` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/record/agents' \ -H "Authorization: Bearer ast_..." ``` Example response (200 OK): ```json { "since": "2026-09-20T00:00:00.000Z", "until": "2026-10-20T00:00:00.000Z", "agents": [ { "key": "claude_code:claude-opus-5-5", "agent_id": "agent_cmtb2f0a8m9k7x51q4ez6r0d", "harness": "claude_code", "model": "claude-opus-5-5", "sessions": 42, "by_source": { "cloud": 30, "local_mirror": 12 }, "decisions": { "adopted": 9, "won": 14, "lost": 6, "undecided_votes": 3, "rerun": 4, "revise": 7, "discard": 1, "did_the_job": { "yes": 11, "no": 2 }, "approved": { "yes": 0, "no": 0 } }, "outcomes": { "pr_merged": 5, "reopened": 1 }, "undecided_sessions": 12, "spend": { "total_usd_micros": "18400000", "median_usd_micros": "310000" } } ] } ``` ## Tasks ### GET /tasks List the workspace's tasks (scope: read) Most recent activity first. Helper and handoff sessions are counted inside their task and never listed on their own. Query parameters: - `attention` (string) — Return only tasks in this state Responses: - `200` — OK - `tasks` (object[], required) - `id` (string, required) — sess_… — the root session's id - `title` (string | null, required) - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `open_request_count` (integer, required) — Open requests addressed to the caller - `session_count` (integer, required) — The root session plus its helper and handoff sessions - `sessions` (object[], required) — Every session in the task, root first, each with its own share of the state: its own open requests, its own run and its own latest result, measured against the task's seen mark. - `id` (string, required) — sess_… - `role` (enum, required) — "primary" · "helper" · "handoff" - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `agent_id` (string | null, required) — The root session's agent (agent_…) - `agent_type` (string, required) - `llm` (string, required) - `archived` (boolean, required) - `last_activity_at` (string, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/tasks' \ -H "Authorization: Bearer ast_..." ``` ### GET /tasks/{id} One task with its sessions, open requests and cost (scope: read) By default the id must name a task root. With by=session, resolves an accessible helper or handoff session to its containing task; both the member and root must be accessible. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) Query parameters: - `by` (string) — Resolve this session to its existing task root. Responses: - `200` — OK - `task` (object, required) - `id` (string, required) — sess_… — the root session's id - `title` (string | null, required) - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `open_request_count` (integer, required) — Open requests addressed to the caller - `session_count` (integer, required) — The root session plus its helper and handoff sessions - `sessions` (object[], required) — The root first, then the others in the order they joined - `id` (string, required) — sess_… - `role` (enum, required) — "primary" · "helper" · "handoff" - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `title` (string | null, required) - `status` (enum, required) — "provisioning" · "idle" · "running" · "terminated" - `agent_id` (string | null, required) - `agent_type` (string, required) - `llm` (string, required) - `consumed_cost` (object, required) — A US-dollar amount. `amount` is whole US cents as an integer decimal string — "125" is $1.25 — rounded half-up to the cent. - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `model_paid_by` (object) — Who pays for the model in this session: the wallet, or the caller's own account at a vendor. Runtime is charged to the wallet either way. Present where external accounts are turned on. - `source` (enum, required) — "wallet" · "account" - `vendor` (string) - `name` (string) - `created_at` (string, required) - `agent_id` (string | null, required) — The root session's agent (agent_…) - `agent_type` (string, required) - `llm` (string, required) - `archived` (boolean, required) - `last_activity_at` (string, required) - `created_at` (string, required) - `consumed_cost` (object, required) — Billed cost summed over every session in the task, a paid agent's flat fee included - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `completion_answer` (object | null, required) — What the caller said about whether this task did the job; null until they answer. Other people's answers are not shown. - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `agent_fees` (object[]) — The flat fees of paid agents in this task. Present only when the task has one. - `session_id` (string, required) - `handle` (string, required) - `fee` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `status` (enum, required) — `held`: set aside from the caller's credit when the task started. `captured`: taken, on the task's first result. `released`: given back because the task was archived or deleted without a result. `disputed`, `refunded`: see the dispute route. — "awaiting_confirmation" · "declined" · "held" · "captured" · "disputed" · "released" · "refunded" - `dispute_until` (string | null, required) — Until when the caller may say the task was not completed; null unless the fee is captured and undisputed - `dispute` (object | null, required) — What the caller said and how it was ruled; null when the fee was never disputed. - `requests` (object[], required) — Open requests addressed to the caller, oldest first. Answer one by sending its reply to `session_id` as a message, exactly as a question is answered in that session. - `id` (string, required) — The request id the agent or the platform assigned - `session_id` (string, required) — The session that is waiting (sess_…) - `message_id` (string, required) - `kind` (enum, required) — `question`: an agent asked. `approval`: a tool needs the caller's go-ahead before it runs (options `allow`, `deny`), or a workspace rule asks for it before the task itself starts (options `confirm`, `decline`; nothing has run, and declining archives the task), or a skill or lesson change suggested after the task finished awaits the workspace's owner or an admin (options `approve`, which writes it for the whole workspace, and `decline`; nothing is sent to the agent). `connect_app`: the agent needs an app that is not connected; it resolves by itself when the caller connects the app, or by answering `skip`. `connect_account`: the task cannot run without the caller's own account at a vendor, or that account has no usage left; what the caller sent is kept and nothing has run. It resolves by itself when the account is connected (`POST /external_accounts`), or by answering `use_wallet` (offered only when the account is exhausted or expired and the task can run on a platform model) or `wait`. `spend`: an agent whose maker asks a flat fee is about to be used for the first time at that price, either as this task's own agent (nothing has run, and nothing runs until it is answered) or as a helper an agent in the task wants to hire. Answer `confirm` or `decline`; a confirmation is remembered for that agent at that price. `browser_handoff`: the agent stopped in its own browser at something only the caller can do (a CAPTCHA, a two-step code, a sign-in or consent screen); the caller takes over the browser (`GET`/`POST /sessions/{id}/browser-handoff`), then answers `done` to hand it back or `skip` to tell the agent it could not be done. — "question" · "approval" · "connect_app" · "connect_account" · "spend" · "browser_handoff" - `subject` (string | null, required) — For `connect_app`, the app's slug; for `connect_account`, the vendor; for `spend`, the agent's handle; for `browser_handoff`, the site the browser is on - `question` (string, required) - `options` (object[], required) - `id` (string, required) - `label` (string, required) - `description` (string) - `allow_free_text` (boolean, required) - `required_user_id` (string | null, required) — Set when only one person may answer - `due_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### POST /tasks/{id}/seen Mark the task seen by the caller (scope: write) Records that the caller has looked at the task now, so a result that already arrived stops reading as `done` for them. Optional session_ids scopes the read to those task members; other members keep their unread results. Other people's view is unchanged. Answers 503 `not_available` until the platform's task tables are in place. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) Body (application/json): - `session_ids` (string[]) — Only these task members are marked seen; omitted marks all current members. — max length 256 Responses: - `200` — OK - `task` (object, required) - `id` (string, required) — sess_… — the root session's id - `title` (string | null, required) - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `open_request_count` (integer, required) — Open requests addressed to the caller - `session_count` (integer, required) — The root session plus its helper and handoff sessions - `sessions` (object[], required) — The root first, then the others in the order they joined - `id` (string, required) — sess_… - `role` (enum, required) — "primary" · "helper" · "handoff" - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `title` (string | null, required) - `status` (enum, required) — "provisioning" · "idle" · "running" · "terminated" - `agent_id` (string | null, required) - `agent_type` (string, required) - `llm` (string, required) - `consumed_cost` (object, required) — A US-dollar amount. `amount` is whole US cents as an integer decimal string — "125" is $1.25 — rounded half-up to the cent. - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `model_paid_by` (object) — Who pays for the model in this session: the wallet, or the caller's own account at a vendor. Runtime is charged to the wallet either way. Present where external accounts are turned on. - `source` (enum, required) — "wallet" · "account" - `vendor` (string) - `name` (string) - `created_at` (string, required) - `agent_id` (string | null, required) — The root session's agent (agent_…) - `agent_type` (string, required) - `llm` (string, required) - `archived` (boolean, required) - `last_activity_at` (string, required) - `created_at` (string, required) - `consumed_cost` (object, required) — Billed cost summed over every session in the task, a paid agent's flat fee included - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `completion_answer` (object | null, required) — What the caller said about whether this task did the job; null until they answer. Other people's answers are not shown. - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `agent_fees` (object[]) — The flat fees of paid agents in this task. Present only when the task has one. - `session_id` (string, required) - `handle` (string, required) - `fee` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `status` (enum, required) — `held`: set aside from the caller's credit when the task started. `captured`: taken, on the task's first result. `released`: given back because the task was archived or deleted without a result. `disputed`, `refunded`: see the dispute route. — "awaiting_confirmation" · "declined" · "held" · "captured" · "disputed" · "released" · "refunded" - `dispute_until` (string | null, required) — Until when the caller may say the task was not completed; null unless the fee is captured and undisputed - `dispute` (object | null, required) — What the caller said and how it was ruled; null when the fee was never disputed. - `requests` (object[], required) — Open requests addressed to the caller, oldest first. Answer one by sending its reply to `session_id` as a message, exactly as a question is answered in that session. - `id` (string, required) — The request id the agent or the platform assigned - `session_id` (string, required) — The session that is waiting (sess_…) - `message_id` (string, required) - `kind` (enum, required) — `question`: an agent asked. `approval`: a tool needs the caller's go-ahead before it runs (options `allow`, `deny`), or a workspace rule asks for it before the task itself starts (options `confirm`, `decline`; nothing has run, and declining archives the task), or a skill or lesson change suggested after the task finished awaits the workspace's owner or an admin (options `approve`, which writes it for the whole workspace, and `decline`; nothing is sent to the agent). `connect_app`: the agent needs an app that is not connected; it resolves by itself when the caller connects the app, or by answering `skip`. `connect_account`: the task cannot run without the caller's own account at a vendor, or that account has no usage left; what the caller sent is kept and nothing has run. It resolves by itself when the account is connected (`POST /external_accounts`), or by answering `use_wallet` (offered only when the account is exhausted or expired and the task can run on a platform model) or `wait`. `spend`: an agent whose maker asks a flat fee is about to be used for the first time at that price, either as this task's own agent (nothing has run, and nothing runs until it is answered) or as a helper an agent in the task wants to hire. Answer `confirm` or `decline`; a confirmation is remembered for that agent at that price. `browser_handoff`: the agent stopped in its own browser at something only the caller can do (a CAPTCHA, a two-step code, a sign-in or consent screen); the caller takes over the browser (`GET`/`POST /sessions/{id}/browser-handoff`), then answers `done` to hand it back or `skip` to tell the agent it could not be done. — "question" · "approval" · "connect_app" · "connect_account" · "spend" · "browser_handoff" - `subject` (string | null, required) — For `connect_app`, the app's slug; for `connect_account`, the vendor; for `spend`, the agent's handle; for `browser_handoff`, the site the browser is on - `question` (string, required) - `options` (object[], required) - `id` (string, required) - `label` (string, required) - `description` (string) - `allow_free_text` (boolean, required) - `required_user_id` (string | null, required) — Set when only one person may answer - `due_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/seen \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"session_ids":["sess_cmtb7q2j"]}' ``` ### PUT /tasks/{id}/completion_answer Say whether the task did the job (scope: write) The caller's own answer for a task that has a result. Each person has one answer per task; sending it again replaces the earlier one. It counts toward `stats.completed_yes` or `stats.completed_no` of the catalog listing the task's agent stands for. 409 `no_result` while no session of the task has replied. 404 for a task the caller cannot see. Answers 503 `not_migrated` until the platform's answer table is in place. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) Body (application/json): - `did_the_job` (boolean, required) Responses: - `200` — OK - `task` (object, required) - `id` (string, required) — sess_… — the root session's id - `title` (string | null, required) - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `open_request_count` (integer, required) — Open requests addressed to the caller - `session_count` (integer, required) — The root session plus its helper and handoff sessions - `sessions` (object[], required) — The root first, then the others in the order they joined - `id` (string, required) — sess_… - `role` (enum, required) — "primary" · "helper" · "handoff" - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `title` (string | null, required) - `status` (enum, required) — "provisioning" · "idle" · "running" · "terminated" - `agent_id` (string | null, required) - `agent_type` (string, required) - `llm` (string, required) - `consumed_cost` (object, required) — A US-dollar amount. `amount` is whole US cents as an integer decimal string — "125" is $1.25 — rounded half-up to the cent. - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `model_paid_by` (object) — Who pays for the model in this session: the wallet, or the caller's own account at a vendor. Runtime is charged to the wallet either way. Present where external accounts are turned on. - `source` (enum, required) — "wallet" · "account" - `vendor` (string) - `name` (string) - `created_at` (string, required) - `agent_id` (string | null, required) — The root session's agent (agent_…) - `agent_type` (string, required) - `llm` (string, required) - `archived` (boolean, required) - `last_activity_at` (string, required) - `created_at` (string, required) - `consumed_cost` (object, required) — Billed cost summed over every session in the task, a paid agent's flat fee included - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `completion_answer` (object | null, required) — What the caller said about whether this task did the job; null until they answer. Other people's answers are not shown. - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `agent_fees` (object[]) — The flat fees of paid agents in this task. Present only when the task has one. - `session_id` (string, required) - `handle` (string, required) - `fee` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `status` (enum, required) — `held`: set aside from the caller's credit when the task started. `captured`: taken, on the task's first result. `released`: given back because the task was archived or deleted without a result. `disputed`, `refunded`: see the dispute route. — "awaiting_confirmation" · "declined" · "held" · "captured" · "disputed" · "released" · "refunded" - `dispute_until` (string | null, required) — Until when the caller may say the task was not completed; null unless the fee is captured and undisputed - `dispute` (object | null, required) — What the caller said and how it was ruled; null when the fee was never disputed. - `requests` (object[], required) — Open requests addressed to the caller, oldest first. Answer one by sending its reply to `session_id` as a message, exactly as a question is answered in that session. - `id` (string, required) — The request id the agent or the platform assigned - `session_id` (string, required) — The session that is waiting (sess_…) - `message_id` (string, required) - `kind` (enum, required) — `question`: an agent asked. `approval`: a tool needs the caller's go-ahead before it runs (options `allow`, `deny`), or a workspace rule asks for it before the task itself starts (options `confirm`, `decline`; nothing has run, and declining archives the task), or a skill or lesson change suggested after the task finished awaits the workspace's owner or an admin (options `approve`, which writes it for the whole workspace, and `decline`; nothing is sent to the agent). `connect_app`: the agent needs an app that is not connected; it resolves by itself when the caller connects the app, or by answering `skip`. `connect_account`: the task cannot run without the caller's own account at a vendor, or that account has no usage left; what the caller sent is kept and nothing has run. It resolves by itself when the account is connected (`POST /external_accounts`), or by answering `use_wallet` (offered only when the account is exhausted or expired and the task can run on a platform model) or `wait`. `spend`: an agent whose maker asks a flat fee is about to be used for the first time at that price, either as this task's own agent (nothing has run, and nothing runs until it is answered) or as a helper an agent in the task wants to hire. Answer `confirm` or `decline`; a confirmation is remembered for that agent at that price. `browser_handoff`: the agent stopped in its own browser at something only the caller can do (a CAPTCHA, a two-step code, a sign-in or consent screen); the caller takes over the browser (`GET`/`POST /sessions/{id}/browser-handoff`), then answers `done` to hand it back or `skip` to tell the agent it could not be done. — "question" · "approval" · "connect_app" · "connect_account" · "spend" · "browser_handoff" - `subject` (string | null, required) — For `connect_app`, the app's slug; for `connect_account`, the vendor; for `spend`, the agent's handle; for `browser_handoff`, the site the browser is on - `question` (string, required) - `options` (object[], required) - `id` (string, required) - `label` (string, required) - `description` (string) - `allow_free_text` (boolean, required) - `required_user_id` (string | null, required) — Set when only one person may answer - `due_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/completion_answer \ -H "Authorization: Bearer ast_..." ``` ### POST /tasks/{id}/requests/{request_id}/answer Answer a request the task is waiting on (scope: write) Sends the caller's answer to the session that asked, as its next turn, in the same form a click in the app produces. Pass `option_id` for one of the request's options, or `text` where the request accepts free text. A `spend` request is applied before anything is sent: confirming remembers the price and, when the task itself was waiting to start, holds the fee and starts it with what it was given (402 `insufficient_credits` when the caller's credit does not cover the fee; the request stays open). Declining such a start closes the request and the task never runs. A `spend` request that asks to raise a member's monthly limit in a workspace is answered by an owner or admin of that workspace with `option_id` `raise` or `decline` (403 `request_not_yours` for anyone else, the member included): raising sets the limit the request offered and sends on what the member's waiting tasks were given. An optional `note` is sent to the agent after the answer, in the same turn, and is not part of the answer: a surface other than the app uses it to say where the answer came from and how to reply there. 404 when the task has no open request with that id, 403 `request_not_yours` when it is addressed to someone else, 400 when the answer is not one the request accepts, 409 `session_busy` while the turn that asked is still running (retry once the session is idle). Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) - `request_id` (string, required) — The request's `id` from the task's `requests` Body (application/json): - option 1 (object) - `option_id` (string, required) - `note` (string) — max length 4000 - option 2 (object) - `text` (string, required) — max length 20000 - `note` (string) — max length 4000 Responses: - `200` — OK - `session_id` (string, required) — The session that asked - `message_id` (string | null, required) — The answer's message; read the session's events from it to follow the turn. Null when a `spend` request was declined, or confirmed for a task that was given nothing to start with. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/requests/ask-7f3c2a/answer \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"option_id":"approve"}' ``` ### POST /tasks/{id}/agent-fee/dispute Say a paid agent did not complete the task (scope: write) For the person who hired, within 72 hours of the task's first result. The fee and the seller's earning then wait for a reviewer, who either refunds the fee to the caller's credit or lets the charge stand. Usage is not refunded. 404 when the task has no agent fee of the caller's, 409 `not_disputable` outside the 72 hours or once it has been disputed, 400 when the task holds more than one fee and `session_id` does not name one. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) Body (application/json): - `reason` (string, required) — max length 1000 - `session_id` (string) Responses: - `200` — OK - `task` (object, required) - `id` (string, required) — sess_… — the root session's id - `title` (string | null, required) - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `open_request_count` (integer, required) — Open requests addressed to the caller - `session_count` (integer, required) — The root session plus its helper and handoff sessions - `sessions` (object[], required) — The root first, then the others in the order they joined - `id` (string, required) — sess_… - `role` (enum, required) — "primary" · "helper" · "handoff" - `attention` (enum, required) — What the caller should do about the task. `needs_you`: a request addressed to the caller is open. `working`: a session in the task is running or starting, or the task is waiting on somebody else. `done`: a result arrived since the caller last marked the task seen. `idle`: nothing new. — "needs_you" · "working" · "done" · "idle" - `status_line` (string | null, required) — One line, at most 140 characters, on where it stands: the oldest question waiting on the caller, else the start of the latest thing an agent said. Null when no agent has said anything. - `title` (string | null, required) - `status` (enum, required) — "provisioning" · "idle" · "running" · "terminated" - `agent_id` (string | null, required) - `agent_type` (string, required) - `llm` (string, required) - `consumed_cost` (object, required) — A US-dollar amount. `amount` is whole US cents as an integer decimal string — "125" is $1.25 — rounded half-up to the cent. - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `model_paid_by` (object) — Who pays for the model in this session: the wallet, or the caller's own account at a vendor. Runtime is charged to the wallet either way. Present where external accounts are turned on. - `source` (enum, required) — "wallet" · "account" - `vendor` (string) - `name` (string) - `created_at` (string, required) - `agent_id` (string | null, required) — The root session's agent (agent_…) - `agent_type` (string, required) - `llm` (string, required) - `archived` (boolean, required) - `last_activity_at` (string, required) - `created_at` (string, required) - `consumed_cost` (object, required) — Billed cost summed over every session in the task, a paid agent's flat fee included - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `unit` (enum) — The unit of `amount`; present on every amount the API returns — "cents" - `amount_usd` (string) — The exact amount in US dollars, up to six decimals — "0.0038" for spend that rounds to 0 cents. Returned, never accepted. - `completion_answer` (object | null, required) — What the caller said about whether this task did the job; null until they answer. Other people's answers are not shown. - `payer` (object) — Who pays for it, decided once when it was created and never changed. Present only when a payer was recorded, which happens in a team workspace where workspace wallets are on; absent means its owner's own balance pays. - `type` (enum, required) — `workspace`: the team workspace's own wallet. `personal`: the balance of the person who started it. — "workspace" · "personal" - `workspace` (object) — The paying workspace; only with type workspace - `slug` (string) - `name` (string) - `agent_fees` (object[]) — The flat fees of paid agents in this task. Present only when the task has one. - `session_id` (string, required) - `handle` (string, required) - `fee` (object, required) - `amount` (string, required) — Whole US cents - `currency` (enum, required) — "USD" - `status` (enum, required) — `held`: set aside from the caller's credit when the task started. `captured`: taken, on the task's first result. `released`: given back because the task was archived or deleted without a result. `disputed`, `refunded`: see the dispute route. — "awaiting_confirmation" · "declined" · "held" · "captured" · "disputed" · "released" · "refunded" - `dispute_until` (string | null, required) — Until when the caller may say the task was not completed; null unless the fee is captured and undisputed - `dispute` (object | null, required) — What the caller said and how it was ruled; null when the fee was never disputed. - `requests` (object[], required) — Open requests addressed to the caller, oldest first. Answer one by sending its reply to `session_id` as a message, exactly as a question is answered in that session. - `id` (string, required) — The request id the agent or the platform assigned - `session_id` (string, required) — The session that is waiting (sess_…) - `message_id` (string, required) - `kind` (enum, required) — `question`: an agent asked. `approval`: a tool needs the caller's go-ahead before it runs (options `allow`, `deny`), or a workspace rule asks for it before the task itself starts (options `confirm`, `decline`; nothing has run, and declining archives the task), or a skill or lesson change suggested after the task finished awaits the workspace's owner or an admin (options `approve`, which writes it for the whole workspace, and `decline`; nothing is sent to the agent). `connect_app`: the agent needs an app that is not connected; it resolves by itself when the caller connects the app, or by answering `skip`. `connect_account`: the task cannot run without the caller's own account at a vendor, or that account has no usage left; what the caller sent is kept and nothing has run. It resolves by itself when the account is connected (`POST /external_accounts`), or by answering `use_wallet` (offered only when the account is exhausted or expired and the task can run on a platform model) or `wait`. `spend`: an agent whose maker asks a flat fee is about to be used for the first time at that price, either as this task's own agent (nothing has run, and nothing runs until it is answered) or as a helper an agent in the task wants to hire. Answer `confirm` or `decline`; a confirmation is remembered for that agent at that price. `browser_handoff`: the agent stopped in its own browser at something only the caller can do (a CAPTCHA, a two-step code, a sign-in or consent screen); the caller takes over the browser (`GET`/`POST /sessions/{id}/browser-handoff`), then answers `done` to hand it back or `skip` to tell the agent it could not be done. — "question" · "approval" · "connect_app" · "connect_account" · "spend" · "browser_handoff" - `subject` (string | null, required) — For `connect_app`, the app's slug; for `connect_account`, the vendor; for `spend`, the agent's handle; for `browser_handoff`, the site the browser is on - `question` (string, required) - `options` (object[], required) - `id` (string, required) - `label` (string, required) - `description` (string) - `allow_free_text` (boolean, required) - `required_user_id` (string | null, required) — Set when only one person may answer - `due_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/agent-fee/dispute \ -H "Authorization: Bearer ast_..." ``` ### GET /tasks/{id}/files List the files in the task's file space (scope: read) The folder every session of the task has at `/workspace/task/`: a handoff session and a helper see the same files as the session that wrote them. Current files only, by path. Answers 503 `not_migrated` until the platform's task file tables are in place. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) Responses: - `200` — OK - `files` (object[], required) - `path` (string, required) - `size_bytes` (integer, required) - `sha256` (string, required) - `updated_at` (string, required) - `updated_by_session` (string | null, required) — The session that last wrote it; null when a person or developer did - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/files' \ -H "Authorization: Bearer ast_..." ``` ### GET /tasks/{id}/export Download the task's files as one archive (scope: read) A zip archive of the current files in the task's file space, each at its own path. 413 `export_too_large` when they are more than 64 MiB together; download them one at a time then. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) Responses: - `200` — The archive - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/export' \ -H "Authorization: Bearer ast_..." ``` ### GET /tasks/{id}/files/{path} Read one task file, or its history (scope: read) Returns the file's bytes. With `versions=true` returns instead every recorded write of that path, newest first, including writes to a path that has since been deleted. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) - `path` (string, required) — The file's path inside the task's file space, such as report.md or data/rows.csv Query parameters: - `versions` (string) Responses: - `200` — The bytes, or the version list - `versions` (object[], required) - `id` (string, required) - `sha256` (string, required) - `size_bytes` (integer, required) - `session` (string | null, required) - `conflict` (boolean, required) — The writer had not seen what was current when it wrote - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/files/report.md' \ -H "Authorization: Bearer ast_..." ``` ### PUT /tasks/{id}/files/{path} Write one task file (scope: write) The body is the file's bytes (16 MiB at most; 413 `file_too_large`). The write becomes the current file. `base_sha256` names what the caller last saw at the path (empty for nothing); when something else is current the default write is still made, both versions are kept, and the response says `conflict: true`. With `require_base_match=true`, a base is required and a mismatch instead returns 409 `revision_conflict` without updating the file. A task holds at most 2000 files and 256 MiB (413 `task_files_full`). Sessions of the task pick the file up at the start of their next turn. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) - `path` (string, required) — The file's path inside the task's file space, such as report.md or data/rows.csv Query parameters: - `base_sha256` (string) - `require_base_match` (boolean) Responses: - `200` — OK - `file` (object, required) - `path` (string, required) - `size_bytes` (integer, required) - `sha256` (string, required) - `updated_at` (string, required) - `updated_by_session` (string | null, required) — The session that last wrote it; null when a person or developer did - `conflict` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/files/report.md \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: text/markdown" \ --data-binary @report.md ``` ### DELETE /tasks/{id}/files/{path} Remove one task file (scope: write) Removes the path from the current files. Its history stays readable with `versions=true`. Path parameters: - `id` (string, required) — The task id: the id of its root session (sess_…) - `path` (string, required) — The file's path inside the task's file space, such as report.md or data/rows.csv Responses: - `200` — OK - `ok` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/files/report.md \ -H "Authorization: Bearer ast_..." ``` ### POST /tasks/{id}/callback Report on a task from the server its agent runs on (scope: task pass) Authorized by the pass handed to that server with the turn, sent as `Authorization: Bearer`, and by nothing else: any other credential is refused with 403. `message` shows progress and keeps the turn open. `result` is the answer; `request` asks the person a question, which they answer like any other request on the task; `error` says the work could not be done. Those three end the turn and the pass, so write the task's files first. 409 `turn_ended` once the turn is over; 401 for a pass that has ended. Path parameters: - `id` (string, required) — The task's id. Body (application/json): - type: "message" (object) — Something to show while the work goes on. The turn stays open. - `type` ("message", required) - `text` (string, required) — max length 200000 - type: "result" (object) — The answer. Ends the turn and the pass. - `type` ("result", required) - `text` (string, required) — max length 200000 - type: "request" (object) — A question for the person. Ends the turn and the pass; the answer arrives as the task's next turn, with a new pass. - `type` ("request", required) - `question` (string, required) — max length 2000 - `options` (object[]) - `id` (string, required) — max length 200 - `label` (string, required) — max length 200 - `description` (string) — max length 500 - `allow_free_text` (boolean) — Whether the person may type an answer. Defaults to true; must be true when there are no options. - type: "error" (object) — The work could not be done. Ends the turn and the pass; the person reads the message. - `type` ("error", required) - `message` (string, required) — max length 500 Responses: - `200` — Recorded - `ok` (boolean, required) - `request_id` (string) — The id of the request that was raised, for `request`. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/tasks/sess_cmtb7q2j/callback \ -H "Authorization: Bearer ast_..." ``` ## Task traces ### POST /traces Start a trace (scope: write) A trace correlates one task across local agents and platform sessions. It holds metadata, plus redacted content where a grant allows it, and is deleted 30 days after creation with everything admitted to it. It is never exported. Returns 404 when the TASK_TRACES switch is off for the workspace and 503 not_migrated before the tables exist. Body (application/json): - `name` (string) — max length 200 Responses: - `201` — Created - `trace` (object, required) - `id` (string, required) — trc_… — the public id - `name` (string | null, required) - `created_at` (string, required) - `expires_at` (string, required) — 30 days after creation; the trace and its events are deleted then - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/traces \ -H "Authorization: Bearer ast_..." ``` ### GET /traces/{id} The trace bundle (scope: read) Spans, the sessions and tasks they reach, cost counted once per session, and gaps. A session joins by an event naming it or by its own `metadata.trace_id`. Returns 404 when the TASK_TRACES switch is off for the workspace and 503 not_migrated before the tables exist. Path parameters: - `id` (string, required) — The trace id (trc_…) Responses: - `200` — OK - `trace` (object, required) - `id` (string, required) — trc_… — the public id - `name` (string | null, required) - `created_at` (string, required) - `expires_at` (string, required) — 30 days after creation; the trace and its events are deleted then - `coverage` (enum, required) — metadata_only when no event carries content; redacted when any kept content had a value replaced or was dropped — "metadata_only" · "partial" · "redacted" - `spans` (object[], required) — What sources reported, oldest first. `edge` is root, resolved, pending (its parent may still arrive) or unresolved (the parent never arrived within 10 minutes). A span its source left open that names a session ends with that session's turn (`outcome_source: platform`): failed on `session.error`, completed on `session.status_idle`, cancelled when the session was archived or deleted first. Events with content carry `content.text`, up to 4 MiB per bundle. - `sessions` (object[], required) — Platform sessions the trace reaches, with their tasks' members; each listed once - `task_ids` (string[], required) - `consumed_cost` (any, required) — Summed over the listed sessions, each counted once; external spend is not known and not included - `gaps` (object[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/traces/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### POST /traces/{id}/grants Mint a grant for one source (scope: write) The token (tgr_…) is shown once. It may only add events to this trace, as this source, in the content scopes it names, until it expires (default 24 hours, at most 7 days, never past the trace). Returns 404 when the TASK_TRACES switch is off for the workspace and 503 not_migrated before the tables exist. Path parameters: - `id` (string, required) — The trace id (trc_…) Body (application/json): - `source` (string, required) — pattern ^[A-Za-z0-9._:@-]{1,128}$ - `ttl_seconds` (integer) - `content_scopes` (enum[]) — Content the source may send beside metadata; omitted admits metadata only — "prompts" · "tool_output" · "files" Responses: - `201` — Created - `grant` (object, required) - `id` (string, required) — tgrant_… - `trace_id` (string, required) - `source` (string, required) — The one source this grant may report as - `content_scopes` (enum[]) — The content this grant admits beside metadata; empty admits metadata only — "prompts" · "tool_output" · "files" - `expires_at` (string, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `token` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/traces/sess_cmtb7q2j/grants \ -H "Authorization: Bearer ast_..." ``` ### DELETE /traces/{id}/grants/{grant_id} Revoke a grant (scope: write) Its source can add nothing more; what was admitted stays. Returns 404 when the TASK_TRACES switch is off for the workspace and 503 not_migrated before the tables exist. Path parameters: - `id` (string, required) — The trace id (trc_…) - `grant_id` (string, required) — The grant id (tgrant_…) Responses: - `200` — OK - `grant` (object, required) - `id` (string, required) — tgrant_… - `trace_id` (string, required) - `source` (string, required) — The one source this grant may report as - `content_scopes` (enum[]) — The content this grant admits beside metadata; empty admits metadata only — "prompts" · "tool_output" · "files" - `expires_at` (string, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/traces/sess_cmtb7q2j/grants/grant_id \ -H "Authorization: Bearer ast_..." ``` ### POST /traces/{id}/events Admit events (scope: trace grant) Authenticated by a trace grant token (Bearer tgr_…), not an API key. At most 256 events, 64 KiB each, 4 MiB per request. Each event is judged on its own: the same event id and content returns its first receipt; different content, a session outside the workspace, another source's span, a changed parent, a parent cycle or content outside the grant's scopes is refused. Returns 404 when the TASK_TRACES switch is off for the workspace and 503 not_migrated before the tables exist. Path parameters: - `id` (string, required) — The trace id (trc_…) Body (application/json): - `events` (object[], required) - `event_id` (string, required) — Unique per source; resending the same event returns its first receipt — pattern ^[A-Za-z0-9._:@-]{1,128}$ - `sequence` (integer, required) — Order within the source - `span_id` (string, required) — pattern ^[A-Za-z0-9._:@-]{1,128}$ - `parent_span_id` (string | null) — Set once, by the span's first event; a different parent later is refused — pattern ^[A-Za-z0-9._:@-]{1,128}$ - `kind` (enum, required) — "start" · "end" · "event" - `name` (string) — max length 200 - `outcome` (enum) — "running" · "completed" · "failed" · "cancelled" · "unknown" - `coverage` (enum) — "complete" · "partial" · "redacted" · "metadata_only" - `session_id` (string) — A session of the trace's workspace (sess_…) - `task_id` (string) — A task of the trace's workspace (its root session id) - `source_time` (string) — The source's clock; informative only - `attributes` (map) - `content` (object) — Only in a scope the grant names. Redacted again on arrival: a credential-shaped value is replaced and the event reads `redacted`; text that cannot be redacted is not kept and the event reads `metadata_only`. - `scope` (enum, required) — "prompts" · "tool_output" · "files" - `text` (string, required) — At most 48 KiB (UTF-8) - `path` (string) — For files, and only for files — max length 1024 - `truncated` (boolean) — The source cut the text to fit - `redactions` (integer) — Values the source already replaced Responses: - `200` — OK - `trace_id` (string, required) - `source` (string, required) - `results` (object[], required) - `event_id` (string, required) - `status` (enum, required) — "admitted" · "duplicate" · "refused" - `receipt` (string) - `received_at` (string) - `code` (enum) — "event_conflict" · "foreign_reference" · "span_owned" · "parent_changed" · "parent_cycle" · "content_not_granted" - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/traces/sess_cmtb7q2j/events \ -H "Authorization: Bearer ast_..." ``` ## Trace library ### GET /trace-library List the workspace's trace library (scope: read) Every uploaded local session in the current workspace, most recently updated first. Workspace keys are refused: uploads and reads belong to a person. Returns 503 not_migrated before the tables exist. Query parameters: - `limit` (integer) - `cursor` (string) - `uploader` (string) — Only this member's uploads - `harness` (string) - `project` (string) Responses: - `200` — OK - `traces` (object[], required) - `id` (string, required) — ltr_… - `harness` (string, required) - `session_id` (string, required) - `title` (string | null) - `project` (string | null) — The repository's folder name - `cwd` (string | null) — Where the session ran, home written as ~ - `git_branch` (string | null) - `git_commit` (string | null) - `git_remote` (string | null) — The repository URL, without credentials - `model` (string | null) — The model that answered most - `models` (string[]) — Every model that answered, most-used first; empty when the upload named none - `harness_version` (string | null) - `client` (string | null) — The front end that ran the harness, such as a Claude Code entrypoint or a Codex originator - `model_provider` (string | null) - `effort` (string | null) - `usage` (object | null) — Token totals the source reported, by kind - `uploader_id` (string, required) - `device` (string | null) - `started_at` (string | null) - `ended_at` (string | null) - `user_turns` (integer) - `tool_calls` (integer) - `messages` (integer, required) — Stored messages, after redaction - `coverage` (enum, required) — "complete" · "partial" · "redacted" - `version` (integer, required) — Raised each time a changed upload replaces it - `updated_at` (string, required) - `environment` (object | null) — The working folder the session ran in, uploaded with it as one of the uploader's files - `next_cursor` (string | null, required) - `total` (integer, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/trace-library' \ -H "Authorization: Bearer ast_..." ``` ### POST /trace-library/check Ask which sessions to upload (scope: write) For each named session: upload (new or changed), unchanged (same sha256 already stored) or owned (another member uploaded it). Workspace keys are refused: uploads and reads belong to a person. Returns 503 not_migrated before the tables exist. Body (application/json): - `sessions` (object[], required) - `harness` (string, required) - `session_id` (string, required) - `sha256` (string, required) — pattern ^[0-9a-f]{64}$ Responses: - `200` — OK - `sessions` (object[]) - `harness` (string) - `session_id` (string) - `status` (enum) — "upload" · "unchanged" · "owned" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/trace-library/check \ -H "Authorization: Bearer ast_..." ``` ### PUT /trace-library/{harness}/{session_id} Upload one session (scope: write) Stores the session's messages, redacted again on arrival and cut to 8 KiB each, replacing an earlier version. The same sha256 is a no-op (unchanged). A session another member uploaded answers 409 trace_owned. The body is at most 8 MiB. Workspace keys are refused: uploads and reads belong to a person. Returns 503 not_migrated before the tables exist. Path parameters: - `harness` (string, required) — claude_code, codex, or another harness name - `session_id` (string, required) — The harness's own session id Body (application/json): - `sha256` (string, required) — The device's digest of the messages; repeats of it are not stored again — pattern ^[0-9a-f]{64}$ - `title` (string) — max length 300 - `cwd` (string) — max length 1024 - `project` (string) — max length 200 - `git_branch` (string) — max length 300 - `git_commit` (string) — max length 80 - `git_remote` (string) — The repository URL, without credentials — max length 1024 - `model` (string) — The model that answered most — max length 200 - `models` (string[]) — Every model that answered, most-used first — max length 200 - `harness_version` (string) — max length 80 - `client` (string) — max length 200 - `model_provider` (string) — max length 100 - `effort` (string) — max length 100 - `usage` (object) - `input_tokens` (integer) - `output_tokens` (integer) - `cache_read_tokens` (integer) - `cache_write_tokens` (integer) - `reasoning_tokens` (integer) - `device` (string) — max length 200 - `started_at` (string) - `ended_at` (string) - `user_turns` (integer) - `tool_calls` (integer) - `partial` (boolean) — The device left messages out to fit - `environment` (object) — The working folder the session ran in, already uploaded as one of your files in this workspace; 400 invalid_environment for a file you cannot see. Fold its file_id into sha256 so linking it later is a changed upload. - `file_id` (string, required) - `name` (string, required) — max length 80 - `bytes` (integer, required) - `files` (integer, required) - `sha256` (string, required) — pattern ^[0-9a-f]{64}$ - `messages` (object[], required) - `role` (enum, required) — "user" · "assistant" · "tool" - `at` (string) - `text` (string, required) — max length 65536 Responses: - `200` — Updated or unchanged - `status` (enum) — "updated" · "unchanged" - `trace` (object) - `id` (string, required) — ltr_… - `harness` (string, required) - `session_id` (string, required) - `title` (string | null) - `project` (string | null) — The repository's folder name - `cwd` (string | null) — Where the session ran, home written as ~ - `git_branch` (string | null) - `git_commit` (string | null) - `git_remote` (string | null) — The repository URL, without credentials - `model` (string | null) — The model that answered most - `models` (string[]) — Every model that answered, most-used first; empty when the upload named none - `harness_version` (string | null) - `client` (string | null) — The front end that ran the harness, such as a Claude Code entrypoint or a Codex originator - `model_provider` (string | null) - `effort` (string | null) - `usage` (object | null) — Token totals the source reported, by kind - `uploader_id` (string, required) - `device` (string | null) - `started_at` (string | null) - `ended_at` (string | null) - `user_turns` (integer) - `tool_calls` (integer) - `messages` (integer, required) — Stored messages, after redaction - `coverage` (enum, required) — "complete" · "partial" · "redacted" - `version` (integer, required) — Raised each time a changed upload replaces it - `updated_at` (string, required) - `environment` (object | null) — The working folder the session ran in, uploaded with it as one of the uploader's files - `201` — Created - `status` (enum) — "created" - `trace` (object) - `id` (string, required) — ltr_… - `harness` (string, required) - `session_id` (string, required) - `title` (string | null) - `project` (string | null) — The repository's folder name - `cwd` (string | null) — Where the session ran, home written as ~ - `git_branch` (string | null) - `git_commit` (string | null) - `git_remote` (string | null) — The repository URL, without credentials - `model` (string | null) — The model that answered most - `models` (string[]) — Every model that answered, most-used first; empty when the upload named none - `harness_version` (string | null) - `client` (string | null) — The front end that ran the harness, such as a Claude Code entrypoint or a Codex originator - `model_provider` (string | null) - `effort` (string | null) - `usage` (object | null) — Token totals the source reported, by kind - `uploader_id` (string, required) - `device` (string | null) - `started_at` (string | null) - `ended_at` (string | null) - `user_turns` (integer) - `tool_calls` (integer) - `messages` (integer, required) — Stored messages, after redaction - `coverage` (enum, required) — "complete" · "partial" · "redacted" - `version` (integer, required) — Raised each time a changed upload replaces it - `updated_at` (string, required) - `environment` (object | null) — The working folder the session ran in, uploaded with it as one of the uploader's files - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/trace-library/harness/session_id \ -H "Authorization: Bearer ast_..." ``` ### DELETE /trace-library/{harness}/{session_id} Delete one session (scope: write) Its uploader or the workspace owner may. Workspace keys are refused: uploads and reads belong to a person. Returns 503 not_migrated before the tables exist. Path parameters: - `harness` (string, required) — claude_code, codex, or another harness name - `session_id` (string, required) — The harness's own session id Responses: - `204` — Deleted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/trace-library/harness/session_id \ -H "Authorization: Bearer ast_..." ``` ## Notifications ### GET /notification-preferences Which task events the caller is emailed and pushed about (scope: read) A person who has never chosen gets the defaults: for email `needs_you` on and `done` off, for push both on. Every task email also carries a link that turns its kind off without signing in. Responses: - `200` — OK - `notification_preferences` (object, required) — The caller's own choices. They follow the person across workspaces. - `push` (object, required) — Which kinds reach the devices the caller subscribed (`/push-subscriptions`). Both on unless turned off; without a subscribed device nothing is pushed. - `needs_you` (boolean, required) — Notify my devices when one of my tasks starts waiting on me. - `done` (boolean, required) — Notify my devices when one of my tasks produces a result while I am away. - `email` (object, required) - `needs_you` (boolean, required) — Email me when one of my tasks starts waiting on me and I have not looked at it. On unless turned off. - `done` (boolean, required) — Email me when one of my tasks produces a result while I am away. Off unless turned on. - `marketing` (boolean, required) — Whether the caller's address still gets AgentSky marketing email. False once they unsubscribed, by an email's link or by this API. A bounce stops delivery but is not an unsubscribe. Login and account email are never affected. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/notification-preferences' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /notification-preferences Change which task events the caller is emailed and pushed about (scope: write) Send `email`, `push` or both, each with only the kinds to change; the response holds all of them. `email.marketing: false` unsubscribes the caller's own address from marketing email, the same opt-out as the unsubscribe link in a marketing email; 409 `no_email` for an account without an address. Answers 503 `not_available` for task kinds until the platform's notification tables are in place. Body (application/json): - `email` (object) - `needs_you` (boolean) - `done` (boolean) - `marketing` (enum) — false unsubscribes the caller's own address from marketing email; true is refused (400), since no surface offers subscribing — false - `push` (object) - `needs_you` (boolean) - `done` (boolean) Responses: - `200` — OK - `notification_preferences` (object, required) — The caller's own choices. They follow the person across workspaces. - `push` (object, required) — Which kinds reach the devices the caller subscribed (`/push-subscriptions`). Both on unless turned off; without a subscribed device nothing is pushed. - `needs_you` (boolean, required) — Notify my devices when one of my tasks starts waiting on me. - `done` (boolean, required) — Notify my devices when one of my tasks produces a result while I am away. - `email` (object, required) - `needs_you` (boolean, required) — Email me when one of my tasks starts waiting on me and I have not looked at it. On unless turned off. - `done` (boolean, required) — Email me when one of my tasks produces a result while I am away. Off unless turned on. - `marketing` (boolean, required) — Whether the caller's address still gets AgentSky marketing email. False once they unsubscribed, by an email's link or by this API. A bounce stops delivery but is not an unsubscribe. Login and account email are never affected. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/notification-preferences \ -H "Authorization: Bearer ast_..." ``` ### GET /push-subscriptions The devices the caller is notified on (scope: read) A subscribed device gets a push notification when one of the caller's tasks starts waiting on them or has a result, under the same rules as task emails, for the kinds left on under `push` in `/notification-preferences`. `application_server_key` is the key a browser subscribes with; it is null where browser push is not configured, and the list is empty where push is not turned on. Responses: - `200` — OK - `application_server_key` (string, required) - `data` (object[], required) - `id` (string, required) - `kind` (enum, required) — `web` for a browser, `expo` for the phone app. — "web" · "expo" - `endpoint` (string, required) — The push service's address for that browser, or the phone app's Expo device token. - `created_at` (string, required) - `last_seen_at` (string, required) — When that device last subscribed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/push-subscriptions' \ -H "Authorization: Bearer ast_..." ``` ### POST /push-subscriptions Subscribe a device to push notifications (scope: write) From a browser, send what its `pushManager.subscribe()` returned. From the phone app, send `{ kind: "expo", token }` with the device token Expo issued. Subscribing the same device again keeps one subscription. Answers 403 `not_enabled` where push is not turned on, 400 for an endpoint that is not a browser push service's or a token that is not Expo's, and 503 `not_migrated` until the platform's push tables are in place. Body (application/json): - `endpoint` (string) — A browser push service's address. With `keys`. - `keys` (object) - `p256dh` (string, required) - `auth` (string, required) - `kind` (enum) — `expo`, for the phone app. With `token`. — "expo" - `token` (string) — The device token Expo issued to this phone. Responses: - `201` — Created - `subscription` (object, required) — One device the caller asked to be notified on. A browser's keys are never returned. - `id` (string, required) - `kind` (enum, required) — `web` for a browser, `expo` for the phone app. — "web" · "expo" - `endpoint` (string, required) — The push service's address for that browser, or the phone app's Expo device token. - `created_at` (string, required) - `last_seen_at` (string, required) — When that device last subscribed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/push-subscriptions \ -H "Authorization: Bearer ast_..." ``` ### DELETE /push-subscriptions/{id} Stop notifying one of the caller's devices (scope: write) Path parameters: - `id` (string, required) Responses: - `204` — Removed - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/push-subscriptions/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ## Agent catalog ### GET /catalog/engines Read operational harness availability (scope: public) The delisted engine identities, read without an HTTP cache. New acquisition rejects them; existing sessions are retained. Responses: - `200` — Current delisting state - `delisted` (string[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/engines' \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/agents List hireable agents (scope: public) Every listed agent: platform engines, and other people's agents that were reviewed and approved, each served as it was approved and carrying `seller`. Needs no credentials; a caller that sends them also gets its own workspace's listings. Answers 503 `not_migrated` until the platform's catalog tables are in place. Query parameters: - `category` (string) - `q` (string) — Matches handle, name, tagline and description - `sort` (string) - `requires` (string) — Keep the agents whose stated requirements match: `account` needs the caller's own account at a vendor, `no_account` needs none, `mcp_servers` can use MCP servers. An account requirement is stated only where external accounts are turned on. Responses: - `200` — OK - `agents` (object[], required) - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/agents' \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/agents/{handle} One agent by handle (scope: public) Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `200` — OK - `agent` (object, required) - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/agents/handle' \ -H "Authorization: Bearer ast_..." ``` ### POST /catalog/listings List one of your own agents under a handle (scope: write) The listing is visible to your workspace only, and `agent: "@handle"` then starts a session with that agent. A handle is unique across the platform, engines included; a taken one is 409 `handle_taken`. Listing the same agent again replaces its listing and frees the old handle. A listing whose agent is archived or deleted stops being offered. Body (application/json): - `agent_id` (string, required) - `handle` (string, required) — 2 to 40 lower-case letters, digits or underscores; a leading @ is accepted - `tagline` (string, required) — max length 140 - `description` (string) — max length 4000 - `categories` (enum[]) — Defaults to general — "websites" · "content" · "video" · "code" · "research" · "general" - `does` (string[]) — max length 300 - `does_not` (string[]) — max length 300 - `examples` (string[]) — max length 300 Responses: - `201` — Created - `agent` (object, required) - `handle` (string, required) — Globally unique and stable: lower-case letters, digits and underscores. What a person types after "/". - `kind` (enum, required) — A platform engine, or an agent a person listed — "engine" · "agent" - `engine_type` (string | null, required) — The agent type to create an agent with, for an engine listing - `agent_id` (string | null, required) — The agent one of the caller's own workspace listings stands for; null for an engine and for another person's agent - `visibility` (enum, required) — public for everyone: an engine, or another person's agent that was reviewed and approved. workspace for the caller's own listing, whether or not it is also public. — "public" · "workspace" - `seller` (object | null, required) — Who made a public agent that is not a platform engine, by the name they chose. Null for an engine and for the caller's own listing. - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `does` (string[], required) — What it does. Empty when not yet written; never guessed. - `does_not` (string[], required) — What it does not do. Empty when not yet written. - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `examples` (string[], required) — Example tasks - `requirements` (object, required) — What the agent needs, stated rather than hidden. `mcp_servers` says whether it can use MCP servers; `website` is the engine's own site. `hosted_by_seller: { host }` is present when the agent runs on its maker's own server instead of a sandbox: what a person sends it, and the files of the task, go to that host, and a person's first task with it waits for them to agree (a task request). - `pricing` (object, required) — `{ mode: "usage" }`: the model and tool usage the task incurs, with no agent fee. `{ mode: "per_task", fee: { amount, currency } }`: usage, plus a flat fee the agent's maker asks for each task; `amount` is whole US cents as a string. Starting such an agent asks the person to confirm the price the first time (a task request of kind `spend`), holds the fee on their credit when the task starts and takes it when the task gives its first result. - `stats` (object, required) — Counts from real use, recounted hourly; never estimated. `uses`: sessions started on the agent. `comparisons` and `wins`: decided side-by-side comparisons the agent took part in, and the ones a person picked it in. `completed_yes` and `completed_no`: people who said a task on this agent did the job, and people who said it did not (`PUT /tasks/{id}/completion_answer`). - `uses` (integer, required) - `comparisons` (integer, required) - `wins` (integer, required) - `completed_yes` (integer, required) - `completed_no` (integer, required) - `usage` (object | null) — What one task typically consumes: medians over the agent's sessions of the last 30 days that metered model usage. Null until at least 10 such sessions exist. - `conformance` (object | null, required) — The verdicts of the listing's latest finished conformance run; for another person's agent, of the run it was submitted with. Null when it has never finished one. - `created_at` (string, required) - `installed` (boolean) — Whether the caller's workspace installed this agent from the catalog and left it enabled. Only on a read made for a workspace. - `default_model` (string | null) — The model the caller's workspace starts this agent with by default; null when it set none. Only with installed. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/catalog/listings \ -H "Authorization: Bearer ast_..." ``` ### DELETE /catalog/listings/{handle} Take one of your own listings down (scope: write) The agent itself is untouched. 404 for a handle that is not one of this workspace's listings. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `200` — OK - `ok` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/catalog/listings/handle \ -H "Authorization: Bearer ast_..." ``` ### POST /catalog/listings/{handle}/conformance Start a conformance run of one of your own listings (scope: write) Answers at once with the run as `running`; read it back with GET. The suite uses the listing the way a customer would, through this API and as you: it starts sessions in your workspace, which you pay for, and deletes them when it is done. 404 for a handle that is not one of this workspace's listings, an engine included. 409 `conformance_running` while a run of that listing is still going. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `202` — Accepted - `run` (object, required) - `id` (string, required) - `handle` (string, required) - `status` (enum, required) — A run still going after 30 minutes is reported as failed. — "running" · "finished" · "failed" - `started_at` (string, required) - `finished_at` (string | null, required) - `reason` (string | null, required) — Why the run could not finish; null unless it failed - `required_passed` (boolean, required) — No required check failed and at least one ran. False until the run finished. - `all_passed` (boolean, required) — Every check ran and passed. False until the run finished. - `report` (object | null, required) — Every check with its outcome; null until the run finished. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/catalog/listings/handle/conformance \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/listings/{handle}/conformance The latest conformance run of one of your own listings (scope: read) `run` is null when the listing has never been run. 404 for a handle that is not one of this workspace's listings. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `200` — OK - `run` (object | null, required) - `id` (string, required) - `handle` (string, required) - `status` (enum, required) — A run still going after 30 minutes is reported as failed. — "running" · "finished" · "failed" - `started_at` (string, required) - `finished_at` (string | null, required) - `reason` (string | null, required) — Why the run could not finish; null unless it failed - `required_passed` (boolean, required) — No required check failed and at least one ran. False until the run finished. - `all_passed` (boolean, required) — Every check ran and passed. False until the run finished. - `report` (object | null, required) — Every check with its outcome; null until the run finished. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/listings/handle/conformance' \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/listings/{handle}/submission Where one of your own listings stands with the public Market (scope: read) Whether it can be submitted and what is missing, its latest submission, and the approved version being served. 404 for a handle that is not one of this workspace's listings. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `200` — OK - `open` (boolean, required) — Whether this workspace may submit listings to the public Market - `missing` (string[], required) — What still has to be true before the listing can be submitted, in words. Empty when it can be. - `pricing` (object) — Present only where this workspace may set a flat fee on its public listings. - `seller_share_percent` (number, required) — The part of each fee the seller keeps - `fee_cents_min` (integer, required) - `fee_cents_max` (integer, required) - `submission` (object | null, required) — The latest submission; null when the listing has never been submitted. - `published` (object | null, required) — The approved version being served to everyone; null when the listing is not public. - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and it has not been submitted again. - `scheduled_check` (object) — What the scheduled checks of the approved version found. A public listing's approved version is checked when it is approved and then by the weekly conformance sweep, as its seller and on the seller's credit for usage, under a spend cap. Absent when the listing is not public. - `passing` (boolean | null, required) — Whether the required checks passed at the last scheduled run that finished; null before the first. False takes the listing's `conformance.required_passed` away for everyone; the listing stays public. - `reason` (string | null, required) — The required checks that failed, or why the latest run was skipped or could not finish - `checked_at` (string | null, required) - `running` (boolean, required) — Whether a scheduled run of this version is going now - `next_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/listings/handle/submission' \ -H "Authorization: Bearer ast_..." ``` ### POST /catalog/listings/{handle}/submission Submit one of your own listings for the public Market (scope: write) Stores the listing and its agent as they are now and puts them in front of a reviewer. Once approved, everyone sees the listing and can start it with `agent: "@handle"`; their session runs a hidden copy of the approved agent in their own workspace, on their own wallet. Later edits are not served until a new submission is approved. `fee` asks a flat fee for each task on top of usage; like everything else it takes effect, and changes, only when a submission carrying it is approved, and a submission without it makes the listing usage-only again. 403 `submissions_closed` when this workspace may not submit. 403 `pricing_closed` when `fee` is sent and this workspace may not set a price. 422 `not_submittable` when something is missing: the message names every item. 409 `already_in_review` while an earlier submission is waiting. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Body (application/json): - `seller_name` (string, required) — The name the Market shows as the maker. Not an email address. — max length 60 - `fee` (object) — The flat fee for each task, in whole US cents: 1 to 100000 - `amount` (string, required) — Whole US cents — pattern ^[1-9][0-9]*$ - `currency` (enum, required) — "USD" Responses: - `201` — Submitted - `open` (boolean, required) — Whether this workspace may submit listings to the public Market - `missing` (string[], required) — What still has to be true before the listing can be submitted, in words. Empty when it can be. - `pricing` (object) — Present only where this workspace may set a flat fee on its public listings. - `seller_share_percent` (number, required) — The part of each fee the seller keeps - `fee_cents_min` (integer, required) - `fee_cents_max` (integer, required) - `submission` (object | null, required) — The latest submission; null when the listing has never been submitted. - `published` (object | null, required) — The approved version being served to everyone; null when the listing is not public. - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and it has not been submitted again. - `scheduled_check` (object) — What the scheduled checks of the approved version found. A public listing's approved version is checked when it is approved and then by the weekly conformance sweep, as its seller and on the seller's credit for usage, under a spend cap. Absent when the listing is not public. - `passing` (boolean | null, required) — Whether the required checks passed at the last scheduled run that finished; null before the first. False takes the listing's `conformance.required_passed` away for everyone; the listing stays public. - `reason` (string | null, required) — The required checks that failed, or why the latest run was skipped or could not finish - `checked_at` (string | null, required) - `running` (boolean, required) — Whether a scheduled run of this version is going now - `next_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/catalog/listings/handle/submission \ -H "Authorization: Bearer ast_..." ``` ### DELETE /catalog/listings/{handle}/submission Withdraw a submission that is waiting for review (scope: write) 404 when nothing of this listing is waiting. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `200` — OK - `open` (boolean, required) — Whether this workspace may submit listings to the public Market - `missing` (string[], required) — What still has to be true before the listing can be submitted, in words. Empty when it can be. - `pricing` (object) — Present only where this workspace may set a flat fee on its public listings. - `seller_share_percent` (number, required) — The part of each fee the seller keeps - `fee_cents_min` (integer, required) - `fee_cents_max` (integer, required) - `submission` (object | null, required) — The latest submission; null when the listing has never been submitted. - `published` (object | null, required) — The approved version being served to everyone; null when the listing is not public. - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and it has not been submitted again. - `scheduled_check` (object) — What the scheduled checks of the approved version found. A public listing's approved version is checked when it is approved and then by the weekly conformance sweep, as its seller and on the seller's credit for usage, under a spend cap. Absent when the listing is not public. - `passing` (boolean | null, required) — Whether the required checks passed at the last scheduled run that finished; null before the first. False takes the listing's `conformance.required_passed` away for everyone; the listing stays public. - `reason` (string | null, required) — The required checks that failed, or why the latest run was skipped or could not finish - `checked_at` (string | null, required) - `running` (boolean, required) — Whether a scheduled run of this version is going now - `next_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/catalog/listings/handle/submission \ -H "Authorization: Bearer ast_..." ``` ### DELETE /catalog/listings/{handle}/publication Stop serving one of your own listings to everyone else (scope: write) The workspace listing and the agent stay. Sessions other people already started keep running. 404 when the listing is not public. Path parameters: - `handle` (string, required) — The agent's handle; a leading @ is accepted Responses: - `200` — OK - `open` (boolean, required) — Whether this workspace may submit listings to the public Market - `missing` (string[], required) — What still has to be true before the listing can be submitted, in words. Empty when it can be. - `pricing` (object) — Present only where this workspace may set a flat fee on its public listings. - `seller_share_percent` (number, required) — The part of each fee the seller keeps - `fee_cents_min` (integer, required) - `fee_cents_max` (integer, required) - `submission` (object | null, required) — The latest submission; null when the listing has never been submitted. - `published` (object | null, required) — The approved version being served to everyone; null when the listing is not public. - `taken_down` (object | null, required) — Set when a reviewer took the public listing down and it has not been submitted again. - `scheduled_check` (object) — What the scheduled checks of the approved version found. A public listing's approved version is checked when it is approved and then by the weekly conformance sweep, as its seller and on the seller's credit for usage, under a spend cap. Absent when the listing is not public. - `passing` (boolean | null, required) — Whether the required checks passed at the last scheduled run that finished; null before the first. False takes the listing's `conformance.required_passed` away for everyone; the listing stays public. - `reason` (string | null, required) — The required checks that failed, or why the latest run was skipped or could not finish - `checked_at` (string | null, required) - `running` (boolean, required) — Whether a scheduled run of this version is going now - `next_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/catalog/listings/handle/publication \ -H "Authorization: Bearer ast_..." ``` ### GET /seller/earnings What you have earned from the fees on your public listings (scope: read) Totals, a line per listing, a line per captured fee (newest first) and the payouts they went into. The seller keeps the share shown on each line; the rest is the platform's. A `referral` line is the caller's share of a fee another seller's agent was paid by a person the caller brought; it counts in `earned`, `paid` and `unpaid`, not in `gross` or `listings`. A fee is payable once the 72 hours its buyer has to dispute it have passed, and payouts are made once a month. Nothing here identifies a buyer. 404 where flat fees are not offered. Responses: - `200` — OK - `totals` (object, required) - `gross` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `earned` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `paid` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `unpaid` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `listings` (object[], required) - `handle` (string, required) - `tasks` (integer, required) - `gross` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `earned` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `lines` (object[], required) - `id` (string, required) - `handle` (string, required) - `kind` (enum, required) — `reversal`: a fee refunded after it had been paid out, taken off the next payout. `referral`: the caller's share of a fee another seller's agent was paid by a person the caller referred (`POST /seller/referrals`); its `fee` is that fee — "earning" · "reversal" · "referral" - `source` (enum, required) — Who brought the buyer; `referral` on a referral line — "shelf" · "seller_referred" · "helper" · "referral" - `fee` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `share_percent` (number, required) - `earned` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `status` (enum, required) — "waiting" · "disputed" · "refunded" · "payable" · "in_payout" · "paid" - `payable_at` (string, required) - `created_at` (string, required) - `payouts` (object[], required) - `id` (string, required) - `period_start` (string, required) - `period_end` (string, required) - `amount` (object, required) - `amount` (string, required) — Whole US cents; negative on a reversal - `currency` (enum, required) — "USD" - `status` (enum, required) — "owed" · "paid" - `paid_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/seller/earnings' \ -H "Authorization: Bearer ast_..." ``` ### GET /seller/account Your seller account status (scope: read) Returns the Stripe Connect account status for the caller. 404 when the caller has no seller account record or the payout tables are not yet migrated. Responses: - `200` — OK - `account_status` (enum, required) — "pending" · "active" · "restricted" - `payout_connected` (boolean, required) — True when the Stripe Connect account is active and transfers can be sent - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/seller/account' \ -H "Authorization: Bearer ast_..." ``` ### POST /seller/account Create your seller account record (scope: write) Creates or upserts the caller's seller account record. Idempotent — calling it again when a record already exists is a no-op. 404 when the payout tables are not yet migrated. Responses: - `201` — Created - `ok` (true, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/seller/account \ -H "Authorization: Bearer ast_..." ``` ### POST /seller/account/connect Start or resume Stripe Connect onboarding (scope: write) Returns a single-use Stripe account-link URL the seller opens to complete payout setup. 404 when the payout tables are not yet migrated. 503 when the payment processor is not configured. Responses: - `200` — OK - `url` (string, required) — Single-use Stripe account-link URL; expires quickly - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/seller/account/connect \ -H "Authorization: Bearer ast_..." ``` ### POST /seller/referrals Record that a user was referred by you (scope: write) Records that the target user was brought to the platform by the caller. A user can have at most one referral source; a second call for the same target is a no-op (200, `recorded: false`). 404 when the payout tables are not yet migrated. Body (application/json): - `referred_user_id` (string, required) — The id of the user who was referred Responses: - `200` — OK - `recorded` (boolean, required) — true when this call created the referral row; false when it already existed - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/seller/referrals \ -H "Authorization: Bearer ast_..." ``` ### GET /seller/notices What you were told about the agents you sell (scope: read) Newest first: a submission approved or sent back, a listing taken down, a lost `Checked` mark, a ruling on a disputed fee, a payout recorded. Each is also mailed once to the seller where task emails are on for the seller's workspace, unless the seller turned these emails off with the link in one of them. `handle` narrows the list to one listing plus the notices that are about none. 404 where neither public submissions nor flat fees are offered. Query parameters: - `handle` (string) Responses: - `200` — OK - `data` (object[], required) - `id` (string, required) - `kind` (enum, required) — "approved" · "sent_back" · "taken_down" · "checks_failed" · "dispute_refunded" · "dispute_upheld" · "payout_paid" - `handle` (string | null, required) - `title` (string, required) - `text` (string, required) — The notice in words, with the reviewer's reason or the amount - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/seller/notices' \ -H "Authorization: Bearer ast_..." ``` ### PUT /catalog/comparison_counts Report comparison counts per handle (scope: first-party) For the first-party client that records side-by-side comparison verdicts; any other token is refused with 403. The body is a full snapshot taken at `reported_at`: each handle is set to its totals and a handle left out is removed, so sending the same snapshot again changes nothing and an older one never overwrites a newer one. The totals reach `stats.comparisons` and `stats.wins` on the next hourly recount. Answers 503 `not_migrated` until the platform's table is in place. Body (application/json): - `reported_at` (string, required) - `counts` (object[], required) - `handle` (string, required) - `comparisons` (integer, required) - `wins` (integer, required) Responses: - `200` — OK - `handles` (integer, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/catalog/comparison_counts \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/categories The category vocabulary (scope: public) Responses: - `200` — OK - `categories` (object[], required) - `id` (enum, required) — "websites" · "content" · "video" · "code" · "research" · "general" - `label` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/categories' \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/environments List environment listings on the shelf (scope: public) Approved environment listings, plus the caller's own workspace's listings. With the shelf open to every workspace it needs no credentials; with it open to some workspaces only, readers signed in to one of them. Answers 404 when the shelf is not enabled. Answers 503 `not_migrated` until the shelf migration is applied. Query parameters: - `q` (string) — Matches handle, name, tagline and description - `category` (string) - `sort` (string) Responses: - `200` — OK - `environments` (object[], required) - `id` (string, required) — The listing id (lst_…) - `handle` (string, required) — Globally unique within environment listings; lower-case letters, digits and underscores - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `packages` (object | null, required) — The listed environment's package configuration, or null when it has none - `tools` (object | null, required) — The listed environment's tool policy, or null when it has none - `networking` (object | null, required) — The listed environment's networking configuration, or null when it has none - `installs` (integer, required) — How many workspaces have installed this listing - `seller` (object | null, required) — Who published it to other workspaces, by the name they chose; null while it is visible only to its own workspace - `visibility` (enum, required) — workspace = visible only to the owning workspace; public = approved, and visible to every workspace the shelf is open to. Other workspaces read and install the approved version; the owner's later edits wait for a new approval — "workspace" · "public" - `conformance` (object | null, required) — The smoke run the approved version passed; null for a listing that is not public - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/environments' \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/environments/{handle} One environment listing by handle (scope: public) Answers 404 when the shelf is not enabled for the reader, the handle is not found, or the listing belongs to a different workspace and is not approved. Path parameters: - `handle` (string, required) — The listing handle Responses: - `200` — OK - `environment` (object, required) - `id` (string, required) — The listing id (lst_…) - `handle` (string, required) — Globally unique within environment listings; lower-case letters, digits and underscores - `name` (string, required) - `tagline` (string, required) — One line, at most 140 characters - `description` (string, required) - `categories` (enum[], required) — "websites" · "content" · "video" · "code" · "research" · "general" - `packages` (object | null, required) — The listed environment's package configuration, or null when it has none - `tools` (object | null, required) — The listed environment's tool policy, or null when it has none - `networking` (object | null, required) — The listed environment's networking configuration, or null when it has none - `installs` (integer, required) — How many workspaces have installed this listing - `seller` (object | null, required) — Who published it to other workspaces, by the name they chose; null while it is visible only to its own workspace - `visibility` (enum, required) — workspace = visible only to the owning workspace; public = approved, and visible to every workspace the shelf is open to. Other workspaces read and install the approved version; the owner's later edits wait for a new approval — "workspace" · "public" - `conformance` (object | null, required) — The smoke run the approved version passed; null for a listing that is not public - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/environments/handle' \ -H "Authorization: Bearer ast_..." ``` ### POST /catalog/environments/{handle}/install Install an environment listing into your workspace (scope: write) Creates a copy of the listed environment in the caller's workspace that `extends` the original, so composition (packages, tool policy, networking) is applied at write time; another workspace's listing is composed from its approved version. A second install of the same listing by the same workspace is idempotent and returns the existing copy. Answers 404 when the shelf is not enabled or the listing is not found. Answers 503 `not_migrated` until the shelf migration is applied. Path parameters: - `handle` (string, required) — The listing handle Responses: - `201` — Created - `environment` (object, required) - `id` (string, required) — env_… - `name` (string, required) - `config` (object, required) — `packages` are installed into the image at build time — an environment is built when it is created and rebuilt when its config changes, so a session starting from it pays no install cost. `networking` is the egress policy its sessions run under; `limited` needs `allow_package_managers` when packages are declared, or the install could never succeed. - `type` ("cloud") — default: "cloud" - `packages` (object) - `apt` (string[]) — default: [] — max length 512 - `pip` (string[]) — default: [] — max length 512 - `npm` (string[]) — default: [] — max length 512 - `networking` (object) - type: "unrestricted" (object) - `type` ("unrestricted", required) - type: "limited" (object) - `type` ("limited", required) - `allowed_hosts` (string[]) — default: [] — max length 253 — pattern ^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - `allow_mcp_servers` (boolean) — default: false - `allow_package_managers` (boolean) — default: false - `is_default` (boolean, required) — Whether a new session that names no environment uses this one. At most one per workspace; setting it on one clears it on the others. - `extends` (string | null, required) — The env_… id of the parent environment this one extends, or null. Immutable after create. Child packages, tools.disabled and networking compose on the parent's at write time. - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/catalog/environments/handle/install \ -H "Authorization: Bearer ast_..." ``` ### GET /catalog/listings/{handle}/hosting Where one of the workspace's listings runs (scope: read) The secret is never returned here. Answers 404 until running an agent on your own server is open for the workspace. Path parameters: - `handle` (string, required) — The handle of one of the workspace's own listings. Responses: - `200` — OK - `hosting` (enum, required) — `platform`: in a sandbox. `seller`: on the maker's own server. — "platform" · "seller" - `endpoint_url` (string) — The address tasks are sent to. Present when `hosting` is `seller`. - `secret` (string) — The secret requests are signed with. Present only in the response that made or replaced it. - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/catalog/listings/handle/hosting' \ -H "Authorization: Bearer ast_..." ``` ### PUT /catalog/listings/{handle}/hosting Run a listing on your own server (scope: write) Each task given to the listing is then sent to this address instead of a sandbox, as one POST per turn: `{id, type: "task.turn", created_at, task: {id, session_id, agent}, message: {id, text}, pass: {token, expires_at}, api_base, callback_url}`. The request is signed like a webhook: `x-asteroids-timestamp`, and `x-asteroids-signature` = `v1=` + HMAC-SHA256 of `.` with the listing's secret; check it before trusting the body. Answer 2xx within 10 seconds, then do the work and report to `callback_url`. The request is made once and is not retried; an address that does not answer 2xx in time ends the turn with an error the person reads. `pass.token` is a Bearer credential for that one turn of that one task. It reads the task (`GET /tasks/{id}`), reports to it (`POST /tasks/{id}/callback`), and reads and writes its files (`/tasks/{id}/files`); every other route refuses it with 403. It stops working when the turn ends, when the person stops the turn or archives the task, or after 15 minutes, whichever is first; a turn still open then is ended with an error. A later message from the person, or their answer to a question, is a new turn with a new pass. The address must be https on the public internet. The first call answers 201 with the signing secret, shown once; a later call changes the address and keeps the secret. A listing that is already public keeps running where its approved version said until a new submission is approved. Answers 404 until running an agent on your own server is open for the workspace. 503 `not_migrated` until its tables are in place. Path parameters: - `handle` (string, required) — The handle of one of the workspace's own listings. Body (application/json): - `endpoint_url` (string, required) — The https address tasks are sent to. Responses: - `200` — Address changed - `hosting` (enum, required) — `platform`: in a sandbox. `seller`: on the maker's own server. — "platform" · "seller" - `endpoint_url` (string) — The address tasks are sent to. Present when `hosting` is `seller`. - `secret` (string) — The secret requests are signed with. Present only in the response that made or replaced it. - `created_at` (string) - `updated_at` (string) - `201` — Now runs on your server; carries the secret - `hosting` (enum, required) — `platform`: in a sandbox. `seller`: on the maker's own server. — "platform" · "seller" - `endpoint_url` (string) — The address tasks are sent to. Present when `hosting` is `seller`. - `secret` (string) — The secret requests are signed with. Present only in the response that made or replaced it. - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/catalog/listings/handle/hosting \ -H "Authorization: Bearer ast_..." ``` ### DELETE /catalog/listings/{handle}/hosting Run a listing in a sandbox again (scope: write) Tasks already started on your server can no longer be sent to it: their next turn ends with an error. Answers 404 until running an agent on your own server is open for the workspace. Path parameters: - `handle` (string, required) — The handle of one of the workspace's own listings. Responses: - `200` — OK - `hosting` (enum, required) — `platform`: in a sandbox. `seller`: on the maker's own server. — "platform" · "seller" - `endpoint_url` (string) — The address tasks are sent to. Present when `hosting` is `seller`. - `secret` (string) — The secret requests are signed with. Present only in the response that made or replaced it. - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/catalog/listings/handle/hosting \ -H "Authorization: Bearer ast_..." ``` ### POST /catalog/listings/{handle}/hosting/secret Replace the secret a listing's requests are signed with (scope: write) Answers the new secret, shown once. The next request is signed with it; the old one is no longer used. Answers 404 until running an agent on your own server is open for the workspace. Path parameters: - `handle` (string, required) — The handle of one of the workspace's own listings. Responses: - `201` — Replaced; carries the new secret - `hosting` (enum, required) — `platform`: in a sandbox. `seller`: on the maker's own server. — "platform" · "seller" - `endpoint_url` (string) — The address tasks are sent to. Present when `hosting` is `seller`. - `secret` (string) — The secret requests are signed with. Present only in the response that made or replaced it. - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/catalog/listings/handle/hosting/secret \ -H "Authorization: Bearer ast_..." ``` ## Artifacts ### GET /artifacts List the workspace's artifacts (scope: read) Newest first. With `task_id`, that task's only; with `kind=design_system`, design systems only. Answers 503 `not_migrated` until the platform's artifact tables are in place. Query parameters: - `task_id` (string) - `kind` (string) Responses: - `200` — OK - `artifacts` (object[], required) - `id` (string, required) - `task_id` (string, required) — The task it belongs to (sess_…) - `name` (string, required) — Unique within the task; publishing under the same name adds a version - `kind` (string | null, required) — design_system when the current revision holds project/design-system.json; null otherwise — "design_system" · null - `version` (integer, required) — The current revision - `entry` (string, required) — The file opened first - `files` (object[], required) - `path` (string, required) - `sha256` (string, required) - `size_bytes` (integer, required) - `share_url` (string | null, required) — The public link, or null while not shared - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts' \ -H "Authorization: Bearer ast_..." ``` ### POST /artifacts Publish task files as an artifact revision (scope: write) Publishes paths from the task's file space (`/tasks/{id}/files`) as a new revision of the artifact with that name in the task, creating the artifact on first use. A path that names a folder publishes everything under it. The revision records each file's content at this moment; later edits to the task's files do not change it. `entry` defaults to the only file, or to an `index.html`. 400 when a path names nothing; at most 500 files. Body (application/json): - `task_id` (string, required) - `name` (string, required) — max length 120 - `paths` (string[], required) - `entry` (string) Responses: - `201` — Created - `artifact` (object, required) - `id` (string, required) - `task_id` (string, required) — The task it belongs to (sess_…) - `name` (string, required) — Unique within the task; publishing under the same name adds a version - `kind` (string | null, required) — design_system when the current revision holds project/design-system.json; null otherwise — "design_system" · null - `version` (integer, required) — The current revision - `entry` (string, required) — The file opened first - `files` (object[], required) - `path` (string, required) - `sha256` (string, required) - `size_bytes` (integer, required) - `share_url` (string | null, required) — The public link, or null while not shared - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/artifacts \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"task_id":"sess_cmtb7q2j","name":"Launch page","paths":["site/index.html","site/style.css"],"entry":"site/index.html"}' ``` ### GET /artifacts/{id} One artifact with every revision (scope: read) Path parameters: - `id` (string, required) — The artifact id Responses: - `200` — OK - `artifact` (any, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /artifacts/{id} Remove an artifact (scope: write) It stops being listed and its public link stops working. Publishing under the same name in the task afterwards brings it back with the next version number. Path parameters: - `id` (string, required) — The artifact id Responses: - `200` — OK - `ok` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5 \ -H "Authorization: Bearer ast_..." ``` ### POST /artifacts/{id}/share Turn the artifact's public link on or off (scope: write) The link needs no credentials and always serves the current revision. Published pages are served sandboxed: their scripts cannot read the platform's cookies or call its API as the visitor. Turning sharing off makes the link stop working; turning it on again gives a new link. Path parameters: - `id` (string, required) — The artifact id Body (application/json): - `enabled` (boolean, required) Responses: - `200` — OK - `share_url` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/share \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"enabled":true}' ``` ### POST /artifacts/design-systems Import a design system (scope: write) Imports a design system's file table (the shape a Claude Design System artifact keeps: `design-system.json`, `tokens.json`, `README.md`, `components//README.md` and `preview.html`, `assets/…`, `fonts/…`, with or without the leading `project/`) into the task's files under `design-systems//project/`, then publishes that folder as an artifact named after its title. Binary files are `data:` URIs. 400 lists every refused path: `..`, dot segments, toolchain manifests and `blob:` pointers into another service's store. Body (application/json): - `task_id` (string, required) - `title` (string) — Replaces the index's title; required when the table has none — max length 120 - `files` (map, required) — Path → text, or a data: URI for binary files; at most 500 Responses: - `201` — Created - `artifact` (object, required) - `id` (string, required) - `task_id` (string, required) — The task it belongs to (sess_…) - `name` (string, required) — Unique within the task; publishing under the same name adds a version - `kind` (string | null, required) — design_system when the current revision holds project/design-system.json; null otherwise — "design_system" · null - `version` (integer, required) — The current revision - `entry` (string, required) — The file opened first - `files` (object[], required) - `path` (string, required) - `sha256` (string, required) - `size_bytes` (integer, required) - `share_url` (string | null, required) — The public link, or null while not shared - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/artifacts/design-systems \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"task_id":"sess_cmtb7q2j","title":"Acme","files":{"README.md":"Set body copy in `body`; `ink` on `surface-100`.","tokens.json":"{\"color\":{\"themes\":[{\"id\":\"light\",\"name\":\"Light\"}],\"tokens\":[{\"name\":\"ink\",\"value\":\"#2b2118\",\"usage\":\"Text.\"}]}}"}}' ``` ### GET /artifacts/{id}/design-system Read the design system an artifact holds (scope: read) 404 when the artifact is not a design system. Path parameters: - `id` (string, required) — The artifact id Responses: - `200` — OK - `design_system` (object, required) — A design system as its files say: the index, the brand book, tokens with the stylesheet compiled from them, components and asset groups. Paths are relative to `root`. - `artifact_id` (string, required) - `name` (string, required) - `version` (integer, required) - `root` (string, required) — The folder that holds project/: empty or ending in / - `title` (string, required) - `namespace` (string | null) - `libraries` (object[]) - `name` (string) - `version` (string) - `last_change` (object | null) - `editable` (boolean, required) - `editable_files` (string[], required) — The text files saved through PUT /artifacts/{id}/design-system/files/{path}, project/README.md first; empty when the system is read-only - `readme` (string | null, required) — project/README.md, the brand book: read it first - `sections` (object[], required) - `path` (string) - `title` (string) - `markdown` (string) - `tokens` (object | null, required) — tokens.json as read, with `dropped` naming every value that could not be read and why - `tokens_css` (string, required) — tokens.css compiled from tokens.json, without @font-face - `fonts` (object[], required) - `family` (string) - `path` (string) - `weight` (string) - `style` (string) - `cover` (boolean, required) - `components` (object[], required) - `name` (string) - `group` (string | null) - `height` (integer) - `width` (integer | null) - `subtitle` (string | null) - `readme` (string | null) - `has_preview` (boolean) - `assets` (object[], required) - `files` (string[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/design-system' \ -H "Authorization: Bearer ast_..." ``` ### GET /artifacts/{id}/design-system/comments Read design-system discussion (scope: read) Path parameters: - `id` (string, required) — The artifact id Responses: - `200` — OK - `comments` (object[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/design-system/comments' \ -H "Authorization: Bearer ast_..." ``` ### POST /artifacts/{id}/design-system/comments Comment on a design system, color or component (scope: write) The server records the authenticated author and version. Comments stay private to workspace members and do not start an agent or create a publication. target is overview, color: or component:. 409 when base_version is stale. Path parameters: - `id` (string, required) — The artifact id Body (application/json): - `text` (string, required) — max length 4000 - `target` (string) — default: "overview" — max length 200 - `base_version` (integer, required) Responses: - `201` — Created - `comment` (object) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/design-system/comments \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"text":"Use a darker ink for small text.","target":"color:ink","base_version":3}' ``` ### GET /artifacts/{id}/design-system/previews/{component} One component preview as a document (scope: read) The preview with tokens.css, fonts, bundle.css, libraries and bundle.js preloaded and `data-theme` set, as JSON for a sandboxed frame (`srcdoc` with no same-origin access). `Cover` is the system's cover. Path parameters: - `id` (string, required) — The artifact id - `component` (string, required) Query parameters: - `theme` (string) — A theme id from tokens.json; defaults to the first Responses: - `200` — OK - `html` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/design-system/previews/component' \ -H "Authorization: Bearer ast_..." ``` ### PUT /artifacts/{id}/design-system/tokens Save edited design tokens (scope: write) Writes tokens.json whole, compiles tokens.css from it, sets the index's lastChange and publishes the system's next version. 409 `conflict` when `base_version` is not the current version. Path parameters: - `id` (string, required) — The artifact id Body (application/json): - `base_version` (integer, required) - `tokens` (object, required) - `note` (string) — max length 280 Responses: - `200` — OK - `artifact` (object, required) - `id` (string, required) - `task_id` (string, required) — The task it belongs to (sess_…) - `name` (string, required) — Unique within the task; publishing under the same name adds a version - `kind` (string | null, required) — design_system when the current revision holds project/design-system.json; null otherwise — "design_system" · null - `version` (integer, required) — The current revision - `entry` (string, required) — The file opened first - `files` (object[], required) - `path` (string, required) - `sha256` (string, required) - `size_bytes` (integer, required) - `share_url` (string | null, required) — The public link, or null while not shared - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/design-system/tokens \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"base_version":3,"tokens":{"color":{"themes":[{"id":"light","name":"Light"}],"tokens":[{"name":"ink","value":"#1d1712","usage":"Text."}]}},"note":"Darker ink"}' ``` ### PUT /artifacts/{id}/design-system/files/{path} Save or restore one design-system file (scope: write) Saves one of the system's `editable_files` (path relative to `root`, such as `project/README.md`) as the system's next version: `content` replaces it, or `restore_version` puts back the file as it was in that version. tokens.json is saved as tokens are, recompiling tokens.css. Sets the index's lastChange. 409 `conflict` when `base_version` is not the current version; 400 on a read-only system or a file that is not editable; 404 when the file is not in the system or in the restored version. Path parameters: - `id` (string, required) — The artifact id - `path` (string, required) Body (application/json): - `base_version` (integer, required) - `content` (string) — The file's new text; send this or restore_version - `restore_version` (integer) — An earlier version whose copy of the file becomes the next version - `note` (string) — max length 280 Responses: - `200` — OK - `artifact` (object, required) - `id` (string, required) - `task_id` (string, required) — The task it belongs to (sess_…) - `name` (string, required) — Unique within the task; publishing under the same name adds a version - `kind` (string | null, required) — design_system when the current revision holds project/design-system.json; null otherwise — "design_system" · null - `version` (integer, required) — The current revision - `entry` (string, required) — The file opened first - `files` (object[], required) - `path` (string, required) - `sha256` (string, required) - `size_bytes` (integer, required) - `share_url` (string | null, required) — The public link, or null while not shared - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/design-system/files/report.md \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"base_version":3,"content":"# Acme\n\nSet body copy in `body`.","note":"Brand book voice"}' ``` ### GET /artifacts/{id}/export Download an artifact as one archive (scope: read) A zip archive of every file of the current revision, or of `version=N`, each at its published path. 413 `export_too_large` when the files are more than 64 MiB together. Path parameters: - `id` (string, required) — The artifact id Query parameters: - `version` (integer) Responses: - `200` — The archive - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/export' \ -H "Authorization: Bearer ast_..." ``` ### GET /artifacts/{id}/files/{path} Download one file of an artifact (scope: read) The current revision's file, or `version=N`. Always sent as a download. Path parameters: - `id` (string, required) — The artifact id - `path` (string, required) Query parameters: - `version` (integer) Responses: - `200` — The bytes - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/artifacts/cmtb6a1r9t4f2c01s6x8y3z5/files/report.md' \ -H "Authorization: Bearer ast_..." ``` ## Browser ### GET /sessions/{id}/browser-handoff See the agent's browser during a hand-off (scope: read) While a `browser_handoff` request in this session is open and addressed to the caller, answers the page the agent's own browser is on. 404 `no_handoff` when none is open, 403 `not_addressed` when it is for someone else, 503 `browser_unavailable` when the browser cannot be reached. Answer the request (`done` or `skip`) to hand the browser back. Path parameters: - `id` (string, required) — The session id Responses: - `200` — The agent's browser: `open: false` when none is running, else the page as a JPEG (base64) with its CSS width and height, address and title. - `browser` (object) - `open` (boolean) - `url` (string) - `title` (string) - `width` (integer) - `height` (integer) - `jpeg` (string) — Base64 JPEG of the visible page. - `403` — Error — { error: { code, message } } - `404` — Error — { error: { code, message } } - `503` — Error — { error: { code, message } } - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/browser-handoff' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/browser-handoff Act in the agent's browser during a hand-off (scope: write) One action by the caller in the agent's browser, under the same conditions as reading it: `{type: "click", x, y}` (CSS pixels of the frame), `{type: "text", text}` (at most 2,000 characters, typed where the focus is), `{type: "key", key}` (Enter, Tab, Backspace, Escape, the arrows or Space) or `{type: "scroll", x, y, deltaY}`. Answers the page afterwards. Path parameters: - `id` (string, required) — The session id Body (application/json): - `type` (enum, required) — "click" · "text" · "key" · "scroll" - `x` (number) - `y` (number) - `text` (string) - `key` (enum) — "Enter" · "Tab" · "Backspace" · "Escape" · "ArrowUp" · "ArrowDown" · "ArrowLeft" · "ArrowRight" · "Space" - `deltaY` (number) Responses: - `200` — The agent's browser: `open: false` when none is running, else the page as a JPEG (base64) with its CSS width and height, address and title. - `browser` (object) - `open` (boolean) - `url` (string) - `title` (string) - `width` (integer) - `height` (integer) - `jpeg` (string) — Base64 JPEG of the visible page. - `400` — Error — { error: { code, message } } - `403` — Error — { error: { code, message } } - `404` — Error — { error: { code, message } } - `503` — Error — { error: { code, message } } - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/browser-handoff \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/browser Read the session browser and live snapshots (scope: read) Uses the session's existing universe and token restrictions. Viewing reconnects without restarting execution. Expired browsers are finalized; final screenshots and action history remain readable. Path parameters: - `id` (string, required) — The session id Responses: - `200` — Authorized browser state, or null when the session has no browser. Provider credentials, interactive viewer links and browser connection URLs are never returned. - `browser` (object | null, required) - `id` (string, required) - `status` (enum, required) — "preparing" · "running" · "completed" · "blocked" · "failed" · "stopped" - `viewerUrl` (null, required) — Always null. Observation uses screenshots; interactive provider viewer links are never exposed. - `url` (string, required) - `startedAt` (string, required) - `expiresAt` (string, required) - `endedAt` (string | null, required) - `elapsedMs` (number, required) - `actionCount` (integer, required) - `progress` (string) - `claim` (string) — The agent's completion claim, separate from task verification. - `resolvedModel` (string) — Resolved Jev model reported by OpenRouter. - `error` (string) - `actions` (object[], required) - `id` (string, required) - `verb` (string, required) - `detail` (string, required) - `timestamp` (string, required) - `status` (string, required) - `verification` (object, required) - `status` (enum, required) — "passed" · "failed" · "unverified" - `checks` (object[], required) - `label` (string, required) - `status` (enum, required) — "passed" · "failed" · "unverified" - `detail` (string, required) - `lastScreenshot` (string | null, required) — Latest observed or final JPEG as a data URL when capture succeeded. - `lastScreenshotAt` (string | null) — Time the latest screenshot was captured successfully. - `cleanupPending` (boolean, required) — A terminal browser may still be releasing provider resources; cleanup is retried durably. - `costs` (object, required) — Recorded billed USD, including the fixed Mercury text-entry helper. Receipts can arrive after completion. - `decisionUsd` (number, required) - `helperUsd` (number, required) - `browserUsd` (number, required) - `runtimeUsd` (number, required) - `totalUsd` (number, required) — Metered cost including waived usage. - `chargedUsd` (number, required) — Actual amount charged after billing waivers. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/browser' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/browser Prepare an isolated browser for a session (scope: write) Prepare before sending the task's first turn. Requires an active session, positive credit balance and remaining session budget. An identical repeated request reuses the browser; a different configuration or an ended browser returns 409. The browser has a 15-minute lifetime, fixed viewport and bounded actions. Structured Google Flights verification checks visible route, dates, passengers, cabin and results; tasks without verification remain unverified. Browser usage is billed separately from model and runtime usage. Path parameters: - `id` (string, required) — The session id Body (application/json): - `task` (string, required) — max length 16000 - `startUrl` (string, required) - `viewport` (object, required) - `width` (integer, required) - `height` (integer, required) - `verification` (object) - `kind` ("google-flights", required) - `origin` (string, required) — max length 120 - `destination` (string, required) — max length 120 - `departureDate` (string, required) — pattern ^\d{4}-\d{2}-\d{2}$ - `returnDate` (string) — pattern ^\d{4}-\d{2}-\d{2}$ - `passengers` (integer, required) - `cabin` (enum, required) — "economy" · "premium-economy" · "business" · "first" Responses: - `201` — Authorized browser state, or null when the session has no browser. Provider credentials, interactive viewer links and browser connection URLs are never returned. - `browser` (object | null, required) - `id` (string, required) - `status` (enum, required) — "preparing" · "running" · "completed" · "blocked" · "failed" · "stopped" - `viewerUrl` (null, required) — Always null. Observation uses screenshots; interactive provider viewer links are never exposed. - `url` (string, required) - `startedAt` (string, required) - `expiresAt` (string, required) - `endedAt` (string | null, required) - `elapsedMs` (number, required) - `actionCount` (integer, required) - `progress` (string) - `claim` (string) — The agent's completion claim, separate from task verification. - `resolvedModel` (string) — Resolved Jev model reported by OpenRouter. - `error` (string) - `actions` (object[], required) - `id` (string, required) - `verb` (string, required) - `detail` (string, required) - `timestamp` (string, required) - `status` (string, required) - `verification` (object, required) - `status` (enum, required) — "passed" · "failed" · "unverified" - `checks` (object[], required) - `label` (string, required) - `status` (enum, required) — "passed" · "failed" · "unverified" - `detail` (string, required) - `lastScreenshot` (string | null, required) — Latest observed or final JPEG as a data URL when capture succeeded. - `lastScreenshotAt` (string | null) — Time the latest screenshot was captured successfully. - `cleanupPending` (boolean, required) — A terminal browser may still be releasing provider resources; cleanup is retried durably. - `costs` (object, required) — Recorded billed USD, including the fixed Mercury text-entry helper. Receipts can arrive after completion. - `decisionUsd` (number, required) - `helperUsd` (number, required) - `browserUsd` (number, required) - `runtimeUsd` (number, required) - `totalUsd` (number, required) — Metered cost including waived usage. - `chargedUsd` (number, required) — Actual amount charged after billing waivers. - `400` — Error — { error: { code, message } } - `402` — Error — { error: { code, message } } - `409` — Error — { error: { code, message } } - `502` — Error — { error: { code, message } } - `503` — Error — { error: { code, message } } - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/browser \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"task":"Find the Example Domain heading and stop when it is visible.","startUrl":"https://example.com","viewport":{"width":1120,"height":780}}' ``` ### DELETE /sessions/{id}/browser Stop the session browser and release its resources (scope: write) Fences new browser actions before capturing the final page and releasing the browser. Repeated requests retry unfinished cleanup; a session without a browser returns null. This operation stops browser access; use POST /sessions/{id}/interrupt to also interrupt the agent's turn. Path parameters: - `id` (string, required) — The session id Responses: - `200` — Authorized browser state, or null when the session has no browser. Provider credentials, interactive viewer links and browser connection URLs are never returned. - `browser` (object | null, required) - `id` (string, required) - `status` (enum, required) — "preparing" · "running" · "completed" · "blocked" · "failed" · "stopped" - `viewerUrl` (null, required) — Always null. Observation uses screenshots; interactive provider viewer links are never exposed. - `url` (string, required) - `startedAt` (string, required) - `expiresAt` (string, required) - `endedAt` (string | null, required) - `elapsedMs` (number, required) - `actionCount` (integer, required) - `progress` (string) - `claim` (string) — The agent's completion claim, separate from task verification. - `resolvedModel` (string) — Resolved Jev model reported by OpenRouter. - `error` (string) - `actions` (object[], required) - `id` (string, required) - `verb` (string, required) - `detail` (string, required) - `timestamp` (string, required) - `status` (string, required) - `verification` (object, required) - `status` (enum, required) — "passed" · "failed" · "unverified" - `checks` (object[], required) - `label` (string, required) - `status` (enum, required) — "passed" · "failed" · "unverified" - `detail` (string, required) - `lastScreenshot` (string | null, required) — Latest observed or final JPEG as a data URL when capture succeeded. - `lastScreenshotAt` (string | null) — Time the latest screenshot was captured successfully. - `cleanupPending` (boolean, required) — A terminal browser may still be releasing provider resources; cleanup is retried durably. - `costs` (object, required) — Recorded billed USD, including the fixed Mercury text-entry helper. Receipts can arrive after completion. - `decisionUsd` (number, required) - `helperUsd` (number, required) - `browserUsd` (number, required) - `runtimeUsd` (number, required) - `totalUsd` (number, required) — Metered cost including waived usage. - `chargedUsd` (number, required) — Actual amount charged after billing waivers. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/browser \ -H "Authorization: Bearer ast_..." ``` ## Workspace ### GET /sessions/{id}/workspace/files List a directory in the session's workspace (scope: read) Returns up to 200 entries (name, type, size, mtime) for a directory under /data/workspace. Dotfiles are excluded by default; pass ?hidden=true to include them. Only regular files and directories are listed: symlinks, FIFOs, sockets and devices never are. Paths are confined to the workspace root; '..' and absolute paths outside the root return 400 invalid_path. A parked or suspended pod returns 409 session_parked — send a message to wake it. Path parameters: - `id` (string, required) — The session id Query parameters: - `path` (string) — Relative or absolute path under workspace root; defaults to root - `hidden` (boolean) — Include dotfiles (default false) Responses: - `200` — OK - `path` (string) - `entries` (object[]) - `name` (string) - `type` (enum) — "file" · "directory" - `size` (integer) - `mtime` (string) - `truncated` (boolean) - `limit` (integer) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/files' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/workspace/files/content Download a file from the session's workspace (scope: read) Returns the raw file bytes as an attachment. Limited to 1 MB. Binary files are supported. Content-Type is inferred from the file extension. Returns 400 not_a_file for directories, 413 file_too_large when over the cap, 403 path_escape for symlink escapes, 409 session_parked when the pod is not running. Path parameters: - `id` (string, required) — The session id Query parameters: - `path` (string, required) — Relative or absolute path to the file under workspace root Responses: - `200` — File contents - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/files/content?path=report.md' \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/workspace/changes Git status and diff summary for each repo in the session's workspace (scope: read) Finds up to 10 git repositories within three levels of the workspace root and, for each, runs `git status --porcelain=v1` and `git diff --stat HEAD` inside the pod as the workspace owner, with fsmonitor, hooks, external diff drivers and textconv disabled. Each output is bounded to 200 lines. `repos` is empty when the workspace has no repository. Never wakes the pod: 409 session_parked when it is not running, 409 workspace_unsupported when its runtime has no exec or resumes a parked sandbox on contact (E2B), 503 workspace_unavailable when the read cannot run as the workspace owner. Path parameters: - `id` (string, required) — The session id Responses: - `200` — OK - `root` (string) - `repos` (object[]) - `path` (string) — repo directory relative to root; "." for the root itself - `status` (string | null) — git status --porcelain=v1 output; null when clean - `stat` (string | null) — git diff --stat HEAD output; null when no diff or no commit yet - `limit` (integer) — most repos reported - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/changes' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/workspace/import Copy a folder from the caller's machine into the workspace (scope: write) The body is the folder as a gzipped tar (up to 25 MB). It is unpacked at / as the workspace owner and made a git repository whose first commit is that copy, so /workspace/patch later answers the work done on it. Wakes a parked pod. 400 invalid_name, 409 already_exists, 413 archive_too_large, 422 invalid_archive. Path parameters: - `id` (string, required) — The session id Query parameters: - `name` (string, required) — the folder's name in the workspace: 1–80 letters, digits, spaces, dots, dashes or underscores Responses: - `200` — OK - `path` (string) - `files` (integer) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/import \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/workspace/imports Start a resumable folder upload into the workspace (scope: write) For folders over the single-request import's 25 MB: declare the folder's name, the gzipped tar's size (up to 2 GiB) and sha256, then send it in parts and complete. Answers the upload; its parts are numbered from 0. Or send {name, file_id}: a gzipped tar already stored as one of your files, which needs no parts and answers state verified, so complete unpacks it straight away (404 for a file you cannot see, 400 not_downloadable). 404 when resumable imports are not enabled for the workspace, 400 invalid_name or invalid_request (size, sha256 or part_size out of range). Path parameters: - `id` (string, required) — The session id Body (application/json): - option 1 (object) - `name` (string, required) — the folder's name in the workspace, as for /workspace/import - `size` (integer, required) — bytes of the gzipped tar, at most 2 GiB - `sha256` (string, required) — hex sha256 of the whole tar - `part_size` (integer) — bytes per part, 1–16 MiB; default 8 MiB - option 2 (object) - `name` (string, required) — the folder's name in the workspace, as for /workspace/import - `file_id` (string, required) — one of your files holding the gzipped tar, at most 2 GiB Responses: - `201` — Created - `id` (string) - `name` (string) - `size` (integer) - `sha256` (string) - `part_size` (integer) - `parts` (integer) — how many parts the upload has - `received` (integer[]) — part numbers already stored - `state` (enum) — "uploading" · "verified" · "importing" · "imported" · "failed" - `path` (string) — where the folder landed, once imported - `files` (integer) — files unpacked, once imported - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/imports \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/workspace/imports/{upload_id} Read a resumable folder upload: which parts arrived and its state (scope: read) `received` lists the part numbers already stored, so an interrupted upload sends only the rest. Path parameters: - `id` (string, required) — The session id - `upload_id` (string, required) Responses: - `200` — OK - `id` (string) - `name` (string) - `size` (integer) - `sha256` (string) - `part_size` (integer) - `parts` (integer) — how many parts the upload has - `received` (integer[]) — part numbers already stored - `state` (enum) — "uploading" · "verified" · "importing" · "imported" · "failed" - `path` (string) — where the folder landed, once imported - `files` (integer) — files unpacked, once imported - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/imports/upload_id' \ -H "Authorization: Bearer ast_..." ``` ### PUT /sessions/{id}/workspace/imports/{upload_id}/parts/{part} Store one part of a resumable folder upload (scope: write) The body is the part's bytes; every part but the last is exactly part_size long. Sending a part again replaces it. An optional X-Part-Sha256 header is checked. 400 invalid_part or part_size_mismatch, 422 part_checksum_mismatch. Path parameters: - `id` (string, required) — The session id - `upload_id` (string, required) - `part` (integer, required) — the part number, from 0 Responses: - `200` — OK - `part` (integer) - `size` (integer) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/imports/upload_id/parts/part \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/workspace/imports/{upload_id}/complete Finish a resumable folder upload and unpack it into the workspace (scope: write) Checks the parts' total size and sha256 against the declaration, then has the pod pull and unpack the folder in the background, exactly as /workspace/import would; poll the upload until its state is imported or failed. Calling it again is safe. 202 while importing. 409 parts_missing, 422 size_mismatch or checksum_mismatch, 409 import_unsupported when the session's runtime predates resumable imports (use /workspace/import). Path parameters: - `id` (string, required) — The session id - `upload_id` (string, required) Responses: - `200` — OK - `id` (string) - `name` (string) - `size` (integer) - `sha256` (string) - `part_size` (integer) - `parts` (integer) — how many parts the upload has - `received` (integer[]) — part numbers already stored - `state` (enum) — "uploading" · "verified" · "importing" · "imported" · "failed" - `path` (string) — where the folder landed, once imported - `files` (integer) — files unpacked, once imported - `error` (object) - `code` (string) - `message` (string) - `202` — Importing - `id` (string) - `name` (string) - `size` (integer) - `sha256` (string) - `part_size` (integer) - `parts` (integer) — how many parts the upload has - `received` (integer[]) — part numbers already stored - `state` (enum) — "uploading" · "verified" · "importing" · "imported" · "failed" - `path` (string) — where the folder landed, once imported - `files` (integer) — files unpacked, once imported - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/imports/upload_id/complete \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/workspace/patch The work in one workspace repo since its base branch, as a git patch (scope: write) Runs inside the pod as the workspace owner and returns one binary-safe `git diff --binary` from the merge base of HEAD with the branch's upstream (or the remote's default branch, or else the base an imported folder recorded, with base_branch "") to the working tree: commits, uncommitted edits and new files that are not ignored. The repository's own index is left as it was. Wakes a parked pod. 400 not_a_repository, 409 no_base when there is no remote branch to compare against, 413 patch_too_large over 5 MB. Path parameters: - `id` (string, required) — The session id Body (application/json): - `repo` (string, required) — repo directory relative to the workspace root, as /workspace/changes lists it; "." for the root Responses: - `200` — OK - `repo` (string) - `base_branch` (string) — the branch the work started from, as the remote names it - `base_commit` (string) — the commit the patch applies to - `commits` (string[]) — subjects of the commits since the base, oldest first - `patch` (string) — base64 of the patch; empty when nothing changed - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/workspace/patch \ -H "Authorization: Bearer ast_..." ``` ## This Mac ### GET /desktop/machines The person's Macs connected through the Mac app (scope: read) Responses: - `200` — OK - `machines` (object[], required) - `id` (string, required) — mac_… - `name` (string, required) - `online` (boolean, required) — The Mac app polled within the last minute - `last_seen_at` (string, required) - `created_at` (string, required) - `reach` (object) — What the Mac app says it can lend now, from its last poll; null when the app is too old to say (it answers no reach requests) - `kinds` (enum[], required) — What a reach request may ask this Mac for now. host: the Mac app's own local Claude Code and Codex chats, call notes, uploads to workspace files, saves to Downloads, local git (apply a session's changes, open a pull request), WeChat reader and local sync; every change through it is confirmed on the Mac per call — "folder" · "browser" · "screen" · "host" - `screen` (enum, required) — off: the person has not turned on lending the screen; needs_permission: macOS has not granted AgentSky Screen Recording or Accessibility (only the person can, in System Settings) — "ready" · "off" · "needs_permission" · "unavailable" - `mounts` (object[]) — The live folders, browser windows, screens and host reaches (the Mac's apps and files) lent from this Mac. Personal keys only: a workspace key never sees what a person lent. Capability URLs are never listed. - `id` (string, required) — dmt_… - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `label` (string, required) - `root` (string) — The folder's absolute path on the Mac (folders only) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/desktop/machines' \ -H "Authorization: Bearer ast_..." ``` ### POST /desktop/machines Register this Mac (scope: write) The Mac app registers the Mac it runs on and is handed its connect token once. The token is what the app polls the relay with; the person's own token never reaches the app. Edge-routed to the chat-proxy like the ports routes. Body (application/json): - `name` (string, required) — What the person calls this Mac Responses: - `201` — Registered - `machine` (object, required) - `id` (string, required) — mac_… - `name` (string, required) - `online` (boolean, required) — The Mac app polled within the last minute - `last_seen_at` (string, required) - `created_at` (string, required) - `reach` (object) — What the Mac app says it can lend now, from its last poll; null when the app is too old to say (it answers no reach requests) - `kinds` (enum[], required) — What a reach request may ask this Mac for now. host: the Mac app's own local Claude Code and Codex chats, call notes, uploads to workspace files, saves to Downloads, local git (apply a session's changes, open a pull request), WeChat reader and local sync; every change through it is confirmed on the Mac per call — "folder" · "browser" · "screen" · "host" - `screen` (enum, required) — off: the person has not turned on lending the screen; needs_permission: macOS has not granted AgentSky Screen Recording or Accessibility (only the person can, in System Settings) — "ready" · "off" · "needs_permission" · "unavailable" - `mounts` (object[]) — The live folders, browser windows, screens and host reaches (the Mac's apps and files) lent from this Mac. Personal keys only: a workspace key never sees what a person lent. Capability URLs are never listed. - `id` (string, required) — dmt_… - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `label` (string, required) - `root` (string) — The folder's absolute path on the Mac (folders only) - `created_at` (string, required) - `connect_token` (string, required) — dmc_… — shown once; the Mac app's only credential, stored hashed - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/desktop/machines \ -H "Authorization: Bearer ast_..." ``` ### POST /desktop/machines/{id}/reach-requests Ask one of your Macs to lend a folder, a browser window, its screen or its apps and files (scope: write) The person's own key only (a workspace key answers 403). The ask reaches the Mac app on its next poll; the person answers there — the folder picker (they choose the folder; Cancel refuses) or Allow / Deny for the browser, the screen and `host` (the Mac app's local chats, call notes, uploads, saves to Downloads and local git; once granted its reads answer at once and each change asks the person again). An agent can ask; only the person grants. Answers 202 with the request and the capability URL the mount will carry; poll `GET /desktop/reach-requests/{id}` for the answer. Refused at once with 409: machine_offline (the app is not polling), reach_unsupported (the app is too old, or cannot lend that kind), screen_reach_off, screen_permissions_missing (only the person can grant Screen Recording and Accessibility in System Settings), reach_request_open (one ask per Mac at a time). Path parameters: - `id` (string, required) Body (application/json): - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `reason` (string, required) — Why, in one sentence; shown to the person on the Mac, quoted as the agent's words — max length 280 Responses: - `202` — Asked - `reach_request` (object, required) - `id` (string, required) — drr_… - `machine_id` (string, required) - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `reason` (string, required) - `status` (enum, required) — pending: waiting for the Mac's next poll; asking: the person is being asked; granted: the mount below is live — "pending" · "asking" · "granted" · "denied" · "unavailable" · "expired" - `mount` (object, required) — The granted mount while it is live - `id` (string, required) — dmt_… - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `label` (string, required) - `root` (string) — The folder's absolute path on the Mac (folders only) - `created_at` (string, required) - `error` (object, required) - `code` (enum, required) — "reach_denied" · "screen_reach_off" · "screen_permissions_missing" · "reach_unavailable" · "reach_timeout" - `message` (string, required) - `created_at` (string, required) - `deadline_at` (string, required) — When an unanswered ask expires: a minute for the Mac to pick it up, then 150 seconds for the person - `mcp_url` (string, required) — https://mac-.agentsky.dev/mcp — shown once, stored hashed. Answers nothing until the person grants the ask; then attach it as a url MCP server (POST /sessions mcp_servers, MCP start_session sources, or PATCH /sessions/{id} mcp_servers) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/desktop/machines/sess_cmtb7q2j/reach-requests \ -H "Authorization: Bearer ast_..." ``` ### GET /desktop/reach-requests/{id} Where an ask for reach stands (scope: read) With `wait` (seconds, at most 25) the call returns as soon as the person answers. Another person's or a workspace key's read answers 404 or 403. Path parameters: - `id` (string, required) Query parameters: - `wait` (integer) Responses: - `200` — OK - `reach_request` (object, required) - `id` (string, required) — drr_… - `machine_id` (string, required) - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `reason` (string, required) - `status` (enum, required) — pending: waiting for the Mac's next poll; asking: the person is being asked; granted: the mount below is live — "pending" · "asking" · "granted" · "denied" · "unavailable" · "expired" - `mount` (object, required) — The granted mount while it is live - `id` (string, required) — dmt_… - `kind` (enum, required) — "folder" · "browser" · "screen" · "host" - `label` (string, required) - `root` (string) — The folder's absolute path on the Mac (folders only) - `created_at` (string, required) - `error` (object, required) - `code` (enum, required) — "reach_denied" · "screen_reach_off" · "screen_permissions_missing" · "reach_unavailable" · "reach_timeout" - `message` (string, required) - `created_at` (string, required) - `deadline_at` (string, required) — When an unanswered ask expires: a minute for the Mac to pick it up, then 150 seconds for the person - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/desktop/reach-requests/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### POST /desktop/reach-requests/{id}/response The Mac answers an ask it did not grant (scope: machine connect token) Sent by the Mac app with outcome denied, screen_reach_off, screen_permissions_missing or reach_unavailable. A grant is not answered here: the Mac creates the mount with `reach_request_id`. Path parameters: - `id` (string, required) Body (application/json): - `outcome` (enum, required) — "denied" · "screen_reach_off" · "screen_permissions_missing" · "reach_unavailable" - `detail` (string) — max length 300 Responses: - `204` — Answered - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/desktop/reach-requests/sess_cmtb7q2j/response \ -H "Authorization: Bearer ast_..." ``` ### DELETE /desktop/machines/{id} Revoke a Mac (scope: write) The app's connect token stops working and every folder it handed over answers 404 from then on. Path parameters: - `id` (string, required) Responses: - `204` — Revoked - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/desktop/machines/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /desktop/machines/{id}/mounts Hand a locally selected folder on that Mac to agents (scope: machine connect token) Answers the address a spec's `mcp_servers` entry carries (`{type: "url", name: "this-mac", url: mcp_url}`): a pod's MCP calls on it are relayed to the Mac app, which answers them with file tools rooted at the folder and holds every change for the person's confirmation. This route accepts only that Mac's connect token after its native picker returns a normalized absolute path; an account token cannot create a mount. A Mac that is not polling answers 503 machine_offline to the pod. Path parameters: - `id` (string, required) Body (application/json): - `root` (string, required) — The folder's absolute path on that Mac, "browser:" for the Mac app's browser window, "screen:" for that Mac's screen, or "host:" for the Mac app's own apps and files, each lent to one task - `label` (string, required) — What the thread shows for it - `reach_request_id` (string) — drr_… — the Mac grants that reach request: the mount carries the capability its caller was handed, and this answer's mcp_url is null Responses: - `201` — Mounted - `id` (string, required) — dmt_… - `machine_id` (string, required) - `root` (string, required) - `label` (string, required) - `mcp_url` (string, required) — https://mac-.agentsky.dev/mcp — put it in a spec's mcp_servers as a url server; the token is the credential. Null when the mount grants a reach request (its caller already holds it) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/desktop/machines/sess_cmtb7q2j/mounts \ -H "Authorization: Bearer ast_..." ``` ### DELETE /desktop/mounts/{id} Take a folder back (scope: write) Its address answers 404 from then on; a running session keeps its spec entry but every call to it fails. Path parameters: - `id` (string, required) Responses: - `204` — Revoked - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/desktop/mounts/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /desktop/mounts/{id}/verify Verify a live folder mount before starting agents (scope: read) Confirms that the mount id and capability URL still name the same live folder owned by this account in the token's universe, or the account's personal universe when the token is unscoped. A stale, revoked, foreign-universe or mismatched mount answers 404. Path parameters: - `id` (string, required) Body (application/json): - `mcp_url` (string, required) — The mount capability URL returned when the folder was selected Responses: - `204` — The mount is live and owned by this account - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/desktop/mounts/sess_cmtb7q2j/verify \ -H "Authorization: Bearer ast_..." ``` ## Routines ### GET /routines List routines (scope: read) Query parameters: - `cursor` (string) - `limit` (integer) — 1–500, default 50 Responses: - `200` — OK - `items` (object[], required) - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `cursor` (string | null) - `hasMore` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines' \ -H "Authorization: Bearer ast_..." ``` ### POST /routines Create a routine — scheduled agent execution (scope: write) initial_events (1–50 user.message events) are delivered on every fire. target picks the mode: new_session creates a fresh session per run; session sends the turn into an existing session (busy target → failed run session_busy_error). schedule is a 5-field POSIX cron with an IANA timezone; omit it for a manual-run-only routine. budget bounds each run separately. The answer carries effective_deliver_to: where each run's result goes (metadata created_in_session names the conversation the routine was made in, which receives results by default). Body (application/json): - `name` (string, required) — max length 256 - `description` (string) — max length 2048 - `metadata` (map) — max length 512 - `target` (object, required) - type: "new_session" (object) - `type` ("new_session", required) - `agent` (string, required) - `environment_id` (string) - `vault_ids` (string[]) - type: "session" (object) - `type` ("session", required) - `session_id` (string, required) - `initial_events` (object[], required) - `type` ("user.message", required) - `content` (object[], required) - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "image" (object) - `type` ("image", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - type: "document" (object) - `type` ("document", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "text" (object) - `type` ("text", required) - `media_type` ("text/plain") — default: "text/plain" - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - `title` (string | null) - `context` (string | null) - `interaction_mode` (enum | null) — "plan" · "build" - `schedule` (object | null) - `type` ("cron", required) - `expression` (string, required) — max length 256 - `timezone` (string, required) - `budget` (object | null) - `type` ("limit", required) - `max_list_cost` (object, required) - `amount` (string, required) — Whole US cents as an integer decimal string, e.g. "125" for $1.25 — max length 15 - `currency` ("USD", required) Responses: - `201` — Created - `routine` (object, required) — Scheduled agent execution: initial_events + a target (new_session per fire, or an existing session) bound to a cron schedule. Fires record routine runs. - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `effective_deliver_to` (object) — Where each run's result goes in effect. channel_thread: the chosen chat. session: a conversation — the one the routine runs in (source target) or the one it was made in (source origin, from metadata created_in_session or proposed_by_session). notification: no conversation. The owner is notified in every case. - `type` (enum, required) — "channel_thread" · "session" · "notification" - `connection_id` (string) - `thread_id` (string) - `session_id` (string) - `source` (enum) — "target" · "origin" - `notify_owner` (enum, required) — true - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"name":"Weekly compliance scan","target":{"type":"new_session","agent":"agent_cmtb2f0a8m9k7x51q4ez6r0d"},"initial_events":[{"type":"user.message","content":[{"type":"text","text":"Run the weekly compliance scan."}]}],"schedule":{"type":"cron","expression":"0 20 * * 5","timezone":"America/New_York"}}' ``` ### GET /routines/{id} Routine detail (scope: read) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `routine` (object, required) — Scheduled agent execution: initial_events + a target (new_session per fire, or an existing session) bound to a cron schedule. Fires record routine runs. - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /routines/{id} Update a routine — omit preserves; null clears schedule/budget (scope: write) initial_events is a full replacement; metadata is key-patched (null deletes a key); name and target cannot be cleared. Archived routines are immutable (409). Path parameters: - `id` (string, required) — The routine id (rtn_…) Body (application/json): - `name` (string) — max length 256 - `description` (string | null) — max length 2048 - `metadata` (map) - `target` (object) - type: "new_session" (object) - `type` ("new_session", required) - `agent` (string, required) - `environment_id` (string) - `vault_ids` (string[]) - type: "session" (object) - `type` ("session", required) - `session_id` (string, required) - `initial_events` (object[]) - `type` ("user.message", required) - `content` (object[], required) - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "image" (object) - `type` ("image", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - type: "document" (object) - `type` ("document", required) - `source` (object, required) - type: "base64" (object) - `type` ("base64", required) - `media_type` (string, required) - `data` (string, required) - type: "text" (object) - `type` ("text", required) - `media_type` ("text/plain") — default: "text/plain" - `data` (string, required) - type: "url" (object) - `type` ("url", required) - `url` (string, required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) - `title` (string | null) - `context` (string | null) - `interaction_mode` (enum | null) — "plan" · "build" - `schedule` (object | null) - `type` ("cron", required) - `expression` (string, required) — max length 256 - `timezone` (string, required) - `budget` (object | null) - `type` ("limit", required) - `max_list_cost` (object, required) - `amount` (string, required) — Whole US cents as an integer decimal string, e.g. "125" for $1.25 — max length 15 - `currency` ("USD", required) Responses: - `200` — OK - `routine` (object, required) — Scheduled agent execution: initial_events + a target (new_session per fire, or an existing session) bound to a cron schedule. Fires record routine runs. - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/routines/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"schedule":{"type":"cron","expression":"0 9 * * 1-5","timezone":"UTC"}}' ``` ### DELETE /routines/{id} Delete a routine — it never fires again and is no longer listed (scope: write) The routine is archived (no schedule, inbound URL or chat trigger fires it) and every read of it, its runs included, answers 404 from then on, so a second delete is 404 too. The sessions its runs started are kept. To stop a routine and keep it listed, archive it instead. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `204` — Deleted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/routines/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /routines/{id}/pause Pause — suppress scheduled fires; manual runs still work (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `routine` (object, required) — Scheduled agent execution: initial_events + a target (new_session per fire, or an existing session) bound to a cron schedule. Fires record routine runs. - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/pause \ -H "Authorization: Bearer ast_..." ``` ### POST /routines/{id}/unpause Unpause — resumes from the NEXT occurrence; missed fires are never backfilled (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `routine` (object, required) — Scheduled agent execution: initial_events + a target (new_session per fire, or an existing session) bound to a cron schedule. Fires record routine runs. - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/unpause \ -H "Authorization: Bearer ast_..." ``` ### POST /routines/{id}/archive Archive — terminal; the schedule stops and the routine becomes immutable (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `routine` (object, required) — Scheduled agent execution: initial_events + a target (new_session per fire, or an existing session) bound to a cron schedule. Fires record routine runs. - `id` (string) — rtn_… identifier - `name` (string) - `description` (string | null) - `metadata` (object) - `target` (object) — {type:"new_session", agent, environment_id?, vault_ids?} — a fresh session per fire (CMA parity) · {type:"session", session_id} — a turn into the existing session every fire (busy target skips with session_busy_error) - `initial_events` (object[]) - `schedule` (object | null) — 5-field POSIX cron + IANA timezone, wall-clock matching, minute granularity; null = manual-run only - `budget` (object | null) — Per-run cap (RFC-0080 shape): copied onto each new session (mode new_session) or granted as a per-fire delta allowance consumed+budget (mode session). Clearable. - `status` (enum) — "active" · "paused" - `paused_reason` (object | null) — {type:"manual"} | {type:"error", error:{type}, consecutive_failures, message?} — non-null exactly when paused. A routine pauses itself after 3 runs in a row fail with an error that the next run would repeat, insufficient_credits_error included (the wallet that pays for it is empty); add credit, then unpause. - `archived_at` (string | null) - `created_at` (string) - `updated_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/archive \ -H "Authorization: Bearer ast_..." ``` ### POST /routines/{id}/run Run now, outside the schedule; works while paused (scope: write) With no body it is a manual run: trigger_context {type:"manual"}. With `input` or `idempotency_key` it is an API-triggered run: trigger_context {type:"api"}; the input is added to the run's first message under the label "Input for this run:", and a repeated idempotency_key on the same routine returns the first run instead of starting another. Neither emits routine_run webhooks. Path parameters: - `id` (string, required) — The routine id (rtn_…) Body (application/json): - `input` (string) — What this run should work on, such as an event's payload — max length 50000 - `idempotency_key` (string) — The caller's id for the event; a repeat returns the first run — max length 200 Responses: - `201` — Created - `run` (object, required) — One fire attempt. Exactly one of session_id / error is non-null. - `id` (string) — rrun_… identifier - `routine_id` (string) - `trigger_context` (object) — {type:"schedule", scheduled_at} | {type:"manual"} - `session_id` (string | null) - `error` (object | null) — {type, message}. Types: agent_archived_error · agent_not_found_error · session_archived_error · session_not_found_error · session_busy_error · environment_archived_error · environment_not_found_error · vault_not_found_error · insufficient_credits_error · rate_limited_error · run_rejected_error · unknown_error. Unrecoverable types auto-pause the routine; transient ones (credits, rate limit, busy) leave it active for the next occurrence. precheck_unchanged is not a failure: the scheduled fire was skipped because the routine's precheck URL returned the same content as at the last run; no session started, the routine stays active and no routine_run webhook is sent. - `agent` (object) — Resolved agent reference at fire time - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/run \ -H "Authorization: Bearer ast_..." ``` ### GET /routines/{id}/inbound-url Whether the routine has an inbound URL (scope: read) `inbound_url` is null when there is none. The URL itself is never returned here, only the start of its secret. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `inbound_url` (object | null, required) - `routine_id` (string, required) - `prefix` (string, required) — The start of the secret in the URL - `created_at` (string, required) - `last_used_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/inbound-url' \ -H "Authorization: Bearer ast_..." ``` ### POST /routines/{id}/inbound-url Make the routine's inbound URL (scope: write) Returns a URL that starts a run of this routine when posted to, with no API token: for a service that can only call a URL. It replaces any earlier one, which stops working at once. The URL carries its own secret and is returned only in this response. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `201` — Created - `inbound_url` (object, required) - `routine_id` (string, required) - `prefix` (string, required) — The start of the secret in the URL - `created_at` (string, required) - `last_used_at` (string | null, required) - `url` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/inbound-url \ -H "Authorization: Bearer ast_..." ``` ### DELETE /routines/{id}/inbound-url Remove the routine's inbound URL (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `removed` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/inbound-url \ -H "Authorization: Bearer ast_..." ``` ### GET /routines/{id}/result-email Whether the routine emails its result (scope: read) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `result_email` (object, required) - `enabled` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/result-email' \ -H "Authorization: Bearer ast_..." ``` ### PUT /routines/{id}/result-email Email the routine's creator the result of each run (scope: write) When a run finishes, its creator gets one email with the last thing the agent said and a link to the run. It is sent whatever the person chose for finished-task emails, and every email carries a link that turns this routine's emails off. Only a routine whose target is `new_session` can have it (400 otherwise); an archived routine answers 409. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `result_email` (object, required) - `enabled` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/result-email \ -H "Authorization: Bearer ast_..." ``` ### DELETE /routines/{id}/result-email Stop emailing the routine's result (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `result_email` (object, required) - `enabled` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/result-email \ -H "Authorization: Bearer ast_..." ``` ### GET /routines/{id}/precheck The routine's precheck (scope: read) `precheck` is null when there is none. It carries what the last check before a scheduled fire found. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `precheck` (object | null, required) - `routine_id` (string, required) - `url` (string, required) - `created_at` (string, required) - `last_checked_at` (string | null, required) — When the URL was last checked before a scheduled fire - `last_result` (string | null, required) — changed: the fire ran. unchanged: the fire was skipped. failed: the check could not be completed and the fire ran anyway. — "changed" · "unchanged" · "failed" · null - `last_error` (object | null, required) — {type, message} when last_result is failed. Types: address_not_allowed · dns_failed · redirect · http_status · too_large · timeout · network. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/precheck' \ -H "Authorization: Bearer ast_..." ``` ### PUT /routines/{id}/precheck Run scheduled fires only when a URL's content changed (scope: write) Before each scheduled fire the platform GETs `url` and compares its content with the content at the last scheduled fire that ran. The same content skips the fire: it is recorded as a run with error.type `precheck_unchanged` and no session, and nothing is charged. New content, and the first check, run the fire. A check that cannot be completed (a redirect, a status outside 2xx, more than 256 KiB, more than 5 seconds, a host that is not on the public internet) never skips: the fire runs and the reason is kept in `last_error`. Manual runs, API runs and inbound-URL runs never consult it. The URL must be https on the default port, with no user name or password, and must not point at a private, loopback or link-local address (400 otherwise); it is fetched with no credentials. Setting a URL forgets what the earlier check remembered. An archived routine answers 409. Path parameters: - `id` (string, required) — The routine id (rtn_…) Body (application/json): - `url` (string, required) Responses: - `200` — OK - `precheck` (object, required) - `routine_id` (string, required) - `url` (string, required) - `created_at` (string, required) - `last_checked_at` (string | null, required) — When the URL was last checked before a scheduled fire - `last_result` (string | null, required) — changed: the fire ran. unchanged: the fire was skipped. failed: the check could not be completed and the fire ran anyway. — "changed" · "unchanged" · "failed" · null - `last_error` (object | null, required) — {type, message} when last_result is failed. Types: address_not_allowed · dns_failed · redirect · http_status · too_large · timeout · network. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/precheck \ -H "Authorization: Bearer ast_..." ``` ### DELETE /routines/{id}/precheck Remove the routine's precheck (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `removed` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/precheck \ -H "Authorization: Bearer ast_..." ``` ### GET /routines/{id}/channel-trigger The routine's chat trigger (scope: read) `channel_trigger` is null when there is none. `available` says whether messages of this workspace's connected chats are offered to routines in this deployment; where it is false a trigger can be stored and will never match. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `channel_trigger` (object | null, required) - `routine_id` (string, required) - `connection_id` (string, required) - `thread_id` (string | null, required) — The one conversation whose messages start a run; null for every conversation on the connection - `text_prefix` (string | null, required) — What a message has to start with to start a run; null for every message - `created_at` (string, required) - `last_matched_at` (string | null, required) - `available` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/channel-trigger' \ -H "Authorization: Bearer ast_..." ``` ### PUT /routines/{id}/channel-trigger Start the routine from a message in a connected chat (scope: write) A message starts a run when it is on the connection, in `thread_id` when one is given, and starts with `text_prefix` when one is given (leading mentions of the bot and letter case are ignored). Only messages from the person the routine belongs to start a run: the person who connected the chat, or a chat identity they linked to the same account. Anyone else's message is routed as if there were no trigger. The text after the prefix is added to the run's first message under the label "Input for this run:". The run is a task of the routine's creator (trigger_context {type:"channel", connection_id, thread_id}), and when its turn ends its last message is posted, once, in the conversation the message came from, with a link to the run. A message that started a run is sent nowhere else. One message starts at most one run; when several routines match, the one naming a thread and then the longest prefix wins. A paused or archived routine starts nothing, and more than 30 runs of one routine in a minute are dropped. Only the routine's creator can set the trigger (403 otherwise), on a connected chat of their own in this workspace (404 otherwise). `thread_id` must be a conversation on that connection and `text_prefix` 1 to 200 characters (400 otherwise). Setting a trigger replaces the earlier one. An archived routine answers 409. Path parameters: - `id` (string, required) — The routine id (rtn_…) Body (application/json): - `connection_id` (string, required) - `thread_id` (string) - `text_prefix` (string) Responses: - `200` — OK - `channel_trigger` (object, required) - `routine_id` (string, required) - `connection_id` (string, required) - `thread_id` (string | null, required) — The one conversation whose messages start a run; null for every conversation on the connection - `text_prefix` (string | null, required) — What a message has to start with to start a run; null for every message - `created_at` (string, required) - `last_matched_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/channel-trigger \ -H "Authorization: Bearer ast_..." ``` ### DELETE /routines/{id}/channel-trigger Remove the routine's chat trigger (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `removed` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/channel-trigger \ -H "Authorization: Bearer ast_..." ``` ### GET /routines/{id}/deliver-to Where the routine's runs post their answer (scope: read) `deliver_to` is the chat the owner chose, or null. `effective_deliver_to` is where results go in effect: the chosen chat, else the conversation the routine runs in or was made in, else the owner notification alone. Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `deliver_to` (object | null, required) - `routine_id` (string, required) - `type` (enum, required) — "channel_thread" - `connection_id` (string, required) - `thread_id` (string, required) - `created_at` (string, required) - `effective_deliver_to` (object, required) — Where each run's result goes in effect. channel_thread: the chosen chat. session: a conversation — the one the routine runs in (source target) or the one it was made in (source origin, from metadata created_in_session or proposed_by_session). notification: no conversation. The owner is notified in every case. - `type` (enum, required) — "channel_thread" · "session" · "notification" - `connection_id` (string) - `thread_id` (string) - `session_id` (string) - `source` (enum) — "target" · "origin" - `notify_owner` (enum, required) — true - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/deliver-to' \ -H "Authorization: Bearer ast_..." ``` ### PUT /routines/{id}/deliver-to Post each run's answer to a conversation in a connected chat (scope: write) When a turn of a run's session ends and the run is not waiting on its owner, the last thing the agent said (at most 3,000 characters) is posted in the conversation with a link to the run, once per run. `thread_id` defaults to the chat itself. A run a chat message started answers where that message was instead. Only the routine's creator can set this (403 otherwise), on a connected chat of their own in this workspace (404 otherwise). An archived routine answers 409. Path parameters: - `id` (string, required) — The routine id (rtn_…) Body (application/json): - `type` (enum, required) — "channel_thread" - `connection_id` (string, required) - `thread_id` (string) Responses: - `200` — OK - `deliver_to` (object, required) - `routine_id` (string, required) - `type` (enum, required) — "channel_thread" - `connection_id` (string, required) - `thread_id` (string, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/deliver-to \ -H "Authorization: Bearer ast_..." ``` ### DELETE /routines/{id}/deliver-to Stop posting the routine's answers to a conversation (scope: write) Path parameters: - `id` (string, required) — The routine id (rtn_…) Responses: - `200` — OK - `removed` (boolean, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/routines/sess_cmtb7q2j/deliver-to \ -H "Authorization: Bearer ast_..." ``` ### POST /routines/inbound/{token} Start a run through the routine's inbound URL No Authorization header: the secret in the path is the credential. The request body, in any format and up to 50,000 characters, is added to the run's first message under the label "Input for this run:"; an empty body starts a run with no input. An `Idempotency-Key` header makes a repeated delivery return the first run. A paused routine answers 409 and starts nothing, an unknown URL or an archived routine answers 404, and more than 30 runs of one routine in a minute answers 429. The run is a task of the routine's owner (trigger_context {type:"api"}). Path parameters: - `token` (string, required) — The secret part of the inbound URL (rin_…) Responses: - `202` — Accepted - `run` (object, required) - `id` (string, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/routines/inbound/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0 \ -H "Authorization: Bearer ast_..." ``` ### GET /routine-runs List routine runs — every fire attempt (scope: read) Query parameters: - `routine` (string) — Filter to one routine (rtn_…) - `has_error` (boolean) — true = failed runs, false = runs with a session - `trigger_type` (string) - `created_at[gte]` (string) — Also gt / lt / lte - `cursor` (string) - `limit` (integer) Responses: - `200` — OK - `items` (object[], required) - `id` (string) — rrun_… identifier - `routine_id` (string) - `trigger_context` (object) — {type:"schedule", scheduled_at} | {type:"manual"} - `session_id` (string | null) - `error` (object | null) — {type, message}. Types: agent_archived_error · agent_not_found_error · session_archived_error · session_not_found_error · session_busy_error · environment_archived_error · environment_not_found_error · vault_not_found_error · insufficient_credits_error · rate_limited_error · run_rejected_error · unknown_error. Unrecoverable types auto-pause the routine; transient ones (credits, rate limit, busy) leave it active for the next occurrence. precheck_unchanged is not a failure: the scheduled fire was skipped because the routine's precheck URL returned the same content as at the last run; no session started, the routine stays active and no routine_run webhook is sent. - `agent` (object) — Resolved agent reference at fire time - `created_at` (string) - `cursor` (string | null) - `hasMore` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routine-runs' \ -H "Authorization: Bearer ast_..." ``` ### GET /routine-runs/{id} Routine run detail (scope: read) Path parameters: - `id` (string, required) — The run id (rrun_…) Responses: - `200` — OK - `run` (object, required) — One fire attempt. Exactly one of session_id / error is non-null. - `id` (string) — rrun_… identifier - `routine_id` (string) - `trigger_context` (object) — {type:"schedule", scheduled_at} | {type:"manual"} - `session_id` (string | null) - `error` (object | null) — {type, message}. Types: agent_archived_error · agent_not_found_error · session_archived_error · session_not_found_error · session_busy_error · environment_archived_error · environment_not_found_error · vault_not_found_error · insufficient_credits_error · rate_limited_error · run_rejected_error · unknown_error. Unrecoverable types auto-pause the routine; transient ones (credits, rate limit, busy) leave it active for the next occurrence. precheck_unchanged is not a failure: the scheduled fire was skipped because the routine's precheck URL returned the same content as at the last run; no session started, the routine stays active and no routine_run webhook is sent. - `agent` (object) — Resolved agent reference at fire time - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/routine-runs/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ## Model subscriptions ### GET /model-subscriptions List connected model subscriptions — metadata only, never credentials (scope: read) Query parameters: - `accounts` (string) — Include additional accounts; omitted preserves the legacy provider-only list Responses: - `200` — OK - `modelSubscriptions` (object[], required) - `provider` (enum) — "anthropic" · "openai" · "cursor" · "xai" · "meta" - `accountId` (string) — Additional account identifier; omitted for the legacy provider account - `label` (string) - `status` (enum) — connected runs agents; needs_reconnect lost its credential; org_disabled is signed in to an organization that turned off plan access for this harness, so nothing runs on it until it is reconnected — "connected" · "needs_reconnect" · "org_disabled" - `expiresAt` (string | null) - `lastUsedAt` (string | null) - `useForNewAgents` (boolean) - `createdAt` (string) - `agents` (object[]) — Agents in this universe running on the subscription - `id` (string) - `agentId` (string | null) - `name` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/model-subscriptions' \ -H "Authorization: Bearer ast_..." ``` ### POST /model-subscriptions Add a separate subscription account (scope: write) Body (application/json): - `credential` (string, required) — max length 100000 - `label` (string) — max length 60 - `provider` (enum, required) — "anthropic" · "openai" · "cursor" · "xai" · "meta" Responses: - `201` — Created - `modelSubscription` (object, required) — A connected consumer AI subscription (Claude Pro/Max, ChatGPT, Cursor, Grok or Muse plan) powering eligible agents at $0 model usage. The credential itself is write-only. - `provider` (enum) — "anthropic" · "openai" · "cursor" · "xai" · "meta" - `accountId` (string) — Additional account identifier; omitted for the legacy provider account - `label` (string) - `status` (enum) — connected runs agents; needs_reconnect lost its credential; org_disabled is signed in to an organization that turned off plan access for this harness, so nothing runs on it until it is reconnected — "connected" · "needs_reconnect" · "org_disabled" - `expiresAt` (string | null) - `lastUsedAt` (string | null) - `useForNewAgents` (boolean) - `createdAt` (string) - `agents` (object[]) — Agents in this universe running on the subscription - `id` (string) - `agentId` (string | null) - `name` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/model-subscriptions \ -H "Authorization: Bearer ast_..." ``` ### PUT /model-subscriptions/{provider} Connect or replace a subscription credential (write-only) (scope: write) anthropic: the sk-ant-oat… token from `claude setup-token`. openai: the contents of ~/.codex/auth.json from `codex login`. cursor: a Cursor API key, or the sign-in file ~/.config/cursor/auth.json from `cursor-agent login`. xai: the sign-in file ~/.grok/auth.json from `grok login`. meta: the sign-in file ~/.config/muse/auth.json from `muse login`. Eligible agents can then run at $0 model usage; with useForNewAgents (default on), new eligible Claude, ChatGPT, Grok and Muse agents use it automatically. Cursor agents use it only when created or switched with modelBilling `account`. Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription Query parameters: - `accountId` (string) — Target an additional account; omitted targets the legacy provider account Body (application/json): - `credential` (string, required) — max length 100000 - `label` (string) — max length 60 Responses: - `200` — OK - `modelSubscription` (object, required) — A connected consumer AI subscription (Claude Pro/Max, ChatGPT, Cursor, Grok or Muse plan) powering eligible agents at $0 model usage. The credential itself is write-only. - `provider` (enum) — "anthropic" · "openai" · "cursor" · "xai" · "meta" - `accountId` (string) — Additional account identifier; omitted for the legacy provider account - `label` (string) - `status` (enum) — connected runs agents; needs_reconnect lost its credential; org_disabled is signed in to an organization that turned off plan access for this harness, so nothing runs on it until it is reconnected — "connected" · "needs_reconnect" · "org_disabled" - `expiresAt` (string | null) - `lastUsedAt` (string | null) - `useForNewAgents` (boolean) - `createdAt` (string) - `agents` (object[]) — Agents in this universe running on the subscription - `id` (string) - `agentId` (string | null) - `name` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/model-subscriptions/provider \ -H "Authorization: Bearer ast_..." ``` ### PATCH /model-subscriptions/{provider} Update useForNewAgents / label (scope: write) Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription Query parameters: - `accountId` (string) — Target an additional account; omitted targets the legacy provider account Body (application/json): - `useForNewAgents` (boolean) - `label` (string) — max length 60 Responses: - `200` — OK - `modelSubscription` (object, required) — A connected consumer AI subscription (Claude Pro/Max, ChatGPT, Cursor, Grok or Muse plan) powering eligible agents at $0 model usage. The credential itself is write-only. - `provider` (enum) — "anthropic" · "openai" · "cursor" · "xai" · "meta" - `accountId` (string) — Additional account identifier; omitted for the legacy provider account - `label` (string) - `status` (enum) — connected runs agents; needs_reconnect lost its credential; org_disabled is signed in to an organization that turned off plan access for this harness, so nothing runs on it until it is reconnected — "connected" · "needs_reconnect" · "org_disabled" - `expiresAt` (string | null) - `lastUsedAt` (string | null) - `useForNewAgents` (boolean) - `createdAt` (string) - `agents` (object[]) — Agents in this universe running on the subscription - `id` (string) - `agentId` (string | null) - `name` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/model-subscriptions/provider \ -H "Authorization: Bearer ast_..." ``` ### DELETE /model-subscriptions/{provider} Disconnect — affected agents keep their pointer and fail until reconnected or switched (scope: write) Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription Query parameters: - `accountId` (string) — Target an additional account; omitted targets the legacy provider account Responses: - `200` — OK - `affectedAgents` (object[]) - `id` (string) - `agentId` (string | null) - `name` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/model-subscriptions/provider \ -H "Authorization: Bearer ast_..." ``` ### POST /model-subscriptions/{provider}/logins Start a browser sign-in to a model plan (scope: write) The provider's own CLI signs in inside a short-lived sandbox; open the returned `url`, then poll GET …/logins/{loginId} until `connected`. accountId `new` adds a separate account; omitted signs in the legacy provider account. Five starts per member per 10 minutes. Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription Body (application/json): - `accountId` (string) - `label` (string) — max length 60 Responses: - `201` — Created - `login` (object, required) — A browser sign-in in progress. starting: the sandbox is booting, poll again. waiting: open `url` (and, when `acceptsCode`, submit the code the provider's page shows). connected: the subscription is stored. cleanup_pending: stored, but the sandbox has not stopped yet; poll again or DELETE. failed: start again. - `state` (enum, required) — "starting" · "waiting" · "connected" · "cleanup_pending" · "failed" - `loginId` (string) — Opaque handle bound to the caller, the workspace and the provider; valid for 16 minutes - `url` (string) — The provider's sign-in page (waiting) - `userCode` (string | null) — A code to confirm on the provider's page, when it shows one - `acceptsCode` (boolean) — The provider's page shows a code to submit to /code - `subscription` (object) — A connected consumer AI subscription (Claude Pro/Max, ChatGPT, Cursor, Grok or Muse plan) powering eligible agents at $0 model usage. The credential itself is write-only. - `provider` (enum) — "anthropic" · "openai" · "cursor" · "xai" · "meta" - `accountId` (string) — Additional account identifier; omitted for the legacy provider account - `label` (string) - `status` (enum) — connected runs agents; needs_reconnect lost its credential; org_disabled is signed in to an organization that turned off plan access for this harness, so nothing runs on it until it is reconnected — "connected" · "needs_reconnect" · "org_disabled" - `expiresAt` (string | null) - `lastUsedAt` (string | null) - `useForNewAgents` (boolean) - `createdAt` (string) - `agents` (object[]) — Agents in this universe running on the subscription - `id` (string) - `agentId` (string | null) - `name` (string) - `reason` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/model-subscriptions/provider/logins \ -H "Authorization: Bearer ast_..." ``` ### GET /model-subscriptions/{provider}/logins/{loginId} Advance a browser sign-in; stores the subscription when it finishes (scope: write) Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription - `loginId` (string, required) — The handle a sign-in start returned Responses: - `200` — OK - `login` (object, required) — A browser sign-in in progress. starting: the sandbox is booting, poll again. waiting: open `url` (and, when `acceptsCode`, submit the code the provider's page shows). connected: the subscription is stored. cleanup_pending: stored, but the sandbox has not stopped yet; poll again or DELETE. failed: start again. - `state` (enum, required) — "starting" · "waiting" · "connected" · "cleanup_pending" · "failed" - `loginId` (string) — Opaque handle bound to the caller, the workspace and the provider; valid for 16 minutes - `url` (string) — The provider's sign-in page (waiting) - `userCode` (string | null) — A code to confirm on the provider's page, when it shows one - `acceptsCode` (boolean) — The provider's page shows a code to submit to /code - `subscription` (object) — A connected consumer AI subscription (Claude Pro/Max, ChatGPT, Cursor, Grok or Muse plan) powering eligible agents at $0 model usage. The credential itself is write-only. - `provider` (enum) — "anthropic" · "openai" · "cursor" · "xai" · "meta" - `accountId` (string) — Additional account identifier; omitted for the legacy provider account - `label` (string) - `status` (enum) — connected runs agents; needs_reconnect lost its credential; org_disabled is signed in to an organization that turned off plan access for this harness, so nothing runs on it until it is reconnected — "connected" · "needs_reconnect" · "org_disabled" - `expiresAt` (string | null) - `lastUsedAt` (string | null) - `useForNewAgents` (boolean) - `createdAt` (string) - `agents` (object[]) — Agents in this universe running on the subscription - `id` (string) - `agentId` (string | null) - `name` (string) - `reason` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/model-subscriptions/provider/logins/loginId' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /model-subscriptions/{provider}/logins/{loginId} Abandon a browser sign-in and free its sandbox (scope: write) Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription - `loginId` (string, required) — The handle a sign-in start returned Responses: - `204` — Abandoned - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/model-subscriptions/provider/logins/loginId \ -H "Authorization: Bearer ast_..." ``` ### POST /model-subscriptions/{provider}/logins/{loginId}/code Submit the code the provider's sign-in page shows (scope: write) Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription - `loginId` (string, required) — The handle a sign-in start returned Body (application/json): - `code` (string, required) — max length 1024 Responses: - `204` — Handed to the waiting CLI - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/model-subscriptions/provider/logins/loginId/code \ -H "Authorization: Bearer ast_..." ``` ### GET /model-subscriptions/{provider}/usage Plan allowance left on a connected account (scope: read) Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription Query parameters: - `accountId` (string) — Target an additional account; omitted targets the legacy provider account Responses: - `200` — OK - `usage` (object, required) — The plan allowance left on a connected account. `unavailable` with a reason when the provider cannot be read. - `checkedAt` (string) - `status` (enum) — "known" · "unavailable" - `reason` (string | null) — "unsupported" · "sign_in" · "token_expired" · "permission" · "temporarily_unavailable" · null - `plan` (string | null) - `allowed` (boolean | null) — false when a window is used up - `windows` (object[]) - `label` (string) - `remainingPercent` (number) - `resetsAt` (string | null) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/model-subscriptions/provider/usage' \ -H "Authorization: Bearer ast_..." ``` ### POST /model-subscriptions/{provider}/switch-to-credits Move the agents on a subscription account onto platform credits (scope: write) Run before disconnecting an account so its agents keep working. Restarts their running sessions. Path parameters: - `provider` (string, required) — anthropic = Claude subscription · openai = ChatGPT plan · cursor = Cursor plan · xai = Grok subscription · meta = Muse subscription Query parameters: - `accountId` (string) — Target an additional account; omitted targets the legacy provider account Responses: - `200` — OK - `switched` (integer) - `restartFailures` (string[]) — Agents switched to credits whose running session could not be restarted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/model-subscriptions/provider/switch-to-credits \ -H "Authorization: Bearer ast_..." ``` ## External accounts ### GET /external_accounts List the workspace's accounts at outside vendors — never credentials (scope: read) Vendor accounts connected here, and the workspace's model plans (managed under /model-subscriptions; their ids start with `plan:`). `vendors` lists the vendors an account can be connected for and whether connecting takes one `key` or the vendor's full `settings`. 404 where external accounts are not turned on for the workspace. Responses: - `200` — OK - `external_accounts` (object[], required) - `id` (string, required) - `kind` (enum, required) — "model_subscription" · "vendor_account" - `vendor` (string, required) - `vendor_name` (string, required) - `label` (string, required) - `status` (enum, required) — "active" · "needs_reconnect" · "exhausted" - `plan` (string | null, required) — The plan the vendor reported, when it reports one - `agents` (object[], required) — The agents that have run on it - `id` (string, required) - `name` (string, required) - `last_used_at` (string | null, required) - `created_at` (string, required) - `vendors` (object[], required) - `id` (string, required) - `name` (string, required) - `credential` (enum, required) — "key" · "settings" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/external_accounts' \ -H "Authorization: Bearer ast_..." ``` ### POST /external_accounts Connect a vendor account, or replace the credential of the one with the same vendor and label (scope: write) The credential is write-only. Every task of the workspace waiting on this vendor's account (an open `connect_account` request) then continues. 422 `invalid_credential` when the vendor does not accept it; 503 `not_migrated` before the store exists. A vendor's own browser sign-in is not offered here. Body (application/json): - `vendor` (string, required) — A vendor id from `vendors` - `label` (string) — Defaults to the vendor's name - `credential` (string | map, required) — One key, or the vendor's account settings as upper-case names with text values - option 1 (string) - option 2 (map) Responses: - `201` — Created - `external_account` (object, required) - `id` (string, required) - `kind` (enum, required) — "model_subscription" · "vendor_account" - `vendor` (string, required) - `vendor_name` (string, required) - `label` (string, required) - `status` (enum, required) — "active" · "needs_reconnect" · "exhausted" - `plan` (string | null, required) — The plan the vendor reported, when it reports one - `agents` (object[], required) — The agents that have run on it - `id` (string, required) - `name` (string, required) - `last_used_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/external_accounts \ -H "Authorization: Bearer ast_..." ``` ### DELETE /external_accounts/{id} Disconnect an account; a later task that needs it asks for it again (scope: write) Path parameters: - `id` (string, required) — The account's id from the list Responses: - `204` — Disconnected - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/external_accounts/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /external_accounts/{id}/usage Which agents and tasks ran on a vendor account (scope: read) Path parameters: - `id` (string, required) — The account's id from the list Responses: - `200` — OK - `usage` (object, required) - `agents` (object[], required) - `id` (string, required) - `name` (string, required) - `tasks` (object[], required) - `session_id` (string, required) - `agent_id` (string | null, required) - `title` (string | null, required) - `first_used_at` (string, required) - `last_used_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/external_accounts/sess_cmtb7q2j/usage' \ -H "Authorization: Bearer ast_..." ``` ## Connectors ### GET /shared-connections Everything you share with a workspace, and the calls made through each (scope: read) Responses: - `200` — OK - `shared` (object[], required) - `connection_id` (string) - `app` (string) - `app_name` (string) - `label` (string) - `workspace` (object) - `slug` (string) - `name` (string) - `shared_at` (string) - `use_count` (integer) - `uses` (object[]) - `session_id` (string) - `started_by` (object) - `user_id` (string) - `name` (string) - `key` (object) — The workspace key that started the session, when one did. - `id` (string) - `name` (string) - `method` (string) - `host` (string) - `status` (integer) - `at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/shared-connections' \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/apps List the apps a person can connect (scope: read) One page of the catalog. With neither `q` nor `cursor` the page leads with the widely used apps. Pages vary in size: continue with `next_cursor` until it is null. Answers 502 `catalog_error` when the catalog cannot be read. Query parameters: - `q` (string) — Search by name - `cursor` (string) — A page's `next_cursor` Responses: - `200` — OK - `apps` (object[], required) - `slug` (string, required) — The app's id, used wherever an app is named. - `name` (string, required) - `description` (string, required) - `next_cursor` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/apps' \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/connections List the caller's connections (scope: read) The accounts the caller connected, by app and then oldest first. Connections belong to the person, not to a workspace. `credit` is what the caller has earned by connecting apps and the most they can earn that way. With `granted=true` the answer is instead `{connections: GrantedConnection[]}`: the connections other people let this credential call through with POST /connectors/proxy — those granted to the selected workspace as a whole, plus those granted to this very key. Query parameters: - `granted` (string) — List what this credential may call through instead of the caller's own connections. Responses: - `200` — OK - `connections` (object[], required) - `id` (string, required) - `app` (string, required) — The app's slug. - `app_name` (string, required) - `label` (string, required) — The account's name at the app, usually an email address. - `created_at` (string, required) - `credit` (object, required) - `earned_usd` (number, required) - `cap_usd` (number, required) - `granted_apps` (string[], required) — Slugs of the apps that earned credit. - `eligible_apps` (string[], required) — Slugs of the apps whose connection can earn credit. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/connections' \ -H "Authorization: Bearer ast_..." ``` ### POST /connectors/connections Start connecting an app (scope: write) Answers the address the person's browser opens to sign in to the app and allow access. Afterwards the browser is sent to `success_url`, with `account_id` added to its query, or to `error_url`; the caller then records the result with `POST /connectors/connections/complete`. Both addresses must be https (http is accepted for localhost) and on one origin. Answers 400 `unknown_app` for an app that cannot be connected and 502 `connect_error` when the authorization cannot be started. Body (application/json): - `app` (string, required) — The app's slug. - `success_url` (string, required) - `error_url` (string, required) Responses: - `200` — OK - `url` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/connectors/connections \ -H "Authorization: Bearer ast_..." ``` ### POST /connectors/connections/complete Record a finished authorization (scope: write) Call this when the browser arrives at `success_url`. The account is checked against the caller's own accounts at the app's provider, so an account somebody else authorized is refused; without `account_id` the caller's newest account for the app is used. Every task of the caller that was waiting on the app continues. Answers 404 when the caller has no such account and 502 `connect_error` when the provider cannot be read. Body (application/json): - `app` (string, required) - `account_id` (string) Responses: - `200` — OK - `connection` (object, required) - `app` (string, required) - `app_name` (string, required) - `label` (string, required) - `credit_grant` (object, required) — Credit given for this connection, when connecting apps earns credit. `status` is granted, already_granted, cap_reached or ineligible. - `status` (string, required) - `amount_usd` (number) - `earned_usd` (number) - `cap_usd` (number) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/connectors/connections/complete \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/connections/{id} Get one of the caller's connections (scope: read) Returns the connection by id. Use this to confirm a connection is still live after OAuth completes, or to poll for it after POST /connectors/connections/complete returns its id. To wait for an OAuth flow to complete, poll GET /connectors/connections (the list) until the target app appears; the {id} endpoint is for confirming a known connection id. Path parameters: - `id` (string, required) Responses: - `200` — OK - `connection` (object, required) - `id` (string, required) - `appSlug` (string, required) - `appName` (string, required) - `label` (string, required) - `imgSrc` (string) - `createdAt` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/connections/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /connectors/connections/{id} Disconnect one of the caller's connections (scope: write) Agents stop being able to use the account at once. Answers 404 for a connection that is not the caller's. Path parameters: - `id` (string, required) Responses: - `204` — Disconnected - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/connectors/connections/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/connections/{id}/grants List who may call through one of the caller's connections with a key (scope: read) Every grant on the connection, workspace by workspace, with the calls made under each. 404 for a connection that is not the caller's. Path parameters: - `id` (string, required) Responses: - `200` — OK - `grants` (object[], required) - `id` (string, required) - `connection_id` (string, required) - `workspace` (object, required) - `slug` (string, required) - `name` (string, required) - `key` (object, required) — The one key the grant is for; null when every key of the workspace may use it. - `id` (string, required) - `name` (string, required) - `created_at` (string, required) - `use_count` (integer, required) — Calls made under this grant so far. - `uses` (object[], required) — The newest calls, at most 20. - `key_id` (string, required) - `method` (string, required) - `host` (string, required) - `status` (integer, required) - `at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/connections/sess_cmtb7q2j/grants' \ -H "Authorization: Bearer ast_..." ``` ### POST /connectors/connections/{id}/grants Let a workspace's keys call through one of the caller's connections (scope: write) The workspace is the one the request selected (X-Universe, or the credential's own). With `key_id`, only that one live key of the workspace. Granting again changes nothing. Only the connection's owner acting as themselves may grant: a workspace key, an app credential or a task pass answers 403, so no key can grant itself access. 404 for a connection that is not the caller's, or a key that is not a live key of the workspace. Sends `connector.granted` to the workspace's webhooks. Sessions in the workspace do not gain the connection: this is the service path, separate from sharing (POST /universes/{slug}/connections). Path parameters: - `id` (string, required) Body (application/json): - `key_id` (string) — One key of the workspace (its id from GET /universes/{slug}/keys or GET /api_keys). Omitted: every key of the workspace. Responses: - `201` — Created - `grant` (object, required) — Permission for a workspace's API keys, or one of them, to call through a connection with POST /connectors/proxy. Made and taken back only by the connection's owner. - `id` (string, required) - `connection_id` (string, required) - `workspace` (object, required) - `slug` (string, required) - `name` (string, required) - `key` (object, required) — The one key the grant is for; null when every key of the workspace may use it. - `id` (string, required) - `name` (string, required) - `created_at` (string, required) - `use_count` (integer, required) — Calls made under this grant so far. - `uses` (object[], required) — The newest calls, at most 20. - `key_id` (string, required) - `method` (string, required) - `host` (string, required) - `status` (integer, required) - `at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/connectors/connections/sess_cmtb7q2j/grants \ -H "Authorization: Bearer ast_..." ``` ### DELETE /connectors/connections/{id}/grants Take a grant back (scope: write) Same body as granting names the grant: the selected workspace and, with `key_id`, one key. The service loses the connection at once. 404 when there is no such grant. Sends `connector.revoked`. Path parameters: - `id` (string, required) Body (application/json): - `key_id` (string) — One key of the workspace (its id from GET /universes/{slug}/keys or GET /api_keys). Omitted: every key of the workspace. Responses: - `204` — Revoked - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/connectors/connections/sess_cmtb7q2j/grants \ -H "Authorization: Bearer ast_..." ``` ### POST /connectors/proxy Call a third-party API through a granted connection (scope: write) For services holding an API key: the same gateway sessions use through `actl connector call`. The connection must be granted to this key (or its workspace) by its owner; otherwise 404 `not_granted`. `url` must be https (400 `https_required`); `method` defaults to GET; `headers` and `body` are passed to the third party. The answer carries the third party's `status`, `ok` and `body` as text. The workspace's tool-call rules decide first: a block rule, or a require_approval rule (a service call has no thread to be approved in), answers 403 `capability_denied`; rules that cannot be read answer 503 `policy_unavailable`. A 2xx from the third party books one CONNECTOR_CALL to the key's account at the price every connector call carries; other statuses are not billed. The call is recorded for the connection's owner (host, method, status). 403 `key_required` for a browser session or a task pass; 502 `proxy_error` when the third party could not be reached. Body (application/json): - `connection_id` (string, required) - `url` (string, required) — The third-party API address, https. - `method` (string) - `headers` (map) - `body` (string) Responses: - `200` — The third party's answer - `status` (integer, required) - `ok` (boolean, required) - `body` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/connectors/proxy \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/github/repos List the repositories the caller's GitHub connection can reach (scope: read) Most recently pushed first, at most 100. Answers 404 `github_not_connected` when the caller has no GitHub connection and 409 `github_reconnect_required` when none of their connections can be used. Responses: - `200` — OK - `repos` (object[], required) - `full_name` (string, required) - `default_branch` (string, required) - `private` (boolean, required) - `pushed_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/github/repos' \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/github/pulls Find the open pull request for a branch through the caller's GitHub connection (scope: read) For a client that pushed `branch` to `repo` and opened the PR in a browser, without a signed-in GitHub CLI. Answers `{ pull: null }` when no PR from that branch of the repository itself is open, 404 `github_not_connected` and 409 `github_reconnect_required` as `GET /connectors/github/repos`. The token never leaves the platform. Query parameters: - `repo` (string, required) — owner/name - `branch` (string, required) Responses: - `200` — OK - `pull` (object, required) - `url` (string, required) - `number` (integer, required) - `title` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/github/pulls?repo=repo&branch=branch' \ -H "Authorization: Bearer ast_..." ``` ### POST /connectors/links Make a link to connect the app a task is waiting on (scope: write) For a task with an open `connect_app` request (see `GET /tasks/{id}`): a link the person the request is addressed to can open in any browser, without being signed in, to connect the app. Only that person can make it. The link can be used once, within fifteen minutes, and this is the only response that carries it; making another replaces one that was not used. When the app is connected the task continues by itself. Answers 404 when the task has no such open request, 403 `request_not_yours` when it is addressed to somebody else, and 503 `not_migrated` until the platform's link table is in place. Body (application/json): - `task_id` (string, required) — The task (sess_…) - `request_id` (string, required) Responses: - `201` — Created - `link` (object, required) - `url` (string, required) - `expires_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/connectors/links \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/links/{token} The page a connect link opens No Authorization header: the secret in the path is the credential. Answers a page that says what is asked, with a button to continue; looking uses nothing up. A link that is unknown, expired, already used, or whose request is no longer waiting answers the same 404 page. More than 10 requests for one link in a minute answers 429. Path parameters: - `token` (string, required) — The secret part of the link (cnl_…) Responses: - `200` — The page - `404` — The link cannot be used - `429` — Too many requests for this link - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0' \ -H "Authorization: Bearer ast_..." ``` ### POST /connectors/links/{token} Continue from a connect link to the app's authorization What the page's button sends. Uses the link up and redirects the browser to the app's sign-in. Refused exactly as the page is. Path parameters: - `token` (string, required) Responses: - `303` — Redirect to the app's authorization - `404` — The link cannot be used - `429` — Too many requests for this link - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/connectors/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0 \ -H "Authorization: Bearer ast_..." ``` ### GET /connectors/links/{token}/done Where the app's authorization returns for a connect link The browser arrives here after the person allows access. The connection is recorded for the person the link was made for, once, and the waiting task continues. Only a link that was used, within the last hour, is accepted; anything else answers the 404 page. Path parameters: - `token` (string, required) Query parameters: - `account_id` (string) Responses: - `200` — Connected - `404` — The link cannot be used - `502` — The app was not connected - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/connectors/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0/done' \ -H "Authorization: Bearer ast_..." ``` ## Workspace identity ### GET /workspace-identity What the identity in the Authorization header stands for (scope: identity) Only a session's workspace identity (Bearer wid_…, delivered to the pod beside each turn) may ask: its session, workspace, role, expiry, the route groups a token from the exchange may call, and the installed connectors with whether its role is granted each. Responses: - `200` — OK - `identity` (object, required) - `session_id` (string, required) - `agent_id` (string | null, required) - `workspace` (object, required) - `id` (string) - `slug` (string) - `name` (string) - `role` (enum, required) — "owner" · "admin" · "member" - `expires_at` (string, required) - `api_route_groups` (string[], required) — The /v1 route groups a token from the exchange may call - `connectors` (object[], required) - `kind` (string) - `name` (string) - `origin` (string) - `granted` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-identity' \ -H "Authorization: Bearer ast_..." ``` ### POST /workspace-identity/exchange Exchange a workspace identity for an API token or a connector credential (scope: identity) Bearer wid_…. {for: agentsky_api} mints a workspace-fixed ast_ token for the session's owner (read+write, one hour, never past the identity) that reaches the route groups the identity lists and nothing else. {for: connector, kind} attaches the vault the connector names for the identity's role to the session and pushes its network policy, then answers placeholders by environment variable name: export them and send requests to origin; the real credential is set at the network edge and never reaches the pod. Every exchange is recorded and revoked with the session. 404 connector_not_installed, 403 role_not_granted, 409 connector_misconfigured, 400 egress refusals, 503 connector_attach_failed. Body (application/json): - option 1 (object) - `for` ("agentsky_api", required) - option 2 (object) - `for` ("connector", required) - `kind` (string, required) — A short lowercase name for the service, e.g. crm — pattern ^[a-z][a-z0-9_-]{1,31}$ - `name` (string) — max length 80 Responses: - `201` — Created - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/workspace-identity/exchange \ -H "Authorization: Bearer ast_..." ``` ### GET /workspace-identity/exchanges The workspace's identity exchanges, newest first (scope: admin, owner or admin) Query parameters: - `limit` (integer) — 1–500, default 100 Responses: - `200` — OK - `exchanges` (object[], required) - `id` (string, required) - `session_id` (string, required) - `kind` (enum, required) — "agentsky_api" · "connector" - `role` (string, required) - `connector_id` (string | null, required) - `vault_id` (string | null, required) - `created_at` (string, required) - `revoked_at` (string | null, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-identity/exchanges' \ -H "Authorization: Bearer ast_..." ``` ### GET /workspace-connectors The services the workspace installed for its agents (scope: read) Responses: - `200` — OK - `connectors` (object[], required) - `id` (string, required) - `kind` (string, required) - `name` (string, required) - `origin` (string, required) - `role_vaults` (map, required) — Public vault ids by workspace role; never a value - `tools` (object[], required) - `name` (string, required) - `description` (string, required) - `example` (string | null, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-connectors' \ -H "Authorization: Bearer ast_..." ``` ### POST /workspace-connectors Install a connector: its origin, the vault per workspace role, and its tools (scope: admin, owner or admin) The credentials live in the named vaults as environment_variable secrets bound to the origin's host; nothing here stores or returns a value. At most 20 connectors per workspace; 409 on a repeated kind and name; 422 for a vault outside the workspace. Body (application/json): - `kind` (string, required) — A short lowercase name for the service, e.g. crm — pattern ^[a-z][a-z0-9_-]{1,31}$ - `name` (string) — Defaults to kind — max length 80 - `origin` (string, required) — https, public host; the host the connector's credentials are sent to - `role_vaults` (object, required) — The vault (vlt_…) each workspace role exchanges its identity for; at least one role - `owner` (string) - `admin` (string) - `member` (string) - `tools` (object[]) — What the service offers, shown to agents that ask what the workspace has - `name` (string, required) — max length 80 - `description` (string) — max length 400 - `example` (string) — max length 400 Responses: - `201` — Created - `connector` (object, required) - `id` (string, required) - `kind` (string, required) - `name` (string, required) - `origin` (string, required) - `role_vaults` (map, required) — Public vault ids by workspace role; never a value - `tools` (object[], required) - `name` (string, required) - `description` (string, required) - `example` (string | null, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/workspace-connectors \ -H "Authorization: Bearer ast_..." ``` ### GET /workspace-connectors/{id} One installed connector (scope: read) Path parameters: - `id` (string, required) Responses: - `200` — OK - `connector` (object, required) - `id` (string, required) - `kind` (string, required) - `name` (string, required) - `origin` (string, required) - `role_vaults` (map, required) — Public vault ids by workspace role; never a value - `tools` (object[], required) - `name` (string, required) - `description` (string, required) - `example` (string | null, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /workspace-connectors/{id} Uninstall a connector (scope: admin, owner or admin) Sessions that already exchanged for it keep what they were given until they end. Path parameters: - `id` (string, required) Responses: - `200` — OK - `removed` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /workspace-connectors/{id}/provision Mint one key per workspace role at the service and vault each (scope: admin, owner or admin) Through the service's own key route (POST {origin}/v1/keys) with the workspace's admin key, read from the named vault by the platform alone. Each key goes into a vault - as _API_KEY bound to the connector's host, which the connector then names for that role; a role that already had a key gets a new one, its secret is rotated in place so attached sessions follow, and the old key is revoked at the service. Behind CONNECTOR_KEY_MINTING (403 not_enabled otherwise). 404 unknown connector or admin vault, 422 admin_key_missing, 502 service_refused. Path parameters: - `id` (string, required) Body (application/json): - `admin_vault` (string, required) — The vault (vlt_…) holding the service's admin key; read by the platform alone - `admin_secret_name` (string) — The secret that is the admin key: an environment_variable secret by variable name, or a static_bearer secret by display name; default CRM_ADMIN_KEY - `roles` (object, required) — Per workspace role: the role the service gives the key, in the service's own vocabulary - `owner` (object) - `role` (string, required) - `see_money` (boolean) - `tools` (string[]) - `admin` (object) - `role` (string, required) - `see_money` (boolean) - `tools` (string[]) - `member` (object) - `role` (string, required) - `see_money` (boolean) - `tools` (string[]) - `deliver_as` (enum) — env (default): _API_KEY bound to the origin's host; mcp_bearer: a bearer for the service's MCP server at origin + mcp_path — "env" · "mcp_bearer" - `mcp_path` (string) — Default /mcp Responses: - `201` — Created - `connector_id` (string, required) - `minted` (map, required) — Per workspace role: the key's id and prefix at the service, when it was minted, and the vault and secret name that carry it; never the key - `not_revoked` (object[], required) — Earlier keys the service would not revoke; revoke them by hand - `role` (string) - `key_id` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j/provision \ -H "Authorization: Bearer ast_..." ``` ### POST /workspace-connectors/{id}/ingest/messages Relay a message envelope to the connector's service as the signed-in person (scope: write) {id} is a connector id or a kind (the oldest connector of that kind answers). The person's workspace role picks the connector's vault; the vault's credential (a _API_KEY environment secret, or a bearer secret for the service's host) goes on a POST to {origin}/v1/ingest/messages and never reaches the caller. The body is the service's message envelope, or an array of them, and passes through unchanged (JSON, at most 1 MiB); Idempotency-Key passes through, defaulting to the envelope's external_message_id or id, or for an array to a digest of its envelopes' ids. ?sync=true passes through (the CRM then ingests inline for at most 100 envelopes); otherwise the service may answer 202 {data:{inbox_id,count,status}} and ingest later, which GET ../ingest/inbox/{inbox_id} follows. The answer is the service's status and body as they were, with x-agentsky-relay naming the audit record. 404 connector_not_installed, 403 role_not_granted, 409 connector_misconfigured, 502 service_unreachable. A workspace identity cannot call this; it exchanges itself instead. Path parameters: - `id` (string, required) Query parameters: - `sync` (string) — Forwarded to the service: ingest inline and answer the counts Body (application/json): - option 1 (object) - option 2 (object[]) Responses: - `202` — The service's answer, as it was - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j/ingest/messages \ -H "Authorization: Bearer ast_..." ``` ### POST /workspace-connectors/{id}/ingest/meeting-notes Relay one finished meeting note to the connector's service as the signed-in person (scope: write) The Mac app's meeting-note.v1 facts for a recorded call (source, app, start and end, the matched calendar event's id and attendees, what each side said, the workspace file ids of the note), or the platform's for a settled cloud recording (source recall). Relayed exactly like ../ingest/messages — the person's workspace role picks the connector's vault, whose credential goes on POST {origin}/v1/ingest/meeting-notes and never reaches the caller — and the service decides what the note becomes (the CRM keeps only notes with a counterpart it knows and stores the transcript as a file). The body is one JSON object of at most 1 MiB; Idempotency-Key passes through, defaulting to meeting-note:, else the start time and app. The answer is the service's status and body as they were, with x-agentsky-relay naming the audit record. The same refusals as ../ingest/messages. Path parameters: - `id` (string, required) Body (application/json): Responses: - `200` — The service's answer, as it was - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j/ingest/meeting-notes \ -H "Authorization: Bearer ast_..." ``` ### GET /workspace-connectors/{id}/ingest/inbox/{inbox} Where a batch the connector's service queued (a 202 from ../ingest/messages) has got to (scope: read) Relayed as the signed-in person with the same vault credential to GET {origin}/v1/ingest/inbox/{inbox}; the service's status and body come back as they were: {data:{status: queued|processing|done|failed, created, existing, errors, last_error}}. The same refusals as the POST; 400 invalid_request for an inbox id that is not 1–120 letters, digits, '_' or '-'. Path parameters: - `id` (string, required) - `inbox` (string, required) — The inbox_id the service answered with Responses: - `200` — The service's answer, as it was - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j/ingest/inbox/inbox' \ -H "Authorization: Bearer ast_..." ``` ### GET /workspace-connectors/{id}/relays Requests the platform relayed to this connector's service, newest first (scope: admin, owner or admin) Path parameters: - `id` (string, required) Query parameters: - `limit` (integer) — 1–500, default 100 Responses: - `200` — OK - `relays` (object[], required) - `id` (string, required) - `connector_id` (string, required) - `vault_id` (string, required) - `user_id` (string, required) - `role` (string, required) - `path` (string, required) - `idempotency_key` (string | null, required) - `upstream_status` (integer | null, required) — The service's status; null when it could not be reached - `error` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-connectors/sess_cmtb7q2j/relays' \ -H "Authorization: Bearer ast_..." ``` ## Discovery ### GET /workspace-describe What this workspace has and what you may do here, with a usage line per item (scope: read) Agents, company skills, routines with their triggers, installed connectors with their tools, hook rules (policies), policy rules such as spend caps (policy_rules), remaining budget, and the caller's role, credential and route groups, read live. A workspace identity may ask before it exchanges itself. Responses: - `200` — OK - `workspace` (object, required) — What the caller's workspace has and what the caller may do there, each item with a one-line usage example - `workspace` (object, required) - `id` (string) - `slug` (string) - `name` (string) - `you` (object, required) - `role` (string | null) - `scopes` (string[]) - `credential` (string) - `route_groups` (array | null) - `example` (string) - `agents` (object[], required) - `id` (string) - `name` (string) - `display_name` (string) - `harness` (string) - `model` (string) - `example` (string) - `skills` (object[], required) - `id` (string) - `name` (string) - `attach_by_default` (boolean) - `example` (string) - `routines` (object[], required) - `id` (string) - `name` (string) - `status` (enum) — "active" · "paused" - `schedule` (string | null) - `triggers` (string[]) - `example` (string) - `connectors` (object[], required) - `id` (string) - `kind` (string) - `name` (string) - `origin` (string) - `granted_roles` (string[]) - `granted` (boolean | null) - `tools` (object[]) - `name` (string, required) - `description` (string, required) - `example` (string | null, required) - `example` (string) - `policies` (object[], required) — The workspace's hook rules (/universes/{slug}/hooks) - `point` (string) - `action` (string) - `enabled` (boolean) - `match` (object) - `policy_rules` (any[], required) — The workspace's policy rules (/universes/{slug}/policies): spend caps, tool-call and call-count limits and capability rules, disabled ones included - `budget` (object, required) - `credit_usd` (number | null) - `session_budget_usd` (number | null) - `session_spent_usd` (number | null) - `help` (object, required) - `example` (string) - `topics` (string[]) - `unavailable` (string[], required) — Sections that could not be read - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/workspace-describe' \ -H "Authorization: Bearer ast_..." ``` ### GET /help Help on a topic, generated from this API document (scope: read) Without topic: the topics (the first segment of every path, plus connectors/ and workflow). With one: its operations, each body's required fields and a skeleton, and one usage line. Aliases: start_session, trigger, identity, singular forms. 404 unknown_topic; 404 connector_not_installed for connectors/ the workspace lacks. Query parameters: - `topic` (string) Responses: - `200` — OK - `help` (object, required) - `topic` (string, required) - `usage` (string, required) — One request to adapt - `operations` (object[], required) - `method` (string) - `path` (string) - `summary` (string) - `description` (string | null) - `body` (object | null) - `connector` (object) - `kind` (string) - `name` (string) - `origin` (string) - `granted` (boolean | null) - `tools` (object[]) - `name` (string, required) - `description` (string, required) - `example` (string | null, required) - `notes` (string[], required) - `topics` (string[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/help' \ -H "Authorization: Bearer ast_..." ``` ## Webhooks ### GET /hook_rules The workspace's hook rules, in evaluation order (scope: read) `enabled` says whether hook rules are turned on for the workspace; where they are not the list is empty and nothing is evaluated. Responses: - `200` — OK - `enabled` (boolean, required) — Whether hook rules are turned on for the workspace. - `can_edit` (boolean, required) — Whether the caller may replace the rules: they are turned on and the caller owns the workspace. - `hook_rules` (object[], required) - `id` (string, required) — hkr_… - `point` (enum, required) — "session.start" · "tool.call" · "spend" · "turn.end" - `match` (map, required) - `action` (enum, required) — "notify" · "block" · "require_approval" - `webhook_id` (string | null, required) - `enabled` (boolean, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/hook_rules' \ -H "Authorization: Bearer ast_..." ``` ### PUT /hook_rules Replace the workspace's hook rules (owner only) (scope: write) The body is the whole ordered list (at most 20); the order is the order of evaluation. A rule sent with the `id` of an existing rule keeps it. Points: `session.start` (a session is about to be created), `tool.call` (a capability call, a call to a connected app, or a tool the gateway runs for a session reached the platform), `spend` (a session's consumed cost reached `match.amount`, whole US cents; read when a turn ends and at a capability call, once per session), `turn.end` (a turn ended). Actions: `notify` delivers a signed event (`hook.session_start`, `hook.tool_call`, `hook.spend`, `hook.turn_end`) to `webhook_id`, one of the workspace's webhook endpoints, whatever events that endpoint subscribed to; never a URL. `block` at `session.start` refuses the session with 403 `blocked_by_hook_rule`; at `tool.call` it refuses the call with `capability_denied` (`reason: hook_rule`). `require_approval` at `tool.call` asks the session's owner exactly as an `ask` capability policy does, and an answer covers one call. `require_approval` at `session.start` creates the session waiting: it has no sandbox and takes no turn (409 `start_not_approved`), and its task carries an `approval` request for the person who created it, answered `confirm` or `decline` through `POST /tasks/{id}/requests/{request_id}/answer`. Approved, it starts with what it was created with; declined, it is archived, never runs and is charged nothing. A session started in a way that cannot wait (a helper, a product run, a handoff) is refused with 403 `blocked_by_hook_rule`. The most restrictive of the matching rules and the capability policies wins. Refused with 400 and the reason: `block` and `require_approval` at `spend` and at `turn.end`. `match` narrows a rule: `{agent_id}` at `session.start` and `turn.end`, `{capability_id}` or `{connector_app}` (the slug of a connected app), not both, at `tool.call`; omitted, the rule applies to all. A notify rule that cannot be delivered never affects the session. 403 `not_enabled` where hook rules are not turned on; 503 `not_migrated` before the tables exist. Body (application/json): - `rules` (object[], required) - `id` (string) — An existing rule's id, to keep it. - `point` (enum, required) — "session.start" · "tool.call" · "spend" · "turn.end" - `action` (enum, required) — "notify" · "block" · "require_approval" - `match` (object) - `agent_id` (string) - `capability_id` (string) - `connector_app` (string) — The slug of a connected app, e.g. "slack". - `amount` (string) — Whole US cents, e.g. "125" for $1.25. Required at spend. - `webhook_id` (string) — Required for notify (whep_…); not allowed otherwise. - `enabled` (boolean) — Default true. Responses: - `200` — OK - `enabled` (boolean, required) — Whether hook rules are turned on for the workspace. - `can_edit` (boolean, required) — Whether the caller may replace the rules: they are turned on and the caller owns the workspace. - `hook_rules` (object[], required) - `id` (string, required) — hkr_… - `point` (enum, required) — "session.start" · "tool.call" · "spend" · "turn.end" - `match` (map, required) - `action` (enum, required) — "notify" · "block" · "require_approval" - `webhook_id` (string | null, required) - `enabled` (boolean, required) - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/hook_rules \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"rules":[{"point":"turn.end","action":"notify","webhook_id":"whep_cmtb2f0a8m9k7x51q4ez6r0d"},{"point":"tool.call","action":"require_approval","match":{"capability_id":"exa.search"}}]}' ``` ### GET /webhooks List webhook endpoints (scope: read) Responses: - `200` — OK - `webhooks` (object[], required) - `id` (string) — whep_… identifier - `url` (string) - `events` (string[]) - `status` (enum) — "active" · "disabled" - `failure_count` (integer) - `created_at` (string) - `secret` (string) — whsec_… — present ONLY in the create response - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/webhooks' \ -H "Authorization: Bearer ast_..." ``` ### POST /webhooks Register a webhook endpoint — the whsec_ secret appears only in this response (scope: write) The URL must be https, with no user name or password, and must not point at a private, loopback or link-local address (400 otherwise). At delivery the host is resolved again: a host that resolves to such an address, and a redirect, are failed attempts. Body (application/json): - `url` (string, required) — max length 2048 - `events` (string[], required) Responses: - `201` — Created - `webhook` (object, required) — Outbound webhook endpoint. Deliveries carry x-asteroids-event/-delivery/-timestamp/-signature (v1=hmac-sha256(secret, ".")), retry 3× (0s/5s/30s), and sustained failure disables (PATCH status:active re-enables). Payloads are thin {id, type, created_at, data:{type, id}} — fetch the resource, dedupe on the event id. - `id` (string) — whep_… identifier - `url` (string) - `events` (string[]) - `status` (enum) — "active" · "disabled" - `failure_count` (integer) - `created_at` (string) - `secret` (string) — whsec_… — present ONLY in the create response - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/webhooks \ -H "Authorization: Bearer ast_..." \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/hooks/agentsky","events":["routine_run.failed","routine.paused"]}' ``` ### GET /webhooks/{id} Webhook endpoint detail (scope: read) Path parameters: - `id` (string, required) — The webhook endpoint id (whep_…) Responses: - `200` — OK - `webhook` (object, required) — Outbound webhook endpoint. Deliveries carry x-asteroids-event/-delivery/-timestamp/-signature (v1=hmac-sha256(secret, ".")), retry 3× (0s/5s/30s), and sustained failure disables (PATCH status:active re-enables). Payloads are thin {id, type, created_at, data:{type, id}} — fetch the resource, dedupe on the event id. - `id` (string) — whep_… identifier - `url` (string) - `events` (string[]) - `status` (enum) — "active" · "disabled" - `failure_count` (integer) - `created_at` (string) - `secret` (string) — whsec_… — present ONLY in the create response - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/webhooks/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /webhooks/{id} Update url/events, or status:active to re-enable a disabled endpoint (scope: write) Path parameters: - `id` (string, required) — The webhook endpoint id (whep_…) Body (application/json): - `url` (string) — max length 2048 - `events` (string[]) - `status` (enum) — "active" · "disabled" Responses: - `200` — OK - `webhook` (object, required) — Outbound webhook endpoint. Deliveries carry x-asteroids-event/-delivery/-timestamp/-signature (v1=hmac-sha256(secret, ".")), retry 3× (0s/5s/30s), and sustained failure disables (PATCH status:active re-enables). Payloads are thin {id, type, created_at, data:{type, id}} — fetch the resource, dedupe on the event id. - `id` (string) — whep_… identifier - `url` (string) - `events` (string[]) - `status` (enum) — "active" · "disabled" - `failure_count` (integer) - `created_at` (string) - `secret` (string) — whsec_… — present ONLY in the create response - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/webhooks/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /webhooks/{id} Delete a webhook endpoint (scope: write) Path parameters: - `id` (string, required) — The webhook endpoint id (whep_…) Responses: - `200` — OK - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/webhooks/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ## Apps with sign-in ### GET /oauth_clients The apps the caller registered (scope: read) Answers 404 until apps with sign-in are open on the platform. Responses: - `200` — OK - `oauth_clients` (object[], required) - `id` (string, required) — The client id the app presents when it sends a person to sign in. - `name` (string, required) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[], required) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[], required) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[], required) — People besides you who may use the app before it is reviewed, by the email address of their account. - `review_status` (enum, required) — An unreviewed app works only for its owner and its test users, and people are told it has not been reviewed. — "unreviewed" · "approved" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/oauth_clients' \ -H "Authorization: Bearer ast_..." ``` ### POST /oauth_clients Register an app that signs people in (scope: write) The app signs a person in with the authorization-code flow and PKCE (S256) at the sign-in service, asking for `openid` plus the scopes registered here and naming this API as the `resource`. The access token it receives is sent as `Authorization: Bearer` to this API. It acts as that person in one workspace: a person who belongs to several chooses it when they allow the app, and the app is refused everywhere else (`universe_mismatch` for another workspace named in `X-Universe`, `not_found` for a session outside it). It holds only the scopes they allowed and reaches tasks, sessions, agents, the catalog and whoami. On a session in that workspace it can also send messages and interrupts (`POST /sessions/{id}/events`, `POST /sessions/{id}/interrupt`, both `write`) and follow the live stream (`GET /sessions/{id}/stream`, `read`). It stops working as soon as the person removes the app from their account page: the next request is refused, and a stream that is open ends within a few seconds. To move the app to another workspace the person removes its access and allows it again. A new app is unreviewed. A caller may register up to 10 apps. Answers 404 until apps with sign-in are open on the platform, and 503 `not_migrated` until its tables are in place. Body (application/json): - `name` (string, required) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[], required) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[], required) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[]) — People besides you who may use the app before it is reviewed, by the email address of their account. Responses: - `201` — Created - `oauth_client` (object, required) — An app registered to sign people in with their account. - `id` (string, required) — The client id the app presents when it sends a person to sign in. - `name` (string, required) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[], required) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[], required) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[], required) — People besides you who may use the app before it is reviewed, by the email address of their account. - `review_status` (enum, required) — An unreviewed app works only for its owner and its test users, and people are told it has not been reviewed. — "unreviewed" · "approved" - `created_at` (string, required) - `updated_at` (string, required) - `client_secret` (string, required) — Shown in this response only. Keep it on your server. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/oauth_clients \ -H "Authorization: Bearer ast_..." ``` ### GET /oauth_clients/{id} One of the caller's apps (scope: read) Path parameters: - `id` (string, required) — The app's client id. Responses: - `200` — OK - `oauth_client` (object, required) — An app registered to sign people in with their account. - `id` (string, required) — The client id the app presents when it sends a person to sign in. - `name` (string, required) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[], required) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[], required) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[], required) — People besides you who may use the app before it is reviewed, by the email address of their account. - `review_status` (enum, required) — An unreviewed app works only for its owner and its test users, and people are told it has not been reviewed. — "unreviewed" · "approved" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/oauth_clients/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /oauth_clients/{id} Change one of the caller's apps (scope: write) Send only the fields to change. Changing the name, the redirect URLs or the scopes of an approved app returns it to unreviewed; changing its test users does not. Path parameters: - `id` (string, required) — The app's client id. Body (application/json): - `name` (string) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[]) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[]) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[]) — People besides you who may use the app before it is reviewed, by the email address of their account. Responses: - `200` — OK - `oauth_client` (object, required) — An app registered to sign people in with their account. - `id` (string, required) — The client id the app presents when it sends a person to sign in. - `name` (string, required) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[], required) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[], required) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[], required) — People besides you who may use the app before it is reviewed, by the email address of their account. - `review_status` (enum, required) — An unreviewed app works only for its owner and its test users, and people are told it has not been reviewed. — "unreviewed" · "approved" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/oauth_clients/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /oauth_clients/{id} Delete one of the caller's apps (scope: write) Everyone's access through the app ends with it. Path parameters: - `id` (string, required) — The app's client id. Responses: - `200` — Deleted - `deleted` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/oauth_clients/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /oauth_clients/{id}/rotate_secret Replace an app's client secret (scope: write) The previous secret stops working at once. People who already allowed the app keep their access. Path parameters: - `id` (string, required) — The app's client id. Responses: - `200` — OK - `oauth_client` (object, required) — An app registered to sign people in with their account. - `id` (string, required) — The client id the app presents when it sends a person to sign in. - `name` (string, required) — Shown to people when they are asked to allow the app. — max length 80 - `redirect_uris` (string[], required) — Where the app may send people back after they decide. Matched exactly: no wildcards, no fragments. Must be https; `http://localhost` and `http://127.0.0.1` are accepted while the app is not reviewed. - `scopes` (enum[], required) — What the app may ask a person for. `read` sees their tasks, agents and sessions; `write` also starts and changes them. — "read" · "write" - `test_users` (string[], required) — People besides you who may use the app before it is reviewed, by the email address of their account. - `review_status` (enum, required) — An unreviewed app works only for its owner and its test users, and people are told it has not been reviewed. — "unreviewed" · "approved" - `created_at` (string, required) - `updated_at` (string, required) - `client_secret` (string, required) — Shown in this response only. Keep it on your server. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/oauth_clients/sess_cmtb7q2j/rotate_secret \ -H "Authorization: Bearer ast_..." ``` ## Apps with access ### GET /oauth_grants The registered apps the caller allowed into their own account (scope: read) The account page's apps with access: each app the person allowed, the scopes they allowed it, and when. Only a credential for the whole account may read this; a workspace key, an app acting for the person and other restricted credentials are 403 `restricted_token`. 404 while apps with sign-in are switched off. Responses: - `200` — OK - `oauth_grants` (object[], required) - `client_id` (string, required) - `name` (string, required) - `scopes` (string[], required) - `review_status` (enum, required) — "unreviewed" · "approved" - `allowed_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/oauth_grants' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /oauth_grants/{client_id} Remove an app's access to the caller's own account (scope: admin) The same Remove as the account page: the consent is forgotten and every token the app holds for this person stops working at once. The grant removed is always the caller's own; 404 `not_found` when that app had no access. Restricted credentials are 403 `restricted_token`. Path parameters: - `client_id` (string, required) Responses: - `200` — Revoked - `revoked` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/oauth_grants/client_id \ -H "Authorization: Bearer ast_..." ``` ## API keys ### GET /api_keys The caller's own API keys (scope: admin) Only the person's own sign-in or a personal key that is not fixed to one workspace may call this; a workspace key, an app acting for the person, a task pass, a key limited to one job and a key fixed to one workspace are refused with 403 `restricted_token`. Workspace keys are listed on the workspace, not here. Responses: - `200` — OK - `api_keys` (object[], required) - `id` (string, required) - `name` (string, required) - `token_prefix` (string, required) — The first characters of the secret, to tell keys apart. - `scopes` (enum[], required) — "read" · "write" · "admin" - `universe` (string | null, required) — The workspace the key is fixed to; null for a key that works in any of the person's workspaces. - `status` (enum, required) — "active" · "revoked" · "expired" - `can_reveal` (boolean, required) — Whether `reveal` can read the secret back. Keys made before secrets were kept encrypted cannot. - `can_restore` (boolean, required) — Whether `restore` can make the key work again once revoked. - `current` (boolean, required) — True for the key this request was made with. - `last_used_at` (string | null, required) - `expires_at` (string | null, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/api_keys' \ -H "Authorization: Bearer ast_..." ``` ### POST /api_keys Create an API key (scope: admin) Only the person's own sign-in or a personal key that is not fixed to one workspace may call this; a workspace key, an app acting for the person, a task pass, a key limited to one job and a key fixed to one workspace are refused with 403 `restricted_token`. A key never holds a scope the caller lacks (403 `insufficient_scope`). The secret is in this response; `reveal` reads it back later. Body (application/json): - `name` (string, required) — max length 60 - `scopes` (enum[], required) — "read" · "write" · "admin" - `universe` (string | null) — Fix the key to this workspace, which the person must belong to. - `expires_at` (string | null) — When the key stops working; must be in the future. Omit for a key that does not expire. Responses: - `201` — Created - `api_key` (object, required) — One of the person's own API keys. Its secret is never part of it. - `id` (string, required) - `name` (string, required) - `token_prefix` (string, required) — The first characters of the secret, to tell keys apart. - `scopes` (enum[], required) — "read" · "write" · "admin" - `universe` (string | null, required) — The workspace the key is fixed to; null for a key that works in any of the person's workspaces. - `status` (enum, required) — "active" · "revoked" · "expired" - `can_reveal` (boolean, required) — Whether `reveal` can read the secret back. Keys made before secrets were kept encrypted cannot. - `can_restore` (boolean, required) — Whether `restore` can make the key work again once revoked. - `current` (boolean, required) — True for the key this request was made with. - `last_used_at` (string | null, required) - `expires_at` (string | null, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `secret` (string, required) — The key itself. Keep it like a password. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/api_keys \ -H "Authorization: Bearer ast_..." ``` ### GET /api_keys/{id} One of the caller's API keys (scope: admin) Path parameters: - `id` (string, required) — The key's id. Responses: - `200` — OK - `api_key` (object, required) — One of the person's own API keys. Its secret is never part of it. - `id` (string, required) - `name` (string, required) - `token_prefix` (string, required) — The first characters of the secret, to tell keys apart. - `scopes` (enum[], required) — "read" · "write" · "admin" - `universe` (string | null, required) — The workspace the key is fixed to; null for a key that works in any of the person's workspaces. - `status` (enum, required) — "active" · "revoked" · "expired" - `can_reveal` (boolean, required) — Whether `reveal` can read the secret back. Keys made before secrets were kept encrypted cannot. - `can_restore` (boolean, required) — Whether `restore` can make the key work again once revoked. - `current` (boolean, required) — True for the key this request was made with. - `last_used_at` (string | null, required) - `expires_at` (string | null, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/api_keys/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### POST /api_keys/{id}/revoke Revoke an API key (scope: admin) The key stops working at once. Revoking the key the request was made with ends that credential too. Revoking a revoked key changes nothing. Path parameters: - `id` (string, required) — The key's id. Responses: - `200` — OK - `api_key` (object, required) — One of the person's own API keys. Its secret is never part of it. - `id` (string, required) - `name` (string, required) - `token_prefix` (string, required) — The first characters of the secret, to tell keys apart. - `scopes` (enum[], required) — "read" · "write" · "admin" - `universe` (string | null, required) — The workspace the key is fixed to; null for a key that works in any of the person's workspaces. - `status` (enum, required) — "active" · "revoked" · "expired" - `can_reveal` (boolean, required) — Whether `reveal` can read the secret back. Keys made before secrets were kept encrypted cannot. - `can_restore` (boolean, required) — Whether `restore` can make the key work again once revoked. - `current` (boolean, required) — True for the key this request was made with. - `last_used_at` (string | null, required) - `expires_at` (string | null, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/api_keys/sess_cmtb7q2j/revoke \ -H "Authorization: Bearer ast_..." ``` ### POST /api_keys/{id}/restore Restore a revoked API key (scope: admin) The same secret works again. An expired key cannot be restored (409 `expired`). Path parameters: - `id` (string, required) — The key's id. Responses: - `200` — OK - `api_key` (object, required) — One of the person's own API keys. Its secret is never part of it. - `id` (string, required) - `name` (string, required) - `token_prefix` (string, required) — The first characters of the secret, to tell keys apart. - `scopes` (enum[], required) — "read" · "write" · "admin" - `universe` (string | null, required) — The workspace the key is fixed to; null for a key that works in any of the person's workspaces. - `status` (enum, required) — "active" · "revoked" · "expired" - `can_reveal` (boolean, required) — Whether `reveal` can read the secret back. Keys made before secrets were kept encrypted cannot. - `can_restore` (boolean, required) — Whether `restore` can make the key work again once revoked. - `current` (boolean, required) — True for the key this request was made with. - `last_used_at` (string | null, required) - `expires_at` (string | null, required) - `revoked_at` (string | null, required) - `created_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/api_keys/sess_cmtb7q2j/restore \ -H "Authorization: Bearer ast_..." ``` ### POST /api_keys/{id}/reveal Read an API key's secret again (scope: admin) Refused during a support impersonation (403 `impersonating`), and for keys made before secrets were kept encrypted (409 `not_retrievable`). Path parameters: - `id` (string, required) — The key's id. Responses: - `200` — OK - `secret` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/api_keys/sess_cmtb7q2j/reveal \ -H "Authorization: Bearer ast_..." ``` ## Branding ### GET /branding The branding of the caller's shared session pages (scope: read) Only the person's own sign-in or a personal key that is not fixed to one workspace may call this; a workspace key, an app acting for the person, a task pass, a key limited to one job and a key fixed to one workspace are refused with 403 `restricted_token`. `branding` is null when nothing is set. Responses: - `200` — OK - `branding` (object | null, required) - `brand_name` (string | null, required) — Shown in place of AgentSky on the shared pages. — max length 40 - `logo_url` (string | null, required) — An uploaded logo, as a path on the developer console's origin. - `brand_color` (string | null, required) — Header color. — pattern ^#[0-9a-fA-F]{6}$ - `accent_color` (string | null, required) — Buttons and links; text on it is chosen for contrast. — pattern ^#[0-9a-fA-F]{6}$ - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/branding' \ -H "Authorization: Bearer ast_..." ``` ### PUT /branding Replace the branding of the caller's shared session pages (scope: write) Only the person's own sign-in or a personal key that is not fixed to one workspace may call this; a workspace key, an app acting for the person, a task pass, a key limited to one job and a key fixed to one workspace are refused with 403 `restricted_token`. Replaces every field: a field left out, null or empty is cleared. Send JSON, or `multipart/form-data` with the same fields plus a `logo` part (PNG, JPEG or WebP, up to 512 KB) to upload a new logo. To keep the current logo, send its `logo_url` back. Body (application/json): - `brand_name` (string | null) — Shown in place of AgentSky on the shared pages. — max length 40 - `logo_url` (string | null) — The logo already in use, to keep it. A new logo is uploaded as the `logo` part of a multipart request, never by address. - `brand_color` (string | null) — Header color. — pattern ^#[0-9a-fA-F]{6}$ - `accent_color` (string | null) — Buttons and links; text on it is chosen for contrast. — pattern ^#[0-9a-fA-F]{6}$ Responses: - `200` — OK - `branding` (object | null, required) - `brand_name` (string | null, required) — Shown in place of AgentSky on the shared pages. — max length 40 - `logo_url` (string | null, required) — An uploaded logo, as a path on the developer console's origin. - `brand_color` (string | null, required) — Header color. — pattern ^#[0-9a-fA-F]{6}$ - `accent_color` (string | null, required) — Buttons and links; text on it is chosen for contrast. — pattern ^#[0-9a-fA-F]{6}$ - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/branding \ -H "Authorization: Bearer ast_..." ``` ### DELETE /branding Return the shared session pages to the default look (scope: write) Responses: - `200` — OK - `branding` (object | null, required) - `brand_name` (string | null, required) — Shown in place of AgentSky on the shared pages. — max length 40 - `logo_url` (string | null, required) — An uploaded logo, as a path on the developer console's origin. - `brand_color` (string | null, required) — Header color. — pattern ^#[0-9a-fA-F]{6}$ - `accent_color` (string | null, required) — Buttons and links; text on it is chosen for contrast. — pattern ^#[0-9a-fA-F]{6}$ - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/branding \ -H "Authorization: Bearer ast_..." ``` ## Channels ### GET /channels/apps List BYO channel apps — credentials masked (scope: read) Responses: - `200` — OK - `apps` (object[], required) - `verification_error` (string) — Non-secret explanation when X verification remains pending - `verification_retryable` (boolean) — Present for pending X verification. False means the saved refresh attempt cannot be replayed; replace credentials with a fresh OAuth grant. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string) - `status` (string) - `credential_keys` (string[]) — Key names only — values are write-only - `created_at` (string) - `setup` (map) — Steps the platform cannot automate (per-app webhook_url / verify_token / events_url / invite_url) - `registered_platforms` (string[], required) — Platforms with a registered-app writer enabled in this service deployment. Does not prove customer credentials or provider delivery. - `shared_platforms` (string[], required) — Shared bot choices supported by app selection in this deployment; does not inventory the legacy shared channel flows. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/apps' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/apps Register your own bot/number as a channel app (scope: write) Credentials are proven against the platform where an API exists, stored encrypted, and write-only from then on. The response's setup object carries the steps the platform cannot automate (webhook URLs to paste, invite links). Body (application/json): - `platform` (enum, required) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string, required) — max length 120 - `credentials` (map, required) — Write-only. Required keys per platform — telegram: bot_token · discord: bot_token · slack: bot_token, signing_secret · whatsapp: access_token, phone_number_id, app_secret · email (requires EMAIL_CHANNEL_ENABLED): region, access_key, secret_key, from_address, sns_topic_arn; optional from_name, inbound_address, allow_subaddressing, reply_to_sender_only (string true/false); optional s3_bucket, s3_prefix (nonempty, ends /), s3_bucket_owner (12 digits), all three together for S3 receiving with the same region/key pair · teams (when enabled; app creation and credential rotation return 501 coming_soon when disabled): app_id (Azure Application client ID), app_password (client secret); optional app_tenant_id (Directory ID for single-tenant bots, omit for multi-tenant). Credentials are validated against Azure AD on creation. The API returns a webhook_url to paste into Azure Bot Configuration → Messaging Endpoint. Connection returns PENDING and a LINK code to send to the bot. Each conversation can bind to its own session. · imessage (coming soon — answers 501 coming_soon today): api_key, phone_number, webhook_secret · x: consumer_secret + either user_access_token (Group A, OAuth 2.0 Bearer) or client_id + refresh_token (Group B, OAuth 2.0 managed refresh). Exactly one group must be supplied. · lark: app_id, app_secret, verification_token (optional encrypt_key, domain: lark|feishu, app_mode: self-built|store, app_ticket for migration) · buzz: private_key, auth_tag · gchat: service_account_json, project_number · line: channel_access_token, channel_secret (requires LINE_CHANNEL_ENABLED; setup returns manual webhook instructions) Responses: - `201` — Created - `verification_error` (string) — Non-secret explanation when X verification remains pending - `verification_retryable` (boolean) — Present for pending X verification. False means the saved refresh attempt cannot be replayed; replace credentials with a fresh OAuth grant. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string) - `status` (string) - `credential_keys` (string[]) — Key names only — values are write-only - `created_at` (string) - `setup` (map) — Steps the platform cannot automate (per-app webhook_url / verify_token / events_url / invite_url) - `202` — Verification pending - `verification_error` (string) — Non-secret explanation when X verification remains pending - `verification_retryable` (boolean) — Present for pending X verification. False means the saved refresh attempt cannot be replayed; replace credentials with a fresh OAuth grant. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string) - `status` (string) - `credential_keys` (string[]) — Key names only — values are write-only - `created_at` (string) - `setup` (map) — Steps the platform cannot automate (per-app webhook_url / verify_token / events_url / invite_url) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/apps \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/apps/{id} Channel app detail — credentials masked (scope: read) Path parameters: - `id` (string, required) — The channel app id Responses: - `200` — OK - `verification_error` (string) — Non-secret explanation when X verification remains pending - `verification_retryable` (boolean) — Present for pending X verification. False means the saved refresh attempt cannot be replayed; replace credentials with a fresh OAuth grant. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string) - `status` (string) - `credential_keys` (string[]) — Key names only — values are write-only - `created_at` (string) - `setup` (map) — Steps the platform cannot automate (per-app webhook_url / verify_token / events_url / invite_url) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/apps/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /channels/apps/{id} Rename or rotate credentials (rotation re-runs registration) (scope: write) Path parameters: - `id` (string, required) — The channel app id Body (application/json): - `retry_registration` (boolean) — Retry saved X verification without resubmitting credentials; mutually exclusive with credentials - `label` (string) — max length 120 - `credentials` (map) — Full replacement bag; rotation re-runs the platform registration Responses: - `200` — OK - `verification_error` (string) — Non-secret explanation when X verification remains pending - `verification_retryable` (boolean) — Present for pending X verification. False means the saved refresh attempt cannot be replayed; replace credentials with a fresh OAuth grant. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string) - `status` (string) - `credential_keys` (string[]) — Key names only — values are write-only - `created_at` (string) - `setup` (map) — Steps the platform cannot automate (per-app webhook_url / verify_token / events_url / invite_url) - `202` — Verification pending - `verification_error` (string) — Non-secret explanation when X verification remains pending - `verification_retryable` (boolean) — Present for pending X verification. False means the saved refresh attempt cannot be replayed; replace credentials with a fresh OAuth grant. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `label` (string) - `status` (string) - `credential_keys` (string[]) — Key names only — values are write-only - `created_at` (string) - `setup` (map) — Steps the platform cannot automate (per-app webhook_url / verify_token / events_url / invite_url) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/channels/apps/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/apps/{id} Delete the channel app (scope: write) Path parameters: - `id` (string, required) — The channel app id Responses: - `204` — Deleted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/apps/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/connections List connections with their bound session (scope: read) Responses: - `200` — OK - `connections` (object[], required) - `id` (string) - `platform` (string) - `channel_id` (string) - `thread_id` (string) — Present after administrative adoption; the existing app-scoped reply address - `label` (string) - `status` (enum) — "PENDING" · "CONNECTED" · "DISCONNECTED" - `bound_session` (object) - `id` (string) - `label` (string) - `created_at` (string) - `connect` (object) — PENDING only: open url; X and webhook-based Slack/Discord use a hosted page, phone-based flows include phone + code, and Lark includes app_id and a code to send to the bot. - `url` (string) - `phone` (string) - `app_id` (string) — Lark app ID used to open the bot - `code` (string) - `expires_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/connections' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/connections Connect a surface — link-flow platforms return a pending connect ceremony (scope: write) telegram/imessage/whatsapp/x/lark/gchat mint a connect ceremony (hosted page or deep link + code) the end user claims in-platform. Slack/Discord with destination.webhook return a LINK ceremony; other Slack/Discord flows create a channel synchronously. Pass app for BYO; callback_url (link flows) redirects the hosted connect page to you after the claim. Body (application/json): - `platform` (enum, required) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" · "loopback" - `label` (string) — max length 120 - `metadata` (object) — Echoed on channel.connected and the callback redirect - `destination` (object) — Pre-bind to exactly one owned session or webhook endpoint when the surface is claimed - `session` (string) - `webhook` (string) - `app` (string) — BYO ChannelApp id — the connect ceremony runs on your bot/number - `adopt` (object) — Admin-scope migration of the app owner's previously verified conversation. Requires an owned active app, an app-scoped thread ID and destination. The caller attests the existing sender binding and consent; no LINK ceremony or provider message is performed. Exact replay reuses the connection; changed sender, metadata, destination or a disconnected record returns 409. Cannot combine with callback_url or invite_user. - `thread_id` (string, required) — max length 8192 - `author_id` (string, required) — max length 1024 - `tenant_key` (string) — Required only for Lark Store adoption; provider app and chat state must confirm this organization. — max length 256 - `invite_user` (string) — Slack only: user id to invite into the created channel - `callback_url` (string) — Link-flow platforms only: the hosted connect page redirects here after the claim with connection_id, status, and metadata query params Responses: - `201` — Created - `id` (string) - `platform` (string) - `channel_id` (string) - `thread_id` (string) — Present after administrative adoption; the existing app-scoped reply address - `label` (string) - `status` (enum) — "PENDING" · "CONNECTED" · "DISCONNECTED" - `bound_session` (object) - `id` (string) - `label` (string) - `created_at` (string) - `connect` (object) — PENDING only: open url; X and webhook-based Slack/Discord use a hosted page, phone-based flows include phone + code, and Lark includes app_id and a code to send to the bot. - `url` (string) - `phone` (string) - `app_id` (string) — Lark app ID used to open the bot - `code` (string) - `expires_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/connections \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/connections/status Read your connection claim's current status (scope: read) Look up a link-flow ceremony by its 32-character token, scoped to the authenticated owner and universe. Reconnecting an existing conversation can replace the pending connection ID; this returns the effective ID. It does not confirm downstream webhook processing. Responses are not cached. Normal API rate limits apply; back off on 429. Query parameters: - `token` (string, required) — The token after LINK: or in the Telegram start parameter Responses: - `200` — OK - `status` (enum, required) — "pending" · "connected" · "expired" · "disconnected" - `connection_id` (string, required) - `platform` (string, required) - `metadata` (object, required) — The caller's original connection metadata - `expires_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/connections/status?token=Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/connections/{id} Disconnect the surface (scope: write) Path parameters: - `id` (string, required) — The connection id Responses: - `204` — Disconnected - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/connections/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/connections/{id}/pending Cancel an unclaimed chat setup (scope: write) Revokes only an owned, unbound PENDING connection with a pending link. Serializes with claims and bindings; returns 409 connection_not_pending if already claimed or bound. Repeating an already canceled pending setup returns 204. Does not disconnect connected chats or emit a channel.disconnected webhook. Path parameters: - `id` (string, required) — The exact connection id returned by setup Responses: - `204` — Pending setup canceled - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/connections/sess_cmtb7q2j/pending \ -H "Authorization: Bearer ast_..." ``` ### PUT /channels/connections/{id}/binding Set where the surface routes — a session or your webhook endpoint (single active binding) (scope: write) Path parameters: - `id` (string, required) — The connection id Body (application/json): - `destination` (object, required) — Exactly one of webhook (endpoint id) or session (session id) - `webhook` (string) - `session` (string) Responses: - `200` — OK - `connection_id` (string) - `binding_id` (string) - `session` (string) - `webhook` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/channels/connections/sess_cmtb7q2j/binding \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/connections/{id}/capabilities Declared platform capabilities — branch on these instead of guessing (scope: read) Path parameters: - `id` (string, required) — The connection id Responses: - `200` — OK - `connection_id` (string) - `platform` (string) - `history_source` (enum) — History origin; unavailable returns 501 rather than an empty history. — "provider" · "sdk_cache" · "unavailable" - `capabilities` (map) — threads · reactions · markers · proactive · markdown · modals · ephemeral · streaming · files - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/connections/sess_cmtb7q2j/capabilities' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/connections/{id}/dev/inbound Inject a message into a loopback connection (scope: write) Local development without a real platform: the message goes through the same webhook pipeline a Telegram or Slack event would. Only loopback connections accept it — anything else answers 400. Path parameters: - `id` (string, required) — The connection id Body (application/json): - `text` (string, required) - `thread` (string) — Thread key within the connection; default "main" - `id` (string) — Message id to attribute it to; default a generated one - `author` (object) - `user_id` (string, required) - `user_name` (string) - `mention` (boolean) — Whether it reads as a mention of the bot Responses: - `202` — Accepted - `thread_id` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/connections/sess_cmtb7q2j/dev/inbound \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/connections/{id}/dev/outbound Everything the agent has sent on a loopback connection (scope: read) The counterpart to dev/inbound: what a real platform would have received. Path parameters: - `id` (string, required) — The connection id Responses: - `200` — OK - `outbound` (object[], required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/connections/sess_cmtb7q2j/dev/outbound' \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/bindings List bindings, optionally by connection (scope: read) Query parameters: - `connection_id` (string) Responses: - `200` — OK - `bindings` (object[], required) - `id` (string) - `connection_id` (string) - `thread_id` (string) - `destination` (object) — Exactly one of webhook (endpoint id) or session (session id) - `webhook` (string) - `session` (string) - `is_default` (boolean) - `status` (string) - `route_key` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/bindings' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/bindings Route one thread of a connection to a session or your webhook (scope: write) Body (application/json): - `connection_id` (string, required) - `thread_id` (string, required) — The one thread this rule routes. Connection-level routing is a single slot — set it via PUT /connections/{id}/binding - `destination` (object, required) — Exactly one of webhook (endpoint id) or session (session id) - `webhook` (string) - `session` (string) - `route_key` (string) — max length 60 Responses: - `201` — Created - `id` (string) - `connection_id` (string) - `thread_id` (string) - `destination` (object) — Exactly one of webhook (endpoint id) or session (session id) - `webhook` (string) - `session` (string) - `is_default` (boolean) - `status` (string) - `route_key` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/bindings \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/bindings/{id} Delete the binding (scope: write) Path parameters: - `id` (string, required) — The binding id Responses: - `204` — Deleted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/bindings/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/identities List the chat identities linked to your account (scope: read) Responses: - `200` — OK - `identities` (object[], required) - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `external_user_id` (string) — The person's id on that platform - `display_name` (string) - `linked_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/identities' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/identities Complete a link offered to a person in a connected chat, as the caller (scope: write) From then on that person's messages in conversations on the same bot or workspace run as the caller's account. The token is the one posted in the chat; it works once and for 30 minutes. 404 when it was used or has expired, 503 `not_migrated` until the platform's tables are in place. Body (application/json): - `token` (string, required) Responses: - `201` — Linked - `identity` (object, required) — A person in a connected chat, linked to the account their messages there run as. - `id` (string) - `platform` (enum) — "telegram" · "slack" · "discord" · "whatsapp" · "imessage" · "email" · "teams" · "x" · "lark" · "buzz" · "gchat" · "line" - `external_user_id` (string) — The person's id on that platform - `display_name` (string) - `linked_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/identities \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/identities/links/{token} Read who a link would connect, before completing it (scope: read) Path parameters: - `token` (string, required) — The link token posted in the chat Responses: - `200` — OK - `link` (any, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/identities/links/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/identities/{id} Unlink a chat identity; that person's messages stop running as you (scope: write) Path parameters: - `id` (string, required) — The identity id Responses: - `204` — Unlinked - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/identities/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/webhooks List webhook endpoints (scope: read) Responses: - `200` — OK - `webhooks` (object[], required) - `id` (string) - `url` (string) - `events` (string[]) - `status` (string) - `secret` (string) — whsec_ HMAC secret — returned on create only - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/webhooks' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/webhooks Create a webhook endpoint — HMAC-signed deliveries, 0/5/30s retries (scope: write) Deliveries carry X-Asteroids-{Event,Delivery,Timestamp,Signature}; ~20 consecutive failures disable the endpoint. Consumers must be idempotent by delivery id. The message.received payload is described by ChannelMessageReceived, including its optional service-owned claim. Body (application/json): - `url` (string, required) — https (http allowed for localhost only) - `events` (string[]) — Default ["message.received"] Responses: - `201` — Created - `id` (string) - `url` (string) - `events` (string[]) - `status` (string) - `secret` (string) — whsec_ HMAC secret — returned on create only - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/webhooks \ -H "Authorization: Bearer ast_..." ``` ### PATCH /channels/webhooks/{id} Re-enable a disabled endpoint (resets failures, keeps the secret) and/or re-pick events (scope: write) Path parameters: - `id` (string, required) — The webhook endpoint id Body (application/json): - `status` (enum) — "ACTIVE" - `events` (string[]) Responses: - `200` — OK - `id` (string) - `url` (string) - `events` (string[]) - `status` (string) - `secret` (string) — whsec_ HMAC secret — returned on create only - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/channels/webhooks/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/webhooks/{id} Delete the webhook endpoint (scope: write) Path parameters: - `id` (string, required) — The webhook endpoint id Responses: - `204` — Deleted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/webhooks/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/threads/{id}/messages Read one authorized conversation history page (scope: read) Provider-authorized history or retained SDK cache only. Does not send a turn, persist files, or mark messages seen. An unsupported adapter returns history_unavailable, not an empty success. Reuse next_cursor with the same thread and limit; same-page retries are allowed. Cursors are encrypted and bound to the current caller, installation and thread. Files are metadata only. Path parameters: - `id` (string, required) — The exact thread id Query parameters: - `limit` (integer) - `cursor` (string) Responses: - `200` — One chronological page; next_cursor continues to older messages - `thread_id` (string) - `history_source` (enum) — "provider" · "sdk_cache" - `coverage` (string) - `messages` (object[]) - `next_cursor` (string | null) - `truncated` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/channels/threads/sess_cmtb7q2j/messages' \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/threads/{id}/messages Post into a thread — markdown renders natively per platform (scope: write) File parts use owned Files API assets and require capabilities.files. Long replies are split into complete platform-sized messages. Supply idempotency_key to avoid duplicate sends on retry; an uncertain or partial send must be inspected before choosing a new key. done and failed are mutually exclusive. Path parameters: - `id` (string, required) — The thread id (from message.received or channel events) Body (application/json): - `parts` (object[], required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) — A visible, unexpired Files API upload or session output; requires capabilities.files. — pattern ^file_[A-Za-z0-9_-]{1,128}$ - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "markdown" (object) - `type` ("markdown", required) - `text` (string, required) - type: "raw" (object) - `type` ("raw", required) - `platform` (string, required) - `payload` (string, required) — Platform-native payload, JSON-encoded - `email` (object) — SES-only headers. The authorized thread owns the recipient; this object cannot change To. - `subject` (string) — max length 200 — pattern ^[^\r\n]+$ - `reply_to` (string) - `idempotency_key` (string) — Durable replay key scoped to the persisted owner, connection and thread, independent of bearer token scope. Same-content replays return the accepted receipt even after disconnect; changed content or an unconfirmed send returns 409 without resending. — max length 200 - `done` (boolean) — Resolves the working marker as successful; cannot be true with failed - `failed` (boolean) — Resolves the working marker as failed; cannot be true with done Responses: - `201` — Created - `message_id` (string) - `thread_id` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/threads/sess_cmtb7q2j/messages \ -H "Authorization: Bearer ast_..." ``` ### PUT /channels/threads/{id}/messages/{mid}/reactions/{emoji} Add a reaction (platform-neutral emoji names) (scope: write) Path parameters: - `id` (string, required) — The thread id - `mid` (string, required) - `emoji` (string, required) Responses: - `204` — Reacted - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/channels/threads/sess_cmtb7q2j/messages/mid/reactions/emoji \ -H "Authorization: Bearer ast_..." ``` ### DELETE /channels/threads/{id}/messages/{mid}/reactions/{emoji} Remove a reaction (scope: write) Path parameters: - `id` (string, required) — The thread id - `mid` (string, required) - `emoji` (string, required) Responses: - `204` — Removed - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/channels/threads/sess_cmtb7q2j/messages/mid/reactions/emoji \ -H "Authorization: Bearer ast_..." ``` ### POST /channels/deliveries Proactive fan-out: post to every thread bound to a destination (scope: write) Body (application/json): - `destination` (object, required) — Exactly one of webhook (endpoint id) or session (session id) - `webhook` (string) - `session` (string) - `parts` (object[], required) - type: "file" (object) - `type` ("file", required) - `file_id` (string, required) — A visible, unexpired Files API upload or session output; requires capabilities.files. — pattern ^file_[A-Za-z0-9_-]{1,128}$ - type: "text" (object) - `type` ("text", required) - `text` (string, required) - type: "markdown" (object) - `type` ("markdown", required) - `text` (string, required) - type: "raw" (object) - `type` ("raw", required) - `platform` (string, required) - `payload` (string, required) — Platform-native payload, JSON-encoded - `done` (boolean) — Resolves each working marker as successful; cannot be true with failed - `failed` (boolean) — Resolves each working marker as failed; cannot be true with done Responses: - `200` — OK — per-thread results - `results` (object[]) - `thread_id` (string) - `status` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/channels/deliveries \ -H "Authorization: Bearer ast_..." ``` ### GET /channels/events/stream SSE mirror of your webhook events — for local development (scope: read) Responses: - `200` — SSE stream - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -N 'https://api.agentsky.dev/v1/channels/events/stream' \ -H "Authorization: Bearer ast_..." ``` ## White label ### GET /partner/onboarding The onboarding instruction for this account (scope: read) Markdown by default (the reader is a language model); `?format=json` returns the same state as fields. It describes the step this account is actually on, so an agent can fetch it, make the call it names, and fetch it again until it reports the account is live. This is the entry point for white-label onboarding — there is no form. Query parameters: - `format` (string) Responses: - `200` — The instruction - `state` (enum) — "unapplied" · "waiting" · "rejected" · "setting_up" · "live" · "suspended" - `next_action` (string) — The one call that advances onboarding; null when nothing does. - `outstanding` (string[]) - `partner` (any) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/partner/onboarding' \ -H "Authorization: Bearer ast_..." ``` ### GET /partner Where this account stands (scope: read) Responses: - `200` — OK - `state` (enum) — "unapplied" · "waiting" · "rejected" · "setting_up" · "live" · "suspended" - `next_action` (string) — The one call that advances onboarding; null when nothing does. - `outstanding` (string[]) - `partner` (any) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/partner' \ -H "Authorization: Bearer ast_..." ``` ### POST /partner/application Ask to become a white-label partner (scope: write) A request, not a switch: a person here reads it and decides, and no call advances it in the meantime. One open application per account. Body (application/json): - `productName` (string, required) — What this partner's customers will call the product. - `intendedHost` (string, required) — Advisory at this stage; hostnames are claimed later. - `notes` (string) Responses: - `201` — Accepted for a decision - `application_id` (string) - `state` (string) - `next_action` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/partner/application \ -H "Authorization: Bearer ast_..." ``` ### POST /partner/key-prefix Claim the prefix this partner's API keys carry (scope: admin) The visible half of every key their customers hold: `acme_…` rather than `ast_…`. Changeable only while no keys exist under it — after that, changing it would strand them. Body (application/json): - `prefix` (string, required) — 2–8 lowercase letters or digits, starting with a letter. Responses: - `200` — Claimed - `partner` (object) - `id` (string) - `slug` (string) - `name` (string) - `status` (enum) — "pending" · "active" · "suspended" - `key_prefix` (string) — Null until claimed. - `brand` (map) - `brand_missing` (string[]) — Brand fields still falling back to ours. - `rate_limit_per_min` (integer) - `hostname_quota` (integer) - `hostnames` (object[]) - `hostname` (string) - `plane` (enum) — "API" · "GATEWAY" · "APP" · "ACCOUNTS" · "STUDIO_POOL" · "STUDIO_WILDCARD" - `ssl_status` (string) - `serving` (boolean) — Only a serving hostname counts toward going live. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/partner/key-prefix \ -H "Authorization: Bearer ast_..." ``` ### POST /partner/domains Claim a hostname for one plane (scope: admin) The response leads with the CNAME a person must add at the partner's own registrar — the one step of onboarding nobody on our side can do. Adding the row is not ownership: nothing routes until domain-control validation issues a certificate. Body (application/json): - `hostname` (string, required) - `plane` (enum, required) — "API" · "GATEWAY" · "APP" · "ACCOUNTS" · "STUDIO_POOL" Responses: - `201` — Claimed, pending DNS - `dns_record` (string) - `next_action` (string) - `partner` (object) - `id` (string) - `slug` (string) - `name` (string) - `status` (enum) — "pending" · "active" · "suspended" - `key_prefix` (string) — Null until claimed. - `brand` (map) - `brand_missing` (string[]) — Brand fields still falling back to ours. - `rate_limit_per_min` (integer) - `hostname_quota` (integer) - `hostnames` (object[]) - `hostname` (string) - `plane` (enum) — "API" · "GATEWAY" · "APP" · "ACCOUNTS" · "STUDIO_POOL" · "STUDIO_WILDCARD" - `ssl_status` (string) - `serving` (boolean) — Only a serving hostname counts toward going live. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/partner/domains \ -H "Authorization: Bearer ast_..." ``` ### PUT /partner/brand Set the names and copy this partner's customers see (scope: admin) All fields together. A partial brand is refused rather than half-applied: every field left unset falls back to a platform value carrying our name, which would reach their customer. Body (application/json): - `productName` (string, required) - `requestIdHeader` (string, required) — Written into responses verbatim; must look like x-something. - `spendGateHeader` (string, required) — Must look like x-something. - `spendGateMessage` (string, required) — What this partner's customer reads when their balance is empty. - `billingUrl` (string, required) - `docsUrl` (string, required) - `supportUrl` (string, required) - `frameAncestors` (string, required) — A bare https origin naming one of this partner's own hostnames — it becomes the CSP on their customers' session pages. Responses: - `200` — Saved - `partner` (object) - `id` (string) - `slug` (string) - `name` (string) - `status` (enum) — "pending" · "active" · "suspended" - `key_prefix` (string) — Null until claimed. - `brand` (map) - `brand_missing` (string[]) — Brand fields still falling back to ours. - `rate_limit_per_min` (integer) - `hostname_quota` (integer) - `hostnames` (object[]) - `hostname` (string) - `plane` (enum) — "API" · "GATEWAY" · "APP" · "ACCOUNTS" · "STUDIO_POOL" · "STUDIO_WILDCARD" - `ssl_status` (string) - `serving` (boolean) — Only a serving hostname counts toward going live. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/partner/brand \ -H "Authorization: Bearer ast_..." ``` ### GET /partner/site What this partner has set for its own hostnames (scope: read) Responses: - `200` — OK - `site` (object) - `brand` (any) - `helpers` (enum | string[]) — "all", or the handles agents in this partner's workspaces may hire. — "all" - `agents` (string[]) — The handles of this partner's own agents: the only ones its hostnames list. - `only_here` (string[]) — Those of `agents` that are listed on this partner's hostnames and nowhere else. The rest also appear in the general catalog once approved. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/partner/site' \ -H "Authorization: Bearer ast_..." ``` ### PUT /partner/site Set how the product looks, which helpers may be hired, and which agents are this partner's (scope: admin) Send any of `brand`, `helpers` and `agents`; a part left out is left as it is. `brand` is replaced whole, or cleared with null. A person who signs up on one of this partner's hostnames gets a workspace that counts as the partner's: those hostnames list only the agents named in `agents`, and agents in those workspaces may hire only the helpers `helpers` allows (a refused hire answers 403 `helper_not_allowed`). 422 `refused` says in words what was wrong. Body (application/json): - `brand` (any) - `helpers` (enum | string[]) — "all" - `agents` (string[]) — Each must be a listing in a workspace this partner's owner owns. - `only_here` (string[]) — Replaces the list whole. Each must be one of `agents`. Refused 422 while this partner's hostnames are not switched on. Responses: - `200` — Saved - `site` (object) - `brand` (any) - `helpers` (enum | string[]) — "all", or the handles agents in this partner's workspaces may hire. — "all" - `agents` (string[]) — The handles of this partner's own agents: the only ones its hostnames list. - `only_here` (string[]) — Those of `agents` that are listed on this partner's hostnames and nowhere else. The rest also appear in the general catalog once approved. - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/partner/site \ -H "Authorization: Bearer ast_..." ``` ## Invites ### GET /invites/{token} What an invitation link offers the signed-in person (scope: read) status is open (this account may join), joined, wrong_email (sent to a different address) or closed (used, withdrawn or expired). Path parameters: - `token` (string, required) — The token from the invitation link Responses: - `200` — OK - `workspace` (object) - `slug` (string) - `name` (string) - `invited_by` (string) - `role` (enum) — "admin" · "member" - `email` (string) - `status` (enum) — "open" · "joined" · "wrong_email" · "closed" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/invites/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0' \ -H "Authorization: Bearer ast_..." ``` ### POST /invites/{token} Join the workspace through an invitation link (scope: write) Only an account signed in with the invited, verified email may join; a credential limited to one workspace cannot. Path parameters: - `token` (string, required) Responses: - `200` — OK - `workspace` (object) - `slug` (string) - `name` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/invites/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0 \ -H "Authorization: Bearer ast_..." ``` ### GET /invites/{token}/public Public preview of an invitation — workspace name and inviter, no auth required Returns the workspace name, the inviter's display name and email, and the email address the invite was sent to. Returns 404 for an expired, accepted or revoked invite. The raw token is never logged or forwarded; it is hashed before any DB lookup. Path parameters: - `token` (string, required) — The token from the invitation link Responses: - `200` — OK - `workspace` (object) - `name` (string) - `invited_by` (object) - `name` (string) - `email` (string) - `email` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/invites/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0/public' \ -H "Authorization: Bearer ast_..." ``` ## Workspace harnesses ### GET /sessions/{id}/harness Remote-harness relay WebSocket (scope: write) WebSocket (Upgrade: websocket) served by the chat-proxy. The maker's ACP process connects with `Authorization: Bearer hct_…` (the per-harness connect token minted via `POST /harnesses/{id}/connect-token`). The relay validates the token, then opens a parallel WebSocket to the session's pod and splices both connections together, forwarding newline-delimited JSON-RPC frames in both directions. One live connection per session; a second attempt while one is open returns 409. The session must be ACTIVE and the pod RUNNING. Gated by the `REMOTE_HARNESSES` platform switch (off → 404). Path parameters: - `id` (string, required) — The session id Responses: - `101` — Switching Protocols — WebSocket relay open - `401` — Missing or invalid connect token - `403` — Token does not belong to this session's harness - `404` — Session not found or REMOTE_HARNESSES gate is off - `409` — Session not active, pod not ready, or relay already connected - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/harness' \ -H "Authorization: Bearer ast_..." ``` ### GET /harnesses List workspace harnesses, oldest first (scope: read) Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Responses: - `200` — OK - `harnesses` (object[], required) - `id` (string, required) — hrn_… — the public id - `handle` (string, required) — Short slug within the workspace (e.g. my-harness) - `name` (string, required) - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `model_billing` (enum, required) — gateway: model calls are billed through the platform wallet. external: the harness manages billing itself. — "gateway" · "external" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/harnesses' \ -H "Authorization: Bearer ast_..." ``` ### POST /harnesses Create a harness (scope: write) Creates a workspace harness with the given handle, name and profile. After creating, add a version with POST /harnesses/{id}/versions, then promote it with POST /harnesses/{id}/versions/{v}/current before creating agents that reference it. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Body (application/json): - `handle` (string, required) — pattern ^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$ - `name` (string, required) — max length 200 - `profile` (object, required) - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `model_billing` (enum) — "gateway" · "external" — default: "external" Responses: - `201` — Created - `harness` (object, required) - `id` (string, required) — hrn_… — the public id - `handle` (string, required) — Short slug within the workspace (e.g. my-harness) - `name` (string, required) - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `model_billing` (enum, required) — gateway: model calls are billed through the platform wallet. external: the harness manages billing itself. — "gateway" · "external" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/harnesses \ -H "Authorization: Bearer ast_..." ``` ### GET /harnesses/{id} One harness (scope: read) Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Responses: - `200` — OK - `harness` (object, required) - `id` (string, required) — hrn_… — the public id - `handle` (string, required) — Short slug within the workspace (e.g. my-harness) - `name` (string, required) - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `model_billing` (enum, required) — gateway: model calls are billed through the platform wallet. external: the harness manages billing itself. — "gateway" · "external" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /harnesses/{id} Rename a harness or change its model_billing (scope: write) Omitted fields are left unchanged. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Body (application/json): - `name` (string) — max length 200 - `model_billing` (enum) — "gateway" · "external" Responses: - `200` — OK - `harness` (object, required) - `id` (string, required) — hrn_… — the public id - `handle` (string, required) — Short slug within the workspace (e.g. my-harness) - `name` (string, required) - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `model_billing` (enum, required) — gateway: model calls are billed through the platform wallet. external: the harness manages billing itself. — "gateway" · "external" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /harnesses/{id} Delete a harness (scope: write) Deletes the harness and all its versions. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Responses: - `204` — Deleted — no body - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /harnesses/{id}/versions List versions for a harness, oldest first (scope: read) Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Responses: - `200` — OK - `versions` (object[], required) - `id` (string, required) — hrnv_… — internal version row id - `harness_id` (string, required) — hrn_… — the parent harness - `version` (string, required) — Version string, e.g. v1-4096m - `status` (enum, required) — pending: registered, build not started. building: E2B template build in progress. ready: build succeeded; can be promoted. current: the version agents are created against; at most one per harness. revoked: explicitly withdrawn; cannot be promoted or used for new sessions. — "pending" · "building" · "ready" · "current" · "revoked" - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/versions' \ -H "Authorization: Bearer ast_..." ``` ### POST /harnesses/{id}/versions Register a new harness version (scope: write) Registers the version string and triggers an E2B template build. Poll the version's status until it reaches ready, then promote with POST /harnesses/{id}/versions/{v}/current. If profile is omitted, the parent harness's profile is used. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Body (application/json): - `version` (string, required) — max length 128 - `profile` (object) - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" Responses: - `201` — Created - `version` (object, required) - `id` (string, required) — hrnv_… — internal version row id - `harness_id` (string, required) — hrn_… — the parent harness - `version` (string, required) — Version string, e.g. v1-4096m - `status` (enum, required) — pending: registered, build not started. building: E2B template build in progress. ready: build succeeded; can be promoted. current: the version agents are created against; at most one per harness. revoked: explicitly withdrawn; cannot be promoted or used for new sessions. — "pending" · "building" · "ready" · "current" · "revoked" - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/versions \ -H "Authorization: Bearer ast_..." ``` ### GET /harnesses/{id}/versions/{v} One harness version (scope: read) Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) - `v` (string, required) — The harness version string, e.g. v1-4096m Responses: - `200` — OK - `version` (object, required) - `id` (string, required) — hrnv_… — internal version row id - `harness_id` (string, required) — hrn_… — the parent harness - `version` (string, required) — Version string, e.g. v1-4096m - `status` (enum, required) — pending: registered, build not started. building: E2B template build in progress. ready: build succeeded; can be promoted. current: the version agents are created against; at most one per harness. revoked: explicitly withdrawn; cannot be promoted or used for new sessions. — "pending" · "building" · "ready" · "current" · "revoked" - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/versions/v' \ -H "Authorization: Bearer ast_..." ``` ### POST /harnesses/{id}/versions/{v}/current Promote a version to current (scope: write) Marks this version as the current one for the harness. The previous current version moves back to ready. New agents created against this harness will pin this version. The version must be in ready or current status; a revoked version cannot be promoted. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) - `v` (string, required) — The harness version string, e.g. v1-4096m Responses: - `200` — OK - `version` (object, required) - `id` (string, required) — hrnv_… — internal version row id - `harness_id` (string, required) — hrn_… — the parent harness - `version` (string, required) — Version string, e.g. v1-4096m - `status` (enum, required) — pending: registered, build not started. building: E2B template build in progress. ready: build succeeded; can be promoted. current: the version agents are created against; at most one per harness. revoked: explicitly withdrawn; cannot be promoted or used for new sessions. — "pending" · "building" · "ready" · "current" · "revoked" - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/versions/v/current \ -H "Authorization: Bearer ast_..." ``` ### POST /harnesses/{id}/versions/{v}/revoke Revoke a harness version (scope: write) Permanently withdraws a version; it cannot be promoted or used for new sessions. Sessions already running on this version continue until they terminate. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) - `v` (string, required) — The harness version string, e.g. v1-4096m Responses: - `200` — OK - `version` (object, required) - `id` (string, required) — hrnv_… — internal version row id - `harness_id` (string, required) — hrn_… — the parent harness - `version` (string, required) — Version string, e.g. v1-4096m - `status` (enum, required) — pending: registered, build not started. building: E2B template build in progress. ready: build succeeded; can be promoted. current: the version agents are created against; at most one per harness. revoked: explicitly withdrawn; cannot be promoted or used for new sessions. — "pending" · "building" · "ready" · "current" · "revoked" - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/versions/v/revoke \ -H "Authorization: Bearer ast_..." ``` ### POST /harnesses/{id}/versions/{v}/verify Record the E2B template listing for a version (scope: write) Stores the E2B sandbox listing string for the built template. Called by the build pipeline after a successful build; advances the version status to ready. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) - `v` (string, required) — The harness version string, e.g. v1-4096m Body (application/json): - `listing` (string, required) Responses: - `200` — OK - `version` (object, required) - `id` (string, required) — hrnv_… — internal version row id - `harness_id` (string, required) — hrn_… — the parent harness - `version` (string, required) — Version string, e.g. v1-4096m - `status` (enum, required) — pending: registered, build not started. building: E2B template build in progress. ready: build succeeded; can be promoted. current: the version agents are created against; at most one per harness. revoked: explicitly withdrawn; cannot be promoted or used for new sessions. — "pending" · "building" · "ready" · "current" · "revoked" - `profile` (object, required) — The runtime description of a workspace harness: the distribution spec (npx, uvx or binary), optional auth command, bundled MCP servers, tools, features and other catalog fields the installer reads when it builds the template image. - `id` (string, required) — pattern ^[a-z0-9][a-z0-9_-]{1,39}$ - `name` (string, required) — max length 200 - `version` (string, required) — max length 128 - `distribution` (map | object) - option 1 (map) - option 2 (object) - `oci` (object, required) - `image` (string, required) — pattern ^[^@]+@sha256:[0-9a-f]{64}$ - `auth_command` (string[]) - `bundled_mcp_servers` (map[]) - `configuration_files` (map[]) - `credential_args` (map) - `graceful_exit_timeout_seconds` (number) - `home_directories` (string[]) - `managed_model_id_template` (string) - `native_provider_setup` (map) - `pipeline` (string) - `prerequisites` (map) - `request_timeout_seconds` (number) - `requirements` (string[]) - `session_close_before_exit` (boolean) - `session_config_options` (map) - `session_load_supported` (boolean) - `session_parameters` (map) - `subscription_files` (boolean) - `docker_target` (string) - `tools` (object[]) - `id` (string, required) - `kind` (string, required) - `title` (string) - `requires` (map) - `features` (string[]) - `placement` (enum) — "in_sandbox" · "remote" - `created_at` (string, required) - `updated_at` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/versions/v/verify \ -H "Authorization: Bearer ast_..." ``` ### POST /harnesses/{id}/connect-token Mint a connect token for a remote harness (shown once) (scope: write) Issues a hct_… bearer token the maker's process uses to authenticate to the relay at WS /api/v1/sessions/{id}/harness. Minting revokes any existing token for this harness. The token value is returned exactly once — it cannot be retrieved again. Gated by the REMOTE_HARNESSES platform switch (off → 404). Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Responses: - `201` — Created — one-time token - `connect_token` (string, required) — hct_… — store immediately; not retrievable after this response - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/connect-token \ -H "Authorization: Bearer ast_..." ``` ### DELETE /harnesses/{id}/connect-token Revoke the current connect token for a remote harness (scope: write) Revokes the active hct_… token. Any maker process currently connected will be dropped on its next frame. Gated by the REMOTE_HARNESSES platform switch. Returns 404 when the WORKSPACE_HARNESSES feature switch is off for this workspace. Returns 503 not_migrated when the backing tables are not yet present. Path parameters: - `id` (string, required) — The harness id (hrn_…) Responses: - `200` — OK - `ok` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/harnesses/sess_cmtb7q2j/connect-token \ -H "Authorization: Bearer ast_..." ``` ## Meeting recording ### GET /meeting-recordings List the caller's meeting recordings (scope: read) All recordings charged to the caller's account, newest first. Paginate with after (a created_at ISO string from the last row). Query parameters: - `limit` (integer) — Default 50 - `after` (string) — Cursor: created_at of the last row from the previous page Responses: - `200` — OK - `recordings` (object[]) - `id` (string) — Internal ledger entry id - `meeting_id` (string) - `bot_id` (string) - `idempotency_key` (string) — meeting:: - `session_id` (string) — The session this recording was attributed to, if any - `billed_bot_seconds` (number) - `billed_recorded_seconds` (number) - `cost_usd` (number) - `error_code` (string) - `created_at` (string) - `has_more` (boolean) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/meeting-recordings' \ -H "Authorization: Bearer ast_..." ``` ### POST /meeting-recordings Report a finished meeting recording for metering (scope: write) Books one charge for one recording, on the two bases the supplier charges us on. bot_seconds is how long the recorder held a seat — first to last status change, including time spent waiting to be admitted — billed at $1.00 per hour. recorded_seconds is what was actually captured, capture start to capture end, billed at $0.30 per hour; it is not a bot lifetime and not a running total for the meeting. Neither basis is derived from the other and both are required. The 60-second minimum applies to each basis separately, not to their sum. A report naming error_code zeroes the transcription basis but still owes the seat, because the supplier still charged for it; only a booking where neither basis ran costs nothing. The reporting account is the account charged. A recording is identified by meeting_id and bot_id together: reporting it again returns the booked terms with 200 and charges nothing further. Body (application/json): - `meeting_id` (string, required) — The meeting, in the reporter's own namespace — max length 200 - `bot_id` (string, required) — The recorder instance that produced this recording — max length 200 - `bot_seconds` (number, required) — Seconds the recorder held a seat, the wait included — the basis the seat charge bills on. 0 means it never ran. Over 86400 is refused rather than truncated - `recorded_seconds` (number, required) — Seconds actually captured — the only basis transcription bills on. Not a bot lifetime; bot_seconds is that. Over 86400 is refused rather than truncated - `error_code` (string) — Why this recording is not billable. Its presence alone forces a $0 receipt — max length 100 - `session_id` (string) — A session to attribute the charge to; omit when the reporter has no session — max length 200 Responses: - `200` — Already reported; the booked terms, unchanged - `recording` (object, required) - `meeting_id` (string, required) - `bot_id` (string, required) - `idempotency_key` (string, required) — meeting:: - `recorded` (boolean, required) — False when this recording was already booked - `billed_bot_seconds` (number, required) — Seat seconds the price was computed on, after that basis's own 60s minimum - `billed_recorded_seconds` (number, required) — Recorded seconds the price was computed on, after that basis's own 60s minimum - `cost_usd` (number, required) - `error_code` (string, required) - `201` — Booked - `recording` (object, required) - `meeting_id` (string, required) - `bot_id` (string, required) - `idempotency_key` (string, required) — meeting:: - `recorded` (boolean, required) — False when this recording was already booked - `billed_bot_seconds` (number, required) — Seat seconds the price was computed on, after that basis's own 60s minimum - `billed_recorded_seconds` (number, required) — Recorded seconds the price was computed on, after that basis's own 60s minimum - `cost_usd` (number, required) - `error_code` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/meeting-recordings \ -H "Authorization: Bearer ast_..." ``` ### GET /meeting-recordings/{id} Get one meeting recording (scope: read) One recording by its internal ledger entry id. 404 when the id names no recording charged to the caller's account. Path parameters: - `id` (string, required) Responses: - `200` — OK - `recording` (object) - `id` (string) — Internal ledger entry id - `meeting_id` (string) - `bot_id` (string) - `idempotency_key` (string) — meeting:: - `session_id` (string) — The session this recording was attributed to, if any - `billed_bot_seconds` (number) - `billed_recorded_seconds` (number) - `cost_usd` (number) - `error_code` (string) - `created_at` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/meeting-recordings/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ## Preferences ### GET /preferences The caller's platform preferences (scope: read) Per-person platform settings: the default session budget applied to new sessions when no budget_cents is passed, and model-improvement consent. These are owned by the platform and reachable via any API key. Responses: - `200` — OK - `preferences` (object) - `default_budget_usd` (string) — Default budget as a USD string (e.g. '5.00') or null for no limit - `model_improvement` (boolean) — Whether conversations may be used for model improvement - `language` (enum) — The language every AgentSky surface shows: the web app, the public pages and the Mac app. Null follows the browser or the Mac. — "en" · "zh" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/preferences' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /preferences Update the caller's platform preferences (scope: write) Change default_budget_usd (a dollar string like '5.00', or null to clear), model_improvement (boolean), language ('en', 'zh', or null to follow the browser or the Mac), or any of them together. Returns all preferences after the update. Body (application/json): - `default_budget_usd` (string) — New default budget as a USD string or null to clear - `model_improvement` (boolean) - `language` (enum) — "en" · "zh" Responses: - `200` — OK - `preferences` (object) - `default_budget_usd` (string) - `model_improvement` (boolean) - `language` (enum) — "en" · "zh" - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/preferences \ -H "Authorization: Bearer ast_..." ``` ## Comparisons ### GET /comparisons List comparisons The caller's multi-agent comparison threads for this workspace, most recently updated first. `?archived=true` lists archived ones. Query parameters: - `archived` (boolean) — true to list archived comparisons. Responses: - `200` — OK - `comparisons` (object[], required) - `id` (string, required) - `title` (string, required) - `openerText` (string) — The task text the comparison was started on. - `columnCount` (integer, required) — Number of agent columns (always ≥ 2 for a comparison). - `agents` (object[], required) - `columnId` (string, required) - `agentType` (string, required) - `llm` (string, required) - `sessionId` (string) — The platform session this column runs in. - `attention` (enum) — "needs_you" · "working" · "done" · "idle" · null - `statusLine` (string) - `updatedAt` (string, required) - `archived` (boolean, required) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/comparisons' \ -H "Authorization: Bearer ast_..." ``` ### POST /comparisons Create a comparison Start a new side-by-side comparison: send one task to the main agent and one or more agents in `compare` simultaneously. `requestId` makes a retry safe. Body (application/json): - `requestId` (string, required) — Idempotency key (16–100 alphanumeric/dash/underscore). - `agentType` (string, required) — The main agent's harness type. - `llm` (string, required) — The main agent's model. - `reasoningEffort` (string) — Optional thinking level for the main agent. - `task` (string, required) — The task text sent to every agent. - `compare` (object[], required) — One or more additional agents to compare against (each with agentType and llm). - `agentType` (string, required) - `llm` (string, required) - `reasoningEffort` (string) - `capabilities` (string[]) — Tool ids to grant every agent (all by default). - `timezone` (string) — IANA timezone for the session. Responses: - `201` — Comparison created. - `comparisonId` (string, required) - `columns` (object[], required) - `columnId` (string) - `sessionId` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/comparisons \ -H "Authorization: Bearer ast_..." ``` ### GET /comparisons/{comparisonId} Get a comparison One comparison: its task, each agent's column and session, status, and the verdict once the person has picked a winner. Columns in a blind Arena match return masked agent/model fields until the pick is recorded. Path parameters: - `comparisonId` (string, required) Responses: - `200` — OK - `id` (string, required) - `title` (string) - `openerText` (string) - `createdAt` (string) - `updatedAt` (string) - `columns` (object[], required) - `columnId` (string) - `sessionId` (string) - `agentType` (string) — Harness id (e.g. "dsh"), as POST /comparisons takes it; "unknown" while the match hides identities. - `llm` (string) — Model id; empty while the match hides identities. - `name` (string) — Display label, e.g. "DeepSeek Harness · DeepSeek V4.1 Flash"; null while identities are hidden. - `podStatus` (string) - `arena` (object) — Present when this comparison has an associated Arena match. - `kind` (string) — "comparison" for named side-by-side; other values are blind Arena. - `status` (string) - `hidden` (boolean) — Identities are hidden while the match is pending. - `pending` (boolean) - `vote` (object) - `outcome` (string) - `winnerSessionId` (string) - `sealed` (boolean) - `why` (string[]) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/comparisons/comparisonId' \ -H "Authorization: Bearer ast_..." ``` ### POST /comparisons/{comparisonId}/pick Pick a winner Record the person's winner pick for this comparison. Writes a `human.decision` event to every session's record, as the web UI does. **Blind Arena voting stays UI-only** — this route refuses matches where identities are still hidden (`arena.kind !== "comparison"`). Scripted API votes could skew the community leaderboard without a human ever seeing the responses. Path parameters: - `comparisonId` (string, required) Body (application/json): - `winnerColumnId` (string) — The columnId of the winning column. Omit to record a tie or "none of these". - `outcome` (enum) — "tie" or "both_bad" when there is no winner; ignored when winnerColumnId is set. — "tie" · "both_bad" Responses: - `200` — Pick recorded. - `comparisonId` (string, required) - `winnerSessionId` (string) - `revealed` (boolean, required) - `seats` (object[], required) - `sessionId` (string) - `agentType` (string) - `llm` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/comparisons/comparisonId/pick \ -H "Authorization: Bearer ast_..." ``` ## Threads ### GET /threads List threads The caller's conversation threads for this workspace (single-agent and multi-agent alike), as the sidebar rail shows them. `?filter=all` includes archived threads; `?archived=true` lists only archived ones. Query parameters: - `filter` (string) — Which threads to include. Default: active. - `archived` (boolean) — Shorthand for filter=archived. Responses: - `200` — OK - `threads` (object[], required) - `id` (string, required) - `title` (string, required) - `openerText` (string) - `columnCount` (integer, required) - `agents` (object[], required) - `columnId` (string, required) - `agentType` (string, required) - `llm` (string, required) - `sessionId` (string) - `attention` (enum) — "needs_you" · "working" · "done" · "idle" · null - `statusLine` (string) - `working` (boolean, required) - `updatedAt` (string, required) - `archived` (boolean, required) - `pinned` (boolean, required) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads' \ -H "Authorization: Bearer ast_..." ``` ### POST /threads Start a thread Start a thread and send it its first message, as the web composer does: one agent, or several to compare when `compare` names more. The thread is created in the workspace the key works in (a team workspace key's own, or `X-Universe` for a personal key) unless `workspace` names one; naming a different workspace than the key works in is refused with `workspace_mismatch`. `requestId` makes a retry safe: the same id and body replay the first outcome. `holdOpener` creates the thread without sending anything, so its first `POST /threads/{id}/messages` becomes the opener. Body (application/json): - `requestId` (string, required) — Idempotency key (16–100 letters, digits, dashes or underscores). - `agentType` (string, required) — The agent's harness type. - `llm` (string) — The model; the harness default when omitted. - `reasoningEffort` (string) — Thinking level the model supports. - `text` (string) — The first message. Required unless `files` or `holdOpener` is given. - `files` (object[]) — Up to 20 uploads (from `POST /files`) sent with the first message. - `id` (string, required) - `name` (string, required) - `mediaType` (string, required) - `compare` (object[]) — More agents that receive the same first message, side by side. - `agentType` (string, required) - `llm` (string) - `reasoningEffort` (string) - `capabilities` (string[]) — Tool ids to grant (all available ones when omitted). - `agentId` (string) — One of the caller's saved agents to run on, with its own tools and skills. - `project` (object) — A GitHub repository and branch mounted into the session: `{repoFullName, branch}`. - `machineMount` (object) — A folder a Mac lent through This Mac. - `browserMount` (object) — A browser window a Mac lent through This Mac. - `screenMount` (object) — A screen a Mac lent through This Mac. - `workspace` (string) — Team workspace slug; defaults to the workspace the key works in. - `interactionMode` (enum) — Send the first message in Plan or Build mode. — "plan" · "build" - `holdOpener` (boolean) — Create the thread without sending a message yet. - `importId` (string) — Continue a conversation imported from a local agent. - `timezone` (string) — IANA timezone for the session. Responses: - `201` — Thread started. - `playgroundId` (string, required) — The new thread's id. - `columns` (object[], required) - `columnId` (string) - `sessionId` (string) - `undelivered` (string[]) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/{id} Read a thread One thread with every agent: each column's `sessionId` is the platform session its conversation is read from, `universe` the team workspace that session belongs to. While a comparison hides who is who, an agent is only its letter (`blind`). Path parameters: - `id` (string, required) — Thread id. Responses: - `200` — OK - `id` (string, required) - `title` (string, required) - `openerText` (string) - `updatedAt` (string) - `comparison` (object) — Set for a thread that compares agents: `{kind, pending, hidden, ended, browser}`. - `columns` (object[], required) - `id` (string) — Column id. - `sessionId` (string) - `name` (string) - `agentType` (string) - `llm` (string) - `reasoningEffort` (string) - `podStatus` (string) - `status` (string) - `universe` (string) - `blind` (boolean) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /threads/{id} Organize a thread Rename, pin, archive, unarchive, or mark a thread read or unread, as the web rail's row menu does. Any of `title`, `pinned`, `archived`, `seen`; the fields given are applied in that order. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `title` (string) - `pinned` (boolean) - `archived` (boolean) - `seen` (boolean) Responses: - `200` — OK - `updated` (boolean) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `503` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/threads/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### DELETE /threads/{id} Delete a thread Delete the thread with its sessions. Path parameters: - `id` (string, required) — Thread id. Responses: - `200` — OK - `deleted` (boolean) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/threads/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/{id}/messages Send a message to a thread Send one message to every agent of the thread, or to the ones `columnIds` names, as the web composer does. `deliveryId` (a UUID) names the send, so a retry reaches the same agents once. The answer says, per agent, whether it took the message. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `text` (string) - `deliveryId` (string, required) - `columnIds` (string[]) - `files` (object[]) - `interactionMode` (enum) — "plan" · "build" - `timezone` (string) Responses: - `200` — OK - `results` (map) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/messages \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/{id}/agents Add an agent to a thread Add one more agent beside the thread's others, in a session of its own, with no message sent to it. The same `requestId` replays the first outcome. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `requestId` (string, required) - `agentType` (string, required) - `llm` (string) - `reasoningEffort` (string) - `timezone` (string) Responses: - `201` — OK - `columns` (object[]) - `columnId` (string) - `sessionId` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/agents \ -H "Authorization: Bearer ast_..." ``` ### DELETE /threads/{id}/agents/{columnId} Remove an agent from a thread Take one agent out of the thread, as the web's column menu does. Its session is left as it is. Path parameters: - `id` (string, required) — Thread id. - `columnId` (string, required) — The column id of the agent. Responses: - `200` — OK - `removed` (boolean) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/threads/agent_cmtb2f0a8m9k7x51q4ez6r0d/agents/columnId \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/{id}/tools Read a thread agent's tools The tools turned off in one agent's environment. Path parameters: - `id` (string, required) — Thread id. Query parameters: - `column` (string) — The agent (column id); the first agent of the thread when omitted. Responses: - `200` — OK - `envId` (string) - `disabled` (string[]) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/tools' \ -H "Authorization: Bearer ast_..." ``` ### PUT /threads/{id}/tools Replace a thread agent's tools Replace the environment's list of turned-off tools. Only environment-scoped tool ids are accepted; tools the agent itself owns are read-only. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `envId` (string, required) - `disabled` (string[], required) Responses: - `200` — OK - `disabled` (string[]) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/tools \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/{id}/engine Read a thread agent's model What one of the thread's agents runs on now: its model, effective thinking level and mounted repository. Refused while a comparison hides who is who. Path parameters: - `id` (string, required) — Thread id. Query parameters: - `column` (string) — The agent (column id); the first agent of the thread when omitted. Responses: - `200` — OK - `agentType` (string) - `llm` (string) - `reasoningEffort` (string) - `project` (object) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/engine' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /threads/{id}/engine Change a thread agent's model Change the model (`llm`) or the thinking level (`reasoningEffort`, null for the default of the model) of one agent, as the pickers on the web do. Applies from its next turn. Path parameters: - `id` (string, required) — Thread id. Query parameters: - `column` (string) — The agent (column id); the first agent of the thread when omitted. Body (application/json): - `llm` (string) - `reasoningEffort` (string) Responses: - `200` — OK - `llm` (string) - `reasoningEffort` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/engine \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/{id}/interrupt Stop a thread Stop whatever the thread's agents are doing; `columnId` stops only that agent. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `columnId` (string) Responses: - `200` — OK - `interrupted` (boolean) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/interrupt \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/{id}/fork Fork a thread Continue one agent's conversation in a thread of its own, on a computer of its own. `columnId` names the agent (the first when omitted); `target` (`{agentType, llm?, reasoningEffort?}` or `{agentId, llm?}`) moves it to another agent or model; `messageId` stops the carried conversation at that message. Asking again with the same `requestId` after `fork_pending` resumes the fork. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `requestId` (string, required) - `columnId` (string) - `messageId` (string) - `target` (object) Responses: - `201` — OK - `playgroundId` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/fork \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/{id}/artifacts List a thread's artifacts The artifacts the thread's agents have saved, oldest first, each at the version it serves today. Path parameters: - `id` (string, required) — Thread id. Responses: - `200` — OK - `artifacts` (object[]) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/artifacts' \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/search Search conversations Search the words of the caller's conversations, as the web's search does. One page covers one agent's conversation; `nextCursor` is null once every conversation has been read. Query parameters: - `q` (string) — 3 to 200 characters. - `threads` (string) — active, archived or all. - `cursor` (string) — The cursor of the page before. Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `502` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/search' \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/options Thread start options What a new thread can start on, as the composer offers it: each agent (with `acp` when it picks its model in its own account), its models with their thinking levels and the plan provider that can run each, the tool groups a person can grant, and the tools a new agent starts with. Use it to fill `POST /threads`. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/options' \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/joinable List joinable threads Threads the caller can open but has not: teammates' threads in the workspace `workspace` names (one they belong to), and threads shared with them. A list the platform did not answer for is null rather than empty. Query parameters: - `workspace` (string) — A team workspace slug. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/joinable' \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/joinable Open a teammate's thread Open a thread someone else started, by the rule the web's session link follows. An owner or a member of its workspace gets a thread of their own on that session (`playgroundId`, `columnId`); someone it was only shared with to read gets `readOnly` with the session to read. Body (application/json): - `sessionId` (string, required) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/joinable \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/{id}/continue-mirrored Continue a Mac-synced chat in the cloud Continue a chat synced from a Mac (the agent `columnId` names, the first when omitted) in the cloud, from the transcript the platform already holds, with `text` (and optional `files`) as the new session's first message, as a send on the web does. It stays the same chat: the new session's history starts with the synced conversation and `playgroundId` is this thread. The chat's owner and any workspace member who may use it can continue it; the new session is the caller's. Works with the Mac offline. 409 `continue_pending` means ask again with the same `requestId`; 422 `not_mirrored` when the chat did not come from a Mac. Path parameters: - `id` (string, required) — Thread id. Body (application/json): - `requestId` (string, required) - `text` (string) - `files` (object[]) - `columnId` (string) - `timezone` (string) Responses: - `201` — OK - `playgroundId` (string) - `sessionId` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/sess_cmtb7q2j/continue-mirrored \ -H "Authorization: Bearer ast_..." ``` ### GET /threads/continue Can a local chat continue in the cloud Whether the caller can continue a chat from their computer in the cloud, and the largest session archive the platform takes. 404 while it is off. Responses: - `200` — OK - `available` (boolean) - `maxArchiveBytes` (integer) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/threads/continue' \ -H "Authorization: Bearer ast_..." ``` ### POST /threads/continue Continue a local chat in the cloud Continue a chat that ran on the caller's computer (Claude Code or Codex) as a cloud thread of its own. `native.archive` (base64) resumes the same harness's session from its checkpoint; `transcript` (`{path, sha256, bytes}`) starts any agent knowing where the transcript will be put. 409 `continue_pending` means ask again with the same body. Body (application/json): - `requestId` (string, required) - `agentType` (string, required) - `llm` (string) - `reasoningEffort` (string) - `title` (string) - `requestedAt` (string, required) - `source` (object, required) - `harness` (enum) — "claude_code" · "codex" - `nativeSessionId` (string) - `harnessVersion` (string) - `native` (object) - `archive` (string) - `transcript` (object) - `path` (string) - `sha256` (string) - `bytes` (integer) Responses: - `201` — OK - `playgroundId` (string) - `sessionId` (string) - `mode` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/threads/continue \ -H "Authorization: Bearer ast_..." ``` ## Library artifacts ### GET /library/links Read sharing settings for an original Library file (scope: read) Read-only: an unshared file returns id=null and revision=0. Membership and original source access are checked. All file kinds use the same link settings; bytes are never copied into a new artifact. Query parameters: - `ref` (string, required) — JSON LibraryFileRef. - `workspace` (string) — The team audience for this link; omit for private/public-only sharing. The original file stays in place. A workspace-scoped key cannot select another audience. Responses: - `200` — Current settings and real workspace members - `link` (any, required) - `availableMembers` (object[], required) - `userId` (string, required) - `name` (string, required) - `email` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/links?ref=ref' \ -H "Authorization: Bearer ast_..." ``` ### PUT /library/links Share, publish or update grants on an original Library file (scope: write) Compare-and-set revision (0 for initial state); stale settings return 409. Public access is view-only. Edit grants require native source editing. Member grants bind current platform member identities. No workspace permits private/public only. Revoking publicEnabled invalidates the URL; publishing again mints a new URL. Body (application/json): - `ref` (object, required) - option 1 (object) - `kind` (enum, required) — "agent_prompt" - `agentId` (string, required) — The original agent user instruction file (AGENTS.md); never the combined runtime/system prompt. - option 2 (object) - `kind` (enum, required) — "file" - `fileId` (string, required) - `sessionId` (string) - option 3 (object) - `kind` (enum, required) — "task_file" - `taskId` (string, required) - `path` (string, required) - option 4 (object) - `kind` (enum, required) — "memory_note" - `storeId` (string, required) - `memoryId` (string, required) - option 5 (object) - `kind` (enum, required) — "skill_file" - `skillId` (string, required) - `path` (string, required) - option 6 (object) - `kind` (enum, required) — "artifact" - `artifactId` (string, required) - option 7 (object) - `kind` (enum, required) — "platform_artifact" - `artifactId` (string, required) - `path` (string) — Exact full path from the native artifact manifest; DesignSystem files use root plus the relative editable_files path. Omit only for the entire artifact entry. - `workspace` (string) - `revision` (integer, required) - `workspaceAccess` (enum, required) — "none" · "view" · "edit" - `publicEnabled` (boolean, required) - `members` (object[], required) - `userId` (string, required) - `access` (enum, required) — "view" · "edit" Responses: - `200` — Updated settings - `link` (any, required) - `availableMembers` (object[], required) - `userId` (string, required) - `name` (string, required) - `email` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/library/links \ -H "Authorization: Bearer ast_..." ``` ### DELETE /library/links Revoke all access granted by a Library link (scope: write) Body (application/json): - `ref` (object, required) - option 1 (object) - `kind` (enum, required) — "agent_prompt" - `agentId` (string, required) — The original agent user instruction file (AGENTS.md); never the combined runtime/system prompt. - option 2 (object) - `kind` (enum, required) — "file" - `fileId` (string, required) - `sessionId` (string) - option 3 (object) - `kind` (enum, required) — "task_file" - `taskId` (string, required) - `path` (string, required) - option 4 (object) - `kind` (enum, required) — "memory_note" - `storeId` (string, required) - `memoryId` (string, required) - option 5 (object) - `kind` (enum, required) — "skill_file" - `skillId` (string, required) - `path` (string, required) - option 6 (object) - `kind` (enum, required) — "artifact" - `artifactId` (string, required) - option 7 (object) - `kind` (enum, required) — "platform_artifact" - `artifactId` (string, required) - `path` (string) — Exact full path from the native artifact manifest; DesignSystem files use root plus the relative editable_files path. Omit only for the entire artifact entry. - `workspace` (string) - `revision` (integer, required) Responses: - `200` — Revoked settings - `link` (any, required) - `availableMembers` (object[], required) - `userId` (string, required) - `name` (string, required) - `email` (string, required) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/library/links \ -H "Authorization: Bearer ast_..." ``` ### GET /library/public/{token} Read a publicly shared original Library file No cookie or API key is required. Returns a view-only document snapshot; raw=1 or download=1 streams the original bytes with Range support. Source-owner permissions and link revocation are rechecked for every request. Transport URLs stay server-side. A path query is rejected: the token reads only the granted original file, not its siblings. Path parameters: - `token` (string, required) Query parameters: - `raw` (string) - `download` (string) Responses: - `200` — Snapshot or original file bytes - `name` (string, required) - `format` (enum, required) — "file" · "site" - `mediaType` (string, required) - `revision` (string, required) - `editable` (boolean, required) - `url` (string, required) — Same API path with raw=1; the original bytes, never a storage credential. - `maxBytes` (integer, required) - `text` (string) - `html` (string) - `office` (string) — Native Office source bytes as base64 when supported. - `history` (object[], required) - `previewUrl` (string) — Isolated browser preview when the reader has the required browser session, or the link is public. - `previewUnavailable` (string) - `sizeBytes` (integer) - `sharedBy` (string) - `libraryRef` (object) — Canonical original file identity when supplied by its native document reader; the link still supplies authorization. - option 1 (object) - `kind` (enum, required) — "agent_prompt" - `agentId` (string, required) — The original agent user instruction file (AGENTS.md); never the combined runtime/system prompt. - option 2 (object) - `kind` (enum, required) — "file" - `fileId` (string, required) - `sessionId` (string) - option 3 (object) - `kind` (enum, required) — "task_file" - `taskId` (string, required) - `path` (string, required) - option 4 (object) - `kind` (enum, required) — "memory_note" - `storeId` (string, required) - `memoryId` (string, required) - option 5 (object) - `kind` (enum, required) — "skill_file" - `skillId` (string, required) - `path` (string, required) - option 6 (object) - `kind` (enum, required) — "artifact" - `artifactId` (string, required) - option 7 (object) - `kind` (enum, required) — "platform_artifact" - `artifactId` (string, required) - `path` (string) — Exact full path from the native artifact manifest; DesignSystem files use root plus the relative editable_files path. Omit only for the entire artifact entry. - `206` — Requested original-file byte range - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/public/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0' \ -H "Authorization: Bearer ast_..." ``` ### GET /library/shared/{token} Read an original Library file shared with the API caller (scope: read) Uses the caller's personal ast_ key, never a browser cookie. Requires the exact workspace membership and individual grant or workspace access. A workspace-scoped key cannot act in another audience workspace. raw=1 or download=1 streams original bytes. JSON snapshots expose source text or native Office bytes; private isolated browser grants require a browser login. A path query is rejected; choose the exact original file in the link ref. Path parameters: - `token` (string, required) Query parameters: - `raw` (string) - `download` (string) Responses: - `200` — Snapshot or original file bytes - `name` (string, required) - `format` (enum, required) — "file" · "site" - `mediaType` (string, required) - `revision` (string, required) - `editable` (boolean, required) - `url` (string, required) — Same API path with raw=1; the original bytes, never a storage credential. - `maxBytes` (integer, required) - `text` (string) - `html` (string) - `office` (string) — Native Office source bytes as base64 when supported. - `history` (object[], required) - `previewUrl` (string) — Isolated browser preview when the reader has the required browser session, or the link is public. - `previewUnavailable` (string) - `sizeBytes` (integer) - `sharedBy` (string) - `libraryRef` (object) — Canonical original file identity when supplied by its native document reader; the link still supplies authorization. - option 1 (object) - `kind` (enum, required) — "agent_prompt" - `agentId` (string, required) — The original agent user instruction file (AGENTS.md); never the combined runtime/system prompt. - option 2 (object) - `kind` (enum, required) — "file" - `fileId` (string, required) - `sessionId` (string) - option 3 (object) - `kind` (enum, required) — "task_file" - `taskId` (string, required) - `path` (string, required) - option 4 (object) - `kind` (enum, required) — "memory_note" - `storeId` (string, required) - `memoryId` (string, required) - option 5 (object) - `kind` (enum, required) — "skill_file" - `skillId` (string, required) - `path` (string, required) - option 6 (object) - `kind` (enum, required) — "artifact" - `artifactId` (string, required) - option 7 (object) - `kind` (enum, required) — "platform_artifact" - `artifactId` (string, required) - `path` (string) — Exact full path from the native artifact manifest; DesignSystem files use root plus the relative editable_files path. Omit only for the entire artifact entry. - `206` — Requested original-file byte range - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/shared/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0' \ -H "Authorization: Bearer ast_..." ``` ### PUT /library/shared/{token} Save a native editable file through the caller's Library grant (scope: write) The same original source writer as the web editor. Requires live edit access and a source revision; stale source versions return 409. Grant revocation or downgrade while reading the request also prevents saving. Public links have no writer. Path parameters: - `token` (string, required) Body (application/json): - `revision` (string, required) - `text` (string) - `html` (string) - `office` (string) — Native Office bytes as base64. - `docx` (string) — Native Word document bytes as base64. - `requestId` (string) - `restoreRevision` (string) Responses: - `200` — Native document save receipt - `url` (string, required) - `revision` (string, required) — The revision acknowledged by this native write, never a later latest read. - `publishedAt` (string, required) — Native publication time, or the host's acknowledgement time after the original writer succeeds. - `text` (string) - `historyRevision` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/library/shared/Yy5oaLAHSF5t96EAi9FiEk7uTjLMxNWDVHK8UPr1zK0 \ -H "Authorization: Bearer ast_..." ``` ### GET /library/artifacts List Library artifacts The caller's artifact Library, as the web Library lists it, newest first; each of their own rows names the thread (`threadId`) whose agent saved it. Query parameters: - `q` (string) — Search the names. - `visibility` (string) - `scope` (string) - `cursor` (string) Responses: - `200` — OK - `artifacts` (object[]) - `nextCursor` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/artifacts' \ -H "Authorization: Bearer ast_..." ``` ### POST /library/artifacts Upload a Library artifact Save a file as a new artifact, as the web Library's upload does. Multipart form: `file`, `requestId` (a retry with the same id saves once) and optional `workspace` (a workspace key to place it in). Responses: - `201` — OK - `artifact` (object) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `413` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/library/artifacts \ -H "Authorization: Bearer ast_..." ``` ### GET /library/artifacts/{id} Read a Library artifact What an artifact the caller can read is, and its saved versions, newest first. Path parameters: - `id` (string, required) — Artifact id. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /library/artifacts/{id} Delete a Library artifact Delete the caller's own artifact with all its versions. `revision` names the version they were looking at, so a stale delete is refused. Path parameters: - `id` (string, required) — Artifact id. Body (application/json): - `revision` (integer, required) Responses: - `200` — OK - `deleted` (boolean) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /library/artifacts/{id}/share Read a Library artifact's sharing Whether the artifact's public link is on, the link, and the version it serves. Path parameters: - `id` (string, required) — Artifact id. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/share' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /library/artifacts/{id}/share Share or unshare a Library artifact Turn the public link on or off, as the web share dialog does. `revision` is the current version; `publicVersion` pins the version the link serves. Path parameters: - `id` (string, required) — Artifact id. Body (application/json): - `enabled` (boolean, required) - `revision` (integer, required) - `publicVersion` (integer) - `fromPublicVersion` (integer) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `503` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/share \ -H "Authorization: Bearer ast_..." ``` ### GET /library/artifacts/{id}/source Read a Library artifact's source What the web editor reads of one version: its HTML, text or Word bytes, whether it can be edited, and the last 20 versions. Path parameters: - `id` (string, required) — Artifact id. Query parameters: - `version` (integer) — A saved version; the current one when omitted. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/source' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /library/artifacts/{id}/source Save a new version of a Library artifact Save a new version as the web editor does: `revision` (the current version; 409 when it is no longer current), `requestId`, and one of `html`, `text`, `docx` (base64) or `restoreRevision`. Path parameters: - `id` (string, required) — Artifact id. Body (application/json): - `revision` (integer, required) - `requestId` (string, required) - `html` (string) - `text` (string) - `docx` (string) - `restoreRevision` (integer) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `413` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/source \ -H "Authorization: Bearer ast_..." ``` ### PATCH /library/artifacts/{id}/workspace Place a Library artifact in a workspace Place the caller's own artifact in a workspace they belong to (by its key), or with null take it back to them alone. Path parameters: - `id` (string, required) — Artifact id. Body (application/json): - `workspace` (string, required) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/workspace \ -H "Authorization: Bearer ast_..." ``` ### GET /library/todos List to-dos from documents Every checklist item (`- [ ] text @who YYYY-MM-DD`) in the Markdown documents of the caller's Library: their own and those shared into their workspaces. The documents are the record; this list is derived from their saved versions. `mine` is what is assigned to one of the caller's handles, plus what is unassigned in their own documents. `@agent` marks an item for an agent (`forAgent`). Query parameters: - `scope` (string) - `status` (string) - `due` (string) - `source` (string) — Only this document's items (artifact id). - `today` (string) — The caller's date, for overdue/today/week; UTC today when omitted. Responses: - `200` — `todos` (id, text, done, assignee, forAgent, mine, due, overdue, line, source {id, name, owned, workspace}), `sources` (id, name, open, total), `today`, `truncated`. - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/todos' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /library/todos/{id} Tick or untick a to-do Edits the to-do's document: only the item's checkbox changes, saved as a new version of the document (in its version history), by the same rules as the editor (its owner or a member of its workspace). 409 `todo_changed` when the item is no longer in the document. Path parameters: - `id` (string, required) — To-do id from the list. Body (application/json): - `done` (boolean, required) Responses: - `200` — `todo` as listed, and `revision`, the document's new version. - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/library/todos/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### GET /library/artifacts/{id}/download Download a Library artifact The saved bytes of one version: the original file, or a site as `.tar.gz`. `x-artifact-format` and `x-artifact-version` name what was sent. Path parameters: - `id` (string, required) — Artifact id. Query parameters: - `version` (integer) — A saved version; the current one when omitted. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/download' \ -H "Authorization: Bearer ast_..." ``` ### GET /library/artifacts/{id}/export Export a Library artifact The current version converted to `format`, one of the formats its share state lists; the filename is in `x-artifact-filename`, percent-encoded. Path parameters: - `id` (string, required) — Artifact id. Query parameters: - `format` (string, required) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/library/artifacts/sess_cmtb7q2j/export?format=format' \ -H "Authorization: Bearer ast_..." ``` ## Projects and imports ### GET /projects List projects The GitHub repositories a new thread can start on, as the project picker lists them, with the caller's remembered choice (`last`). Without a list, `unavailable` says why. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/projects' \ -H "Authorization: Bearer ast_..." ``` ### PUT /projects Remember a project Remember the repository (`project`: `{{repoFullName, branch}}`, or null for none) for the caller's next new thread, on the web as well. Body (application/json): - `project` (object) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PUT https://api.agentsky.dev/v1/projects \ -H "Authorization: Bearer ast_..." ``` ### GET /imports List imports Imports of local agent conversations waiting for the caller to continue or discard. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/imports' \ -H "Authorization: Bearer ast_..." ``` ### POST /imports Start an import Mint a one-time import prompt to paste into a local agent (`harness`, default `claude_code`); answers `{{traceId, script, expiresAt}}`. Continue the import with `POST /threads` and `importId`. Body (application/json): - `harness` (string) Responses: - `201` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `429` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/imports \ -H "Authorization: Bearer ast_..." ``` ### GET /imports/{id} Read an import Where an import stands: `{{status, error, continuation}}`. Path parameters: - `id` (string, required) Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/imports/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /imports/{id} Discard an import Permanently discard an import that has not been continued. Path parameters: - `id` (string, required) Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/imports/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ### POST /imports/{id}/rerun Redo an imported session's task Put an imported session beside other agents redoing its task: the person's own run is the first column, visible and sent nothing, and each seat joins a blind comparison and receives the task the import recorded. The same `requestId` replays the first outcome. Path parameters: - `id` (string, required) Body (application/json): - `requestId` (string, required) - `seats` (object[], required) - `agentType` (string, required) - `llm` (string) - `reasoningEffort` (string) - `timezone` (string) Responses: - `201` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/imports/sess_cmtb7q2j/rerun \ -H "Authorization: Bearer ast_..." ``` ## Saved agents ### GET /saved-agents List saved agents The caller's saved agents as My Agents lists them. `?for=composer` lists the agents the composer offers (saved ones, then the ones listed in the workspace); `?for=form` adds what the New agent form needs: the caller's skills, the channels an agent may allow, the longest name, and the provider account forms. Query parameters: - `for` (string) Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/saved-agents' \ -H "Authorization: Bearer ast_..." ``` ### POST /saved-agents Save an agent Save a new agent, as the New agent form does. An agent on its own provider account carries `accountSetup` (`{mode, values}`), stored write-only. The same `requestId` replays the first outcome. Body (application/json): - `requestId` (string, required) - `name` (string, required) - `agentType` (string, required) - `llm` (string) - `reasoningEffort` (string) - `capabilities` (string[], required) - `skillIds` (string[]) - `connectionSupport` (object) - `accountSetup` (object) Responses: - `201` — OK - `agentId` (string) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/saved-agents \ -H "Authorization: Bearer ast_..." ``` ### GET /saved-agents/{id} Read a saved agent One saved agent with its tools, skills, and which channels and connectors it allows. Path parameters: - `id` (string, required) Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/saved-agents/sess_cmtb7q2j' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /saved-agents/{id} Change a saved agent `connectionSupport` changes which channels and whether connectors the agent allows, as the My Agents card does; `accountSetup` (`{mode, values}`) replaces the setup of an agent on its own provider account, write-only. Path parameters: - `id` (string, required) Body (application/json): - `connectionSupport` (object) - `accountSetup` (object) Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/saved-agents/sess_cmtb7q2j \ -H "Authorization: Bearer ast_..." ``` ## Settings ### GET /settings Read settings What the web's Settings page shows: profile (name, email, picture), default session budget, whether chats may improve models, the chosen language, and task emails (`needs_you`, `done`; null when they could not be read). Budget, model improvement and language are the same values as `/preferences`; task emails live only here. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/settings' \ -H "Authorization: Bearer ast_..." ``` ### PATCH /settings Change settings Change any of `defaultBudgetUsd` (a dollar amount as text, or null for none), `modelImprovement`, `language` and `emails` (`{needs_you?, done?}`); answers with all settings. Body (application/json): - `defaultBudgetUsd` (string) - `modelImprovement` (boolean) - `language` (string) - `emails` (object) - `needs_you` (boolean) - `done` (boolean) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X PATCH https://api.agentsky.dev/v1/settings \ -H "Authorization: Bearer ast_..." ``` ## Arena ### GET /arena Arena standings Read-only community Arena stats: current standings, recent verdicts, the active lineup, and the vote-reason vocabulary. Starting Arena matches and casting Arena votes require the web UI — scripted API votes would skew the blind, human-judged community leaderboard. Responses: - `200` — OK - `open` (boolean) - `contestants` (integer) - `verdicts` (integer) - `ties` (integer) - `bothBad` (integer) - `standings` (object[]) - `agentType` (string, required) - `label` (string, required) - `model` (string, required) - `wins` (integer, required) - `losses` (integer, required) - `matches` (integer, required) - `winRate` (number, required) - `recent` (object[]) - `reasons` (string[]) - `failureTags` (string[]) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/arena' \ -H "Authorization: Bearer ast_..." ``` ## Session insights ### GET /sessions/{id}/started-with What a conversation started with The files and context the conversation started with, as the web's Shared files tab shows them. Path parameters: - `id` (string, required) — Session id. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/started-with' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/spend-answer Answer a price request Confirm or decline a paid agent's price for this conversation, as the web's card does: `requestId` names the request and `optionId` the chosen option. Path parameters: - `id` (string, required) — Session id. Body (application/json): - `requestId` (string, required) - `optionId` (string, required) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `409` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/spend-answer \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/secrets Conversation secrets Whether this conversation has a vault a pasted credential can be saved in, and the names it holds. Never the values. Path parameters: - `id` (string, required) — Session id. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/secrets' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/secrets Save a conversation secret Save a pasted credential in the conversation's vault, as the composer's guard does (`name`, `host`, `header`, `valueTemplate`, `value`); answers the reference to put in the message instead of the value. Path parameters: - `id` (string, required) — Session id. Body (application/json): - `name` (string, required) - `host` (string) - `header` (string) - `valueTemplate` (string) - `value` (string, required) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/secrets \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/helpers Task helpers The helpers this session hired into its task, as the Background tasks panel lists them: session id, title, agent, state, status line and cost, plus the whole task's cost. Path parameters: - `id` (string, required) — Session id. Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `502` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/helpers' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/annotations Send annotations Send marked-up elements of a page or artifact to the agent as one message, as the web's and the Mac's Annotate do: each selection with its label, selector, nearby text and box, an optional comment per selection, and optional screenshots as uploaded files. Path parameters: - `id` (string, required) — Session id. Body (application/json): - `selections` (object[]) - `files` (object[]) Responses: - `200` — OK - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/annotations \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/feedback Read message feedback The feedback the person left on this session's messages, keyed by message id, and the available vote reasons. Path parameters: - `id` (string, required) — Session id. Responses: - `200` — OK - `reports` (map, required) — Feedback keyed by message id. - `reasons` (string[], required) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/feedback' \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/feedback Record message feedback Record a thumb on one agent message: `"up"`, `"down"`, a report object, or `null` to take it back. Path parameters: - `id` (string, required) Body (application/json): - `messageId` (string, required) - `feedback` (enum | object | null, required) — "up", "down", a report object, or null to remove. - option 1 (enum) — "up" · "down" - option 2 (object) — A thumb and optional reasons on one agent message. - `vote` (enum, required) — "up" · "down" - `reasons` (string[]) - `details` (string) - option 3 (null) Responses: - `200` — OK - `saved` (boolean) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/feedback \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/side-chat Side chat Ask one question about this session's conversation, as the web's Side chat does: answered beside the session, never sent to the agent. Path parameters: - `id` (string, required) Body (application/json): - `transcript` (string, required) — The session's conversation text the question refers to. - `history` (object[]) — Prior side-chat exchanges in this panel, as {role, text}[]. - `question` (string, required) - `quote` (string) — A specific passage the question targets. Responses: - `200` — OK - `answer` (string, required) - `looked` (string[], required) — Sources the side model consulted. - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/side-chat \ -H "Authorization: Bearer ast_..." ``` ### POST /sessions/{id}/code-review Code review Run a Code Review request for this session via the platform's code-review plane (read, open, or review a GitHub pull request). Refused on a blind Arena comparison session. Path parameters: - `id` (string, required) Body (application/json): Responses: - `200` — OK - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `422` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X POST https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/code-review \ -H "Authorization: Bearer ast_..." ``` ### GET /sessions/{id}/snapshot Get snapshot link The public read-only snapshot link of this session, if one exists. `?thread={id}` is required. Owner only. Path parameters: - `id` (string, required) Query parameters: - `thread` (string, required) — The thread (comparison/playground) id that owns this session. Responses: - `200` — OK - `url` (string, required) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `404` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl 'https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/snapshot?thread=thread' \ -H "Authorization: Bearer ast_..." ``` ### DELETE /sessions/{id}/snapshot Revoke snapshot link Permanently revokes the public snapshot link for this session. Owner only. Path parameters: - `id` (string, required) Query parameters: - `thread` (string, required) Responses: - `200` — OK - `url` (null) - `400` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `401` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `403` — Error — { error: { code, message } } - `error` (object) - `code` (string) - `message` (string) - `4xx` — `{ "error": { "code", "message" } }` (see the error table) Example request: ```bash curl -X DELETE https://api.agentsky.dev/v1/sessions/sess_cmtb7q2j/snapshot \ -H "Authorization: Bearer ast_..." ``` ## Stream events Send and listen are decoupled: POST `…/events`, then read `…/stream` until `session.status_idle` — there is no non-streaming reply mode. Frames are `id:` + `event:` + one `data:` JSON object `{ id, type, sessionId, agent, at, …payload }` (the `id` is the dedupe key against `GET …/events`): | event | payload | meaning | |-------|---------|---------| | `user.message` | content | history only (`GET …/events`) and the POST echo — user messages are not sent on the live stream; `content` is the CMA block list you sent, every image/document source rewritten to a `file` source | | `system.message` | content | history only and the POST echo — the per-turn system text sent alongside a user.message | | `user.interrupt` | by, mode?, deadline_at?, cause? | an interrupt was accepted — echoed on the live stream and kept in history; the pod's `session.status_idle` follows. `mode: "save"` asks the run to start no new work, save what it has and end its turn with a reply listing what was saved, by `deadline_at`; a run still going then is stopped by a second `user.interrupt` with `cause: "save_timeout"` | | `agent.message` | parts, text | a user-facing agent post; a turn may carry several | | `agent.reasoning` | part | raw engine event: thinking (live only) | | `agent.tool_use` | part | raw engine event: a tool invocation (live only) | | `agent.tool_use_delta` | part, offset | only on a stream opened with `?include=agent.tool_use_delta`: the next chunk of a tool call's input JSON (`part.args_partial`) at character `offset`, while the agent is still writing it; in order, the chunks are the input the call's `agent.tool_use` then carries in full (live only) | | `agent.tool_result` | part | raw engine event: the tool's outcome (live only) | | `agent.status` | part | raw engine event: working / waiting / … (live only) | | `session.status_running` | — | a turn started | | `session.status_idle` | stop_reason | the turn is over (`stop_reason.type` is `end_turn` — also after an interrupt — `budget_reached`, `retries_exhausted` after an exhausted engine error, or `provider_lost` after a confirmed runtime Pod loss); never break on bare idle; the stream stays open | | `session.error` | error | a failure inside the turn (`error.type`, `error.message`, `error.retry_status` `retrying` or `exhausted`); the stream stays open and a `session.status_idle` follows | | `session.status_terminated` | — | the session was archived; terminal — nothing follows; close the stream | | `session.deleted` | — | terminal — nothing follows; close the stream | One turn on the standing stream: ``` id: msg-77e0c4… event: agent.message data: {"id":"msg-77e0c4…","type":"agent.message","sessionId":"sess-8c41f0…","agent":"nemesis-ee87","at":"…","text":"Signups today: 42, up 12%.","parts":[…]} id: api-user-9d52a1…#idle event: session.status_idle data: {"id":"api-user-9d52a1…#idle","type":"session.status_idle","sessionId":"sess-8c41f0…","agent":"nemesis-ee87","at":"…","stop_reason":{"type":"end_turn"}} ``` --- # CLI reference — sky `npm i -g @agentsky/sky`. The CLI uses the public API for agent management and the AgentSky app import endpoint for local conversations. ## auth | command | what it does | |---------|--------------| | `sky auth login` | opens the browser to authorize this machine; mints a personal token (read+write) | | `sky auth login --no-browser` | prints a URL to open anywhere; polls until approved (headless/CI bootstrap) | | `sky auth whoami` | who the current token acts as, resolved universe, scopes, and the workspace a key is fixed to | | `sky auth logout` | forgets the stored token (revoke it in Developer → API keys) | | `sky auth connect claude` | connect your Claude Pro/Max subscription (runs `claude setup-token`, or paste a token) — eligible agents then run at $0 model usage | | `sky auth connect chatgpt` | connect your ChatGPT plan by uploading your local Codex sign-in (with consent) | | `sky auth connect --browser` | sign a plan in on the provider's own page (claude, chatgpt, cursor, grok, muse): the platform runs the provider's CLI in a short-lived sandbox and stores the result. `--account new` adds another account, `--account ` re-signs one, `--label` names it, `--no-open` only prints the link | | `sky auth accounts` | list connected model subscriptions and the agents they power | | `sky auth usage ` | plan allowance left in each usage window (`--account `, `--json`) | | `sky auth switch-to-credits ` | move the agents on a subscription onto platform credits, restarting their running sessions (`--account `, `-y`) | | `sky auth disconnect ` | disconnect a model subscription (agents on it fail until switched to credits or reconnected) | | `sky auth apps` | the registered apps you let into your account, with the scopes you allowed | | `sky auth revoke-app ` | remove an app's access to your account; its tokens stop working at once (admin scope) | Config lives at `~/.config/sky/config.json`; `sky config` prints its location and current values. CI needs no login or config — set `SKY_API_TOKEN` (and optionally `SKY_API_URL`, or `SKY_CONSOLE_URL` to point `sky auth login` at a different console). `sky auth login` always mints a **read + write** token. That covers every command here except `sky agent archive` and `sky agent destroy`, which need **admin** and will answer `insufficient_scope` on a logged-in CLI. Mint an admin key under Developer → API keys and pass it for those calls: ```bash SKY_API_TOKEN=ast_… sky agent destroy agent_… ``` ## setup | command | what it does | |---------|--------------| | `sky setup [skill\|logins\|import\|secrets ...]` | bring this machine's agents to AgentSky: install the AgentSky skill and register its MCP server, connect the plans they are signed in to, send your own skills and global instruction files to your workspace, and save the tool keys you pick to your vault — every step when none is named, each asked about first | | `sky setup --key ` | use a setup key from agentsky.dev/setup | | `sky setup --dry-run` | say what would be done; write and send nothing | | `sky setup --json` | print progress as one JSON object per line; never asks | Narrow a run with `--agent claude_code\|codex\|cursor`, `--plan claude\|chatgpt\|cursor` (connect only these plans), `--secret ` (save these tool keys; asked one by one when omitted) or `--no-claude` (leave the Claude plan alone); `-y` skips the questions. Requires CLI 2.7.0 or later. No progress event carries a credential. The `skill` step (CLI 2.9.0 or later) writes the `agentsky` skill to `skills/agentsky/` under `CLAUDE_CONFIG_DIR` (default `~/.claude`), `CODEX_HOME` (default `~/.codex`) and `~/.cursor`, with `catalog.md` beside it: every agent you can hire as of that run, your own workspace's first. It then registers the AgentSky MCP server as `agentsky` with `claude mcp add` and `codex mcp add`, replacing an earlier registration, so the skill can hire over the MCP tools; Codex reads the token from `AGENTSKY_API_KEY`, so set it in the shell that runs `codex`. Run the step again to refresh the catalog and the registration. ## import | command | what it does | |---------|--------------| | `sky import [--harness claude_code\|codex\|all] [--limit 10]` | list recent local conversations, choose one and confirm its upload; prints an AgentSky review link | | `sky import --session [--url ] [-y]` | import a specific recorded conversation; `--yes` confirms the transfer without prompting | | `sky import --all --limit [-y]` | import up to the selected limit after confirming the listed conversations | Requires CLI 2.4.0 or later, `sky auth login` with an unrestricted personal write token, and an existing AgentSky profile. Open AgentSky once with the same account before the first import. Set `--url` to the AgentSky app origin (or use `AGENT_USE_URL`); this is not the developer API URL. Local development permits an HTTP loopback origin; remote destinations require HTTPS. The CLI reads Claude Code records under `CLAUDE_CONFIG_DIR` (default `~/.claude`) and Codex records under `CODEX_HOME` (default `~/.codex`). Recorded descendants are included. It snapshots and redacts the selected JSONL locally before uploading, rejects incomplete records and enforces a 16 MiB limit. Non-interactive use requires `--yes`; when multiple conversations are found, select one with `--session` or choose `--all`. An explicit session ID is searched across the recorded history, independently of the recent-list limit. The browser lets you review or discard the import before continuing. Continuation attaches the full redacted record and asks the receiving agent to read it. Local project files, login credentials and a native resumable checkpoint are not transferred; an automatic wrapper does not infer the task's semantic completion state. ## launch / deploy / clone | command | what it does | |---------|--------------| | `sky launch [--start]` | scaffolds ./agent.toml if absent, creates the agent spec + its first session (idle until the first message; `--start` boots it now), stamps the agent id back into the file, keeping its comments | | `sky deploy` | re-applies ./agent.toml to the existing agent, `type` and `llm` included (the prompt, model and skills apply to sessions started after the save; an unchanged file changes nothing) | | `sky clone [--harness] [--new] [--dry-run] [-y] [--open]` | clones your local agent (Claude Code first) into the cloud: instructions, model, secretless MCP servers — never secrets or history. Linked machines re-run it to sync the prompt; --new launches fresh | | `sky config` | where the config file is and what is in it | ## capabilities | command | what it does | |---------|--------------| | `sky capabilities [--attention] [--json]` | list what your agents can use in the current workspace, with availability, sharing and cost; `--attention` shows only rows that need you | ## harness A workspace-defined harness is an ACP agent the workspace publishes from its own profile; the platform builds, verifies and versions it. Behind the `WORKSPACE_HARNESSES` switch. | command | what it does | |---------|--------------| | `sky harness init [dir]` | write a `harness.json` profile skeleton to validate and fill in | | `sky harness verify [dir] [--handle @listing]` | validate the profile locally; with `--handle`, run the listing's conformance suite through the API and print its checks | | `sky harness push [dir] [--version X]` | create the harness if its handle is new, or submit a new version, and print the version's state | | `sky harness connect --session sess_… --token hct_… -- ` | run an ACP agent that speaks over stdio as that session's brain (a remote harness): dial the session's relay, pipe the agent's JSON-RPC both ways, reconnect with a fresh agent after a drop, and stop when the relay refuses the token or the session; without `--session` it reads `AGENTSKY_HARNESS_RELAY_URL` and `AGENTSKY_HARNESS_TOKEN`; `--once` exits instead of reconnecting. The agent must do all its work through the session's sandbox (ACP `fs/*` and `terminal/*`) | ## worker A worker pool lets a workspace's own machines run the pod: the machine connects out to the platform and runs the exact image the platform names. Behind the `WORKER_POOLS` switch. | command | what it does | |---------|--------------| | `sky worker start --pool wpool_… [--label …] [--token …]` | connect this machine to a worker pool (token from `SKY_WORKER_TOKEN` or `--token`), run the pod in Docker when a session is assigned, tunnel its HTTP and stream back to the platform, and stop on Ctrl-C | ## trace A task trace correlates the work of a local agent with the cloud sessions it starts. By default it holds metadata only — span and tool names and session ids. Prompts, tool output and chosen files are sent only when you name them with `--capture`, redacted on your machine and again on arrival. A trace and everything in it is deleted after 30 days and is never exported. Behind the `TASK_TRACES` switch. | command | what it does | |---------|--------------| | `sky trace start [--name …] [--source claude-code] [--hooks [settings]] [--capture prompts,tool_output,files] [--file path]… [--dir path]…` | start a trace, mint this machine's grant and record a root span; `--hooks` installs the Claude Code hooks (default `./.claude/settings.local.json`); `--capture` also sends that content, and the files scope reads only the `--file` files and files under `--dir` folders, never credential, key, cookie or agent-state files | | `sky trace hook` | record one Claude Code hook event read from stdin; run by the installed hooks, never fails and prints nothing; stops recording once the grant is revoked or expired | | `sky trace flush` | send what the local spool holds (up to 32 MiB, kept 24 hours), retrying with backoff | | `sky trace status` | the running trace, pending events, and what was dropped, given up or refused | | `sky trace show [trc_…] [--json]` | the trace's spans, the sessions and tasks they reach, cost counted once per session, and gaps | | `sky trace stop` | end the root span, send what is left and revoke this machine's grant | The **trace library** is separate: every local Claude Code and Codex session you choose to sync, kept in a workspace and searchable by its members. Sync reads only `~/.claude/projects` and `~/.codex/sessions` / `archived_sessions`, redacts on your machine and again on arrival, and routes each session by the repository's `.agentsky.json`, then the first `--route` whose match is in the folder path or git remote, then `--default` (otherwise it stays local). | command | what it does | |---------|--------------| | `sky trace sync --enable [--route match=workspace]… [--default workspace \| --keep-local]` | turn sync on for this machine and save its routing; prints the folders it reads and where each session goes | | `sky trace sync [--full] [--session id-or-path]… [--limit n] [--dry-run] [--quiet]` | upload new or changed sessions (unchanged files and content are skipped); `--install-schedule` also sweeps every 15 minutes with a weekly full re-read (macOS); `--hook` runs from a Claude Code SessionEnd/Stop hook or Codex notify and returns at once; `--status` reports what was stored where; `--disable` stops uploading | | `sky trace search [--workspace slug] [--limit n] [--cursor …] [--json]` | `sky context search --scope traces` in a workspace's library, newest first | | `sky trace library [--workspace slug] [--limit n] [--json]` | the sessions in a workspace's library, most recently updated first | ## agent An agent is a reusable spec. Conversation verbs act on its **default session**. | command | what it does | |---------|--------------| | `sky agent create [--type --llm --name] [--capability id…] [-f file]` | create the spec only — `sky session create` starts the runtime | | `sky agent list` | specs in the current universe, with session counts | | `sky agent show ` | one spec: engine, model, capabilities, sessions | | `sky agent message ["…"] [--file p…] [--file-id file_…] [--image p…] [--system "…"] [--plan\|--build] [--detach]` | send one message (text, files, images, optional per-turn system text) to the default session and stream the turn until idle; `--plan` asks for a reviewable plan instead of changes | | `sky agent chat [--system "…"]` | interactive REPL with the default session | | `sky agent archive ` | retire the spec — read-only, running sessions unaffected (admin scope — see auth) | | `sky agent edit [--name n] [--engine agentType] [--model llm] [--thinking effort\|default]` | change an agent's name, engine, model or thinking level, as its Library folder does (`PATCH /agents/{id}`) | | `sky agent destroy ` | delete the spec; delete its sessions first. A spec whose sessions were deleted still owns their rows, so it is **archived** rather than removed and keeps appearing in `sky agent list` (admin scope — see auth) | ## session The pod-backed runtime of one agent. Creation starts provisioning by default. Use `--deferred` to leave the session idle without a pod until its first turn. `--message` starts that turn immediately and provisions even with `--deferred`. | command | what it does | |---------|--------------| | `sky session create [--title] [--message "…"] [--deferred] [--vcpus --memory] [--file path[:/mount]…] [--repo url[@ref]…] [--repo-token t] [--no-defaults] [--budget-cents n]` | create a session (eager by default); `--deferred` waits for the first turn; `--no-defaults` attaches none of the workspace's defaults (environment, vault, files, skills, team memory); `--budget-cents` caps what it may spend; `--message` starts that turn immediately, even with `--deferred`; `--file` uploads and mounts files read-only in the working directory, `--repo` clones GitHub repositories there (the token never enters the sandbox) | | `sky session list [agent]` | sessions, with status and the default marker | | `sky session show ` | one session: status, model, budget, mounted resources | | `sky session message ["…"] [--file p…] [--file-id file_…] [--image p…] [--system "…"] [--plan\|--build] [--detach]` | send one message (an agent id means its default session); files upload first and ride the message; `--file-id` attaches a file already uploaded, by id. `--plan` runs the turn in plan mode: the agent inspects and answers with a plan, then waits for a choice to implement; `--build` overrides a session left in plan mode | | `sky session chat [--system "…"]` | interactive REPL over the standing stream | | `sky session interrupt ` | abort the in-flight turn | | `sky session read [--wait] [--timeout s] [--json]` | what the agent has said since the last message it was sent; `--wait` holds until its turn finishes (600 s by default) | | `sky session archive ` | irreversibly archive: no new messages, pod torn down, history stays readable | | `sky session delete ` | permanently delete the session, its events, and its sandbox | | `sky session share [--view] [--rotate] [--off]` | print the session's share link, turning sharing on; `--view` makes it read-only, `--rotate` replaces it, `--off` turns it off | | `sky session usage [--limit n] [--json]` | what the session has cost so far, and its newest billed entries | | `sky session access [--private\|--members] [--add-viewer email…] [--remove-viewer email…]` | who may use the session and what you are to it; the flags change it (owner) | | `sky session shared [--limit n]` | sessions other people shared with you to read | | `sky session people [--json]` | everyone who may send the session turns (the people `@` can name and it can be assigned to), marked `here` when they have it open and `assigned` for its assignee | | `sky session assign [user-id] [--clear]` | assign the session to one of its people (a user id from `sky session people`): they see it waiting on them and are notified; `--clear` unassigns it | | `sky session model [model] [--thinking level]` | print the session's model and thinking level, or switch them; the next turn uses the new ones (`--thinking default` returns to the agent's own) | | `sky session handoff [--from-message id] [--model id] [--thinking level] [--no-wait]` | hand the session's conversation and workspace files to another agent (an agent id or catalog handle), optionally on a chosen model and thinking level, and print the new session; it forks natively when both run the same harness, otherwise carries a written summary | | `sky session cloud-copy [--json]` | what is kept in the cloud for a chat synced from your own computer: its working folder (restored at `/workspace/`) and its native session (resumed exactly on the same agent), or none stored yet; only the chat's owner sees it | ### session resources The files and GitHub repositories mounted into a session's working directory. | command | what it does | |---------|--------------| | `sky session resources list ` | what is mounted, oldest first | | `sky session resources get ` | one resource | | `sky session resources add ` | upload and mount a file before the next turn | | `sky session resources rm ` | unmount a file (repositories stay for the session's life) | | `sky session resources rotate ` | replace a repository's token; the new policy is pushed to the live pod | ### session ports Local servers the agent runs inside the session's pod, each exposed on an origin of its own (`https://app-.agentsky.dev`). A private port — the default — admits one browser per entry link; a public port admits anyone with the address. | command | what it does | |---------|--------------| | `sky session ports list ` | the exposed ports, oldest first | | `sky session ports expose [--public]` | expose a port the agent's server listens on (1024–65535, never the pod's own) as private, or public with the flag — either way the visibility is set, so re-running makes a public port private; prints its address | | `sky session ports open ` | print an entry link: single-use and good for a minute on a private port, the plain address on a public one | | `sky session ports revoke ` | stop exposing it; the address answers 404 from then on | ### session workspace The files the agent works on in its pod. Reads never wake a parked session (`session_parked`: send it a message first); `patch` and `import` do. | command | what it does | |---------|--------------| | `sky session workspace ls [path] [--hidden]` | list a directory (default: the workspace root), up to 200 entries | | `sky session workspace cat [--out file]` | print a file up to 1 MB, or save it | | `sky session workspace changes ` | git status and diff summary of each repository in the workspace | | `sky session workspace patch [repo] [--out file]` | the work in a repository since its base branch as a git patch, ready for `git apply` | | `sky session workspace import [--name n]` | copy a local folder in as a git repository; files git ignores, `.git` and `node_modules` stay behind; over 25 MB it goes up in resumable parts | ### session browser The sandbox browser inside a session pod. Start it to get an entry link for interactive use; the agent uses its own browser tools without needing this. | command | what it does | |---------|--------------| | `sky session browser show [--json]` | the current browser status and entry URL | | `sky session browser start [--json]` | start (or provision) the browser; prints its entry URL | | `sky session browser stop ` | stop it | ## files Uploads you reference from a message. An upload is write-once; every session that receives it keeps its own readable copy. | command | what it does | |---------|--------------| | `sky files upload [--expires s]` | upload local files and folders (every file inside, four at a time), each up to 50 GiB, resuming where an interrupted run left off; prints each `file_…` id | | `sky files usage` | how many files the workspace holds and the space they take; there is no storage limit | | `sky files list [--session ] [--limit n] [--page cursor]` | your uploads, or one session's files | | `sky files get ` | one file's metadata | | `sky files rm ` | soft-delete an upload | | `sky files download -o ` | a session file's bytes | ## task A task's files, one folder every session of the task shares, and the requests waiting on you. A task is addressed by its first session's id. | command | what it does | |---------|--------------| | `sky task files ` | the task's current files | | `sky task get [-o ]` | one file's bytes, printed or written to `` | | `sky task put ` | write a local file to ``; every session of the task sees it at its next turn | | `sky task rm ` | remove a path; its history is kept | | `sky task show [--json]` | the task's sessions and its open requests addressed to you: questions, approvals, apps to connect | | `sky task export [-o dir] [--tier gold\|silver\|exploratory] [--redact name=alias]` | the task's session record as a Harbor task directory: instruction, rubric and judge, files, rollout, one step per revision; names and secrets scrubbed | | `sky task threads [--json]` | the task's threads from the session record: root first, then every helper, handoff and child with its source, agent, status and spend | | `sky task decide [--target ] [--yes\|--no] [--reason "…"] [--key ]` | record what you did with a session's result: adopt, rerun, revise, discard, vote, did_the_job, approve; nothing is recorded unless you run it | | `sky task outcome [--ref ] [--key ]` | record what became of a session's result: pr_merged, deployed, reopened, later_failure, shipped | | `sky task answer (--option \| --text "…") [--note "…"]` | answer an open request; the asking session continues with the answer | | `sky task dispute --reason "…" [--session ]` | dispute an agent fee — report the task was not completed (allowed within 72 hours of the first result; name the session when the task has more than one fee) | | `sky task verdict yes\|no` | record whether the task did the job (replaces an earlier answer) | ## skills The workspace's skill store. Upload a skill once, then attach it to agents by id (`[[skills]]` in agent.toml). | command | what it does | |---------|--------------| | `sky skills upload [--name ] [--json]` | upload a directory with `SKILL.md` at its root as a new skill; prints its id | | `sky skills list` | the workspace's stored skills | | `sky skills attach --agent ` | add a stored skill to an agent; new sessions load it | | `sky skills detach --agent ` | remove a stored skill from an agent; new sessions stop loading it | | `sky skills usage [--since ] [--json]` | how often agents looked up each skill and which names they asked for that do not exist (last 30 days by default) | | `sky plugins install [--mcp-token ]... [--json]` | install or update a Claude Code plugin from `github:owner/repo[/path][@ref]` or a github.com URL: its skills and commands become workspace skills on for every new session; prints the connectors it uses and which to connect | | `sky plugins list [--json]` | the workspace's installed plugins and their skills | | `sky plugins rm ` | remove a plugin's skills (one a session still holds is switched off) | | `sky plugins enable ` / `sky plugins disable ` | switch a plugin on or off: its skills attach to new sessions or stop, and plugin sessions follow | | `sky plugins run [--agent ] [--json]` | start a plugin session: only that plugin's skills and MCP servers, connected apps limited to the ones it declared | | `sky plugins policy [--source ]` | workspace owners and admins: a plugin's policy; default installs it now, blocked stops installs | | `sky plugins market list [--json]` | every plugin the workspace's Claude Code marketplaces list, with its source, policy and install state | | `sky plugins market add ` / `sky plugins market rm ` | workspace owners and admins: list plugins from a folder holding `.claude-plugin/marketplace.json`, or stop | | `sky skills revisions [--json]` | a skill's saved versions, newest first | | `sky skills cat ` | print one file of a skill version, e.g. `SKILL.md` | | `sky skills rm ` | delete a skill from the workspace | ## reflections Skill and lesson changes a reviewer proposed after a finished, workspace-visible session. Nothing changes until a workspace owner or admin approves. A pending proposal is also a request in the task it came from, with Approve and Dismiss, addressed to the workspace's owner; answering it there is the same as `sky reflections approve` or `reject`. | command | what it does | |---------|--------------| | `sky reflections list [--status ] [--json]` | the workspace's proposals (default: pending) | | `sky reflections approve ` | write the proposal through the skill store or memory store | | `sky reflections reject ` | decline the proposal; nothing is written | ## artifact A named, versioned result published from a task's files, with an optional public link. | command | what it does | |---------|--------------| | `sky artifact list [--task ]` | the workspace's artifacts, or one task's | | `sky artifact publish --name "…" --path … [--entry ]` | publish those task files; the same name again adds a version | | `sky artifact get [-o ]` | one file of the artifact's current version | | `sky artifact share [--off]` | print a public link anyone can open; `--off` stops it, and sharing again gives a new link | | `sky artifact rm ` | delete the artifact | ## design-system A design system is an artifact whose files hold `project/design-system.json`: a brand book (`project/README.md`), `tokens.json`, components and assets, the file table a Claude Design System artifact keeps. Agents read its brand book before making slides, a design or a page. | command | what it does | |---------|--------------| | `sky design-system list` | the workspace's design systems | | `sky design-system show [--readme]` | its themes, tokens, components and assets, and any token it could not read; `--readme` prints the brand book | | `sky design-system import [--title "…"]` | import a local folder (one holding `project/`, or `project/` itself) into the task and publish it | | `sky design-system tokens --base [--note "…"]` | save an edited `tokens.json` as the next version; refused when someone saved after `--base` | | `sky design-system write [file] --base [--restore ] [--note "…"]` | save one of the system's editable files (such as `project/README.md`) from a local file as the next version, or put back the copy from `--restore`; refused when someone saved after `--base` | | `sky design-system comments [--text "…" --base --target color:ink]` | read discussion or add feedback on the system, a color or a component; no agent starts | ## library Your artifact Library on agentsky.dev: the files and sites your agents saved or you uploaded (`/v1/library/artifacts…`). A task's artifacts on the platform are `sky artifact`. | command | what it does | |---------|--------------| | `sky library list [--search ] [--cursor ] [--json]` | your Library, newest first | | `sky library upload [--workspace ]` | save a file from this computer to your Library | | `sky library show ` | one artifact and its saved versions | | `sky library delete ` | delete your artifact with all its versions | | `sky library share ` | turn on its public link and print it | | `sky library unshare ` | turn off its public link | | `sky library place ` | place it in a workspace, or take it back to you alone | | `sky library download [--version ]` | save one version's bytes to a file | | `sky library links show --ref '' [--workspace ]` | read any file's sharing, available members and current revision; creates nothing | | `sky library links share --ref '' [--workspace ] [--workspace-access none\|view\|edit] [--member user:view\|edit] [--remove-member user] [--revision ]` | change only the requested grants; repeat member flags as needed; preserves public access | | `sky library links publish --ref '' [--workspace ] [--disable] [--revision ]` | create a public view-only link, or revoke only public access; preserves workspace and members | | `sky library links revoke --ref '' [--workspace ] --revision ` | revoke all sharing at an explicitly reviewed revision | | `sky library links read --token [--public] [--output ]` | read the native shared document snapshot; public reads need no key; output streams original bytes with raw=1 | | `sky library links save --token --revision --file ` | save UTF-8 text through a live recipient edit grant and the original source writer | | `sky library links save --token --revision --data ''` | native HTML, Office, Word or restore: exactly one html, office, docx or restoreRevision (text also accepted), with optional requestId UUID; @file and - read JSON from disk or stdin | The links commands use [the same Library links API](https://agentsky.dev/docs/api.md) (`GET/PUT/DELETE /library/links`). Pass the source's exact reference as JSON: `{"kind":"agent_prompt","agentId":"agent_…"}` (the user-authored AGENTS.md only), `{"kind":"file","fileId":"file_…","sessionId":"session_…"}` (`sessionId` optional), `{"kind":"task_file","taskId":"…","path":"report.md"}`, `{"kind":"memory_note","storeId":"…","memoryId":"…"}`, `{"kind":"skill_file","skillId":"…","path":"SKILL.md"}`, `{"kind":"artifact","artifactId":"…"}`, or `{"kind":"platform_artifact","artifactId":"…","path":"index.html"}` (`path` optional). An uploaded meeting note uses its Drive `file` reference. `--workspace` names the audience workspace; these commands never move a file or send messages. For DesignSystem files, use the full native manifest path (the actual system `root` plus an `editable_files` path, or the native document reader's `libraryRef`). Omitting the platform path selects the entire artifact entry. Token readers reject `?path`; a file link never selects a sibling. Editable DesignSystem files use their original versioned writer and republish the derived files. Only editable sources accept edit grants; public access is always view-only. A share or publish reads current state then saves only the requested changes. A conflict is reported without retrying over a newer revision. All link commands print the complete JSON receipt, including `sharedUrl`, `publicUrl` and `revision`. Legacy `library share/unshare` and `artifact share` keep their original URLs and version behavior. Use the 43-character token from the end of `/library/shared/` or `/library/public/`. The default reader uses the caller's own key; `--public` chooses the anonymous view-only reader and sends no key. `--output` uses the same streaming file writer as other downloads and refuses HTTP errors before writing a file. The source revision from `links read` is an opaque string, distinct from the integer sharing-settings revision used by `links revoke`. Saving requires that exact source revision and exactly one native content/restore field; `--file` and `--data` are alternatives. Office and docx values are base64 native bytes. Save receipts name the version the original writer committed. A read-only grant returns 403; a stale revision returns 409 without retry. Public links have no save command or API writer. ## todos The checklist items (`- [ ] text @who YYYY-MM-DD`) of the Markdown documents in your Library and your workspaces, as Library › todos lists them (`GET/PATCH /library/todos`). | Command | What it does | |---|---| | `sky todos list [--workspace] [--status open\|done\|all] [--due any\|overdue\|today\|week\|none] [--source ] [--json]` | to-dos assigned to you or unassigned in your documents; `--workspace` lists every to-do you can read | | `sky todos done ` | tick it; its document saves a new version | | `sky todos reopen ` | untick it; its document saves a new version | ## routine Run agents on a schedule — see [Routines](https://agentsky.dev/docs/routines.md). | command | what it does | |---------|--------------| | `sky routine create --agent |--session --message "…" [--cron "0 9 * * 1-5"] [--timezone tz] [--budget-cents n]` | `--agent` starts a fresh session per fire; `--session` fires into one long-lived session | | `sky routine list` | routines, with cron, target and state | | `sky routine show ` | one routine, plus its last 5 runs | | `sky routine run ` | fire now, outside the schedule (works while paused) | | `sky routine pause ` | suppress scheduled fires (manual runs still work) | | `sky routine unpause ` | resume from the next occurrence — missed fires are not backfilled | | `sky routine archive ` | terminal: the schedule stops and the routine becomes immutable | | `sky routine rm ` | delete the routine: it never runs again; the sessions it started are kept | | `sky routine runs [id] [--failed] [--limit n]` | every fire attempt, newest first — of one routine or all — with the session it started or why it failed | What starts a routine besides its schedule, and where its answers go. With no flag each command shows the current setting. | command | what it does | |---------|--------------| | `sky routine inbound-url [--create\|--rm]` | a URL that starts a run when posted to, with no API token; `--create` prints it once and replaces any earlier one | | `sky routine precheck [https-url] [--rm]` | scheduled fires run only when the content at the URL changed; skipped fires cost nothing | | `sky routine result-email [on\|off]` | email you each run's last answer (routines that start a new session per fire) | | `sky routine chat-trigger --connection [--thread ] [--prefix "/report"] [--rm]` | your own message in a connected chat starts a run; the text after the prefix is its input, and the answer is posted back there | | `sky routine deliver-to --connection [--thread ] [--rm]` | post each run's answer to a conversation in a connected chat | ## link A session someone shared with you by a guest link (`/s/`). The link is the grant: any signed-in key may use it, turns bill the session's owner, and a view-only link can only be read (`GET /links//events`). | command | what it does | |---------|--------------| | `sky link send [--idempotency-key key]` | send a message through a guest link (URL or token); the same key never sends twice | | `sky link stop ` | stop the turn running on the session the link opens | ## billing Your own balance, auto top-up, usage and credit. A workspace's wallet is managed on the workspace. | command | what it does | |---------|--------------| | `sky billing show` | balance, whether a card is saved, and auto top-up | | `sky billing card` | print the address of a card form; saving a card moves no money | | `sky billing topup <10\|20\|50\|100> [--request-id id]` | buy credit with your saved card; the same request id never charges twice | | `sky billing claim ` | claim a credit invite link (agentsky.dev/i/) for your own balance; claiming twice funds once | | `sky billing auto-topup [--on\|--off] [--threshold 5\|10\|20] [--amount 10\|20\|50\|100]` | show auto top-up, or change it; turning it on needs a saved card | | `sky billing usage [--from day] [--to day] [--by day\|kind\|session\|api_key] [--source session\|api_key] [--key id] [--json]` | what you spent over UTC days (`--to` excluded; default the last 30), across sessions and API keys | | `sky billing export [same range flags] [--format csv\|jsonl] [-o file]` | every usage record in the range | | `sky billing requests [--from day] [--to day] [--key id] [--cursor c] [--json]` | one row per API request your developer API keys made, newest first; pass the printed cursor for the next page | | `sky billing coding-plan show` | whether you are on the coding plan, its monthly amount, and whether it renews | | `sky billing coding-plan join <10\|40> [--request-id id]` | join with your saved card (charged now; API gateway calls are then billed at cost), or change your monthly amount from the next renewal | | `sky billing coding-plan cancel` | stop the plan renewing; the paid month stays | | `sky billing referral-links list` | your customer discount links, their use, and what referred customers earned you (accounts on a discount plan) | | `sky billing referral-links create (--price 0.8 \| --sku id=0.8 ...) [--email address] [--uses n] [--days n]` | a discount link priced at no less than your own price; `--email` sends it to one customer and it redeems once | | `sky billing referral-links disable ` | stop a link being redeemed; customers already enrolled keep their price | ## keys Your own API keys (admin scope). Workspace keys belong to the workspace. | command | what it does | |---------|--------------| | `sky keys list` | your keys, with prefix, status, scopes and last use | | `sky keys create [--scopes read,write,admin] [--workspace slug\|personal] [--expires iso]` | make a key; prints its secret once | | `sky keys reveal ` | print a key's secret again, where the key allows it | | `sky keys revoke ` / `sky keys restore ` | stop a key at once, or let a revoked key work again | ## notes Memory notes are the documents in the Library's memory stores; every save keeps the earlier version. | command | what it does | |---------|--------------| | `sky notes stores` | the workspace's memory stores | | `sky notes list [folder/]` | the notes in a store, optionally under a folder | | `sky notes read ` | print one note | | `sky notes save (--path /new/note.md \| --note )` | write a note from a local file: a new one at `--path`, or a new version of `--note` | ## memory and context Your personal memory is what every session you start is told about you; `context` searches it, your past conversations and the workspace trace library. | command | what it does | |---------|--------------| | `sky memory list` | your entries, numbered | | `sky memory save ` / `sky memory edit ` / `sky memory rm ` / `sky memory clear` | change it; each write carries the version it read, so it never overwrites a change it did not see | | `sky context search [--scope folder] [--limit n] [--cursor c] [--since iso] [--until iso] [--json]` | the one search over everything you can open: plain words, `"exact phrase"` or `/regex/` (every matching line), `--scope` a folder such as `skills/review/`, `memory/`, `files/` or `docs/`; prints `path:line: snippet` and a `sky context read` line per hit | | `sky context read '' [--offset n]` | read a hit in full | ## webhook Your HTTPS endpoints for platform events, each signed with a `whsec_` secret. | command | what it does | |---------|--------------| | `sky webhook list` | endpoints, their events and status | | `sky webhook create --event ` | register an endpoint (for example `--event goal.completed routine_run.failed`); prints its secret once | | `sky webhook enable ` | re-enable an endpoint disabled after failed deliveries | | `sky webhook rm ` | delete it | ## hook-rule Workspace hook rules run a webhook or block at platform-controlled points: `session.start`, `tool.call`, `spend`, and `turn.end`. Rules are evaluated in order; the first matching rule decides. See `PUT /v1/hook_rules` in the [API reference](https://agentsky.dev/docs/api#group-webhooks) for the rule schema. | command | what it does | |---------|--------------| | `sky hook-rule list [--workspace ] [--json]` | the workspace's top-level hook rules (no `--workspace`) or the workspace-level hooks for a specific workspace | | `sky hook-rule set [--workspace ] [--rules ]` | replace the entire ordered list; reads JSON from stdin when `--rules` is omitted | ## vendor External vendor accounts: third-party API keys and agent logins your agents use (such as OpenAI, Anthropic, or other model providers connected as accounts). | command | what it does | |---------|--------------| | `sky vendor list [--json]` | the workspace's vendor accounts, with status and last use | | `sky vendor add [--label ] [--credential ]` | connect a vendor account; prompts for the credential when `--credential` is omitted | | `sky vendor rm ` | disconnect a vendor account | | `sky vendor usage [--json]` | the agents and tasks that used this account | ## browser-profile Your browser: the sites your agents stay signed in to between sessions. It is yours in every workspace; share it with one member or the whole workspace, for every site or only some. Needs your own `ast_` key; a workspace key lists only what is shared with the whole workspace. | command | what it does | |---------|--------------| | `sky browser-profile list [--json]` | your browser, whom you shared it with, and the browsers shared with you here | | `sky browser-profile sites [--json]` | the sites your browser is signed in to | | `sky browser-profile import [--file ]` | import a Playwright storage-state JSON file into your browser; reads stdin when `--file` is omitted | | `sky browser-profile remove-site ` | sign your browser out of a site everywhere (the agent will need to sign in again) | | `sky browser-profile share [--member ] [--sites ]` | share your browser with one member, or everyone here; every site unless `--sites` | | `sky browser-profile unshare ` | stop a share; it ends from the grantee's next turn | ## mac Your Macs, through the This Mac relay: what each one lends to agents now, and asking one to lend a folder, a browser window or its screen. The person answers on the Mac (the folder picker, or Allow / Deny); the CLI only asks and waits. Needs your own `ast_` key: a workspace key cannot see or ask for what a person lends. | command | what it does | |---------|--------------| | `sky mac list [--json]` | your Macs: online or not, what each can lend (and why not the screen), and the folders, browser windows and screens lent now | | `sky mac ask --reason [--no-wait] [--json]` | ask a Mac to lend one (`host`: its local Claude Code and Codex chats, call notes, uploads, saves to Downloads and local git, every change confirmed on the Mac); prints the `mcp_url` a session attaches once granted, and waits up to four minutes for the answer (exit 1 when it is denied, unavailable or expired) | | `sky mac ask-status [--wait ] [--json]` | where an ask stands: `pending`, `asking`, `granted`, `denied`, `unavailable` or `expired` | | `sky mac take-back ` | end a folder, browser window, screen or apps and files lent from a Mac | ## notifications Email and browser push notification preferences for tasks. | command | what it does | |---------|--------------| | `sky notifications get [--json]` | the current email and push preferences | | `sky notifications unsubscribe-marketing` | stop AgentSky marketing email to your address; login and account email continue | | `sky notifications set [--email-needs-you true\|false] [--email-done true\|false] [--push-needs-you true\|false] [--push-done true\|false]` | update one or more settings | ## help Search the AgentSky help center or read an article. | command | what it does | |---------|--------------| | `sky help search [--top n] [--json]` | full-text search across help articles (returns up to 20) | | `sky help get [--json]` | read a full help article by its slug | ## api Every operation in the [API reference](https://agentsky.dev/docs/api) from the terminal, with your logged-in key: the same route, checks and answer as any other client (like `gh api`). The named commands above are the everyday interface; `sky api` reaches everything else a person can do on agentsky.dev or in the Mac app. | command | what it does | |---------|--------------| | `sky api search ` | list the operations whose method, path or summary contain every word | | `sky api [-q key=value]… [-d json\|@file\|-] [--workspace slug] [--idempotency-key k] [-o file]` | send one request, e.g. `sky api PATCH /threads/thr_123 -d '{"title":"Q3 plan"}'`; prints the JSON answer, exits 1 on an error answer; `-o` saves a download | ## channel Chat surfaces (Slack, Telegram, Discord, WhatsApp, iMessage, email, Teams, X, Lark, Google Chat, LINE) routed to a session. Building your own bot on channel webhooks, deliveries and reactions is on the API (`/channels/…` in `/docs/api`). | command | what it does | |---------|--------------| | `sky channel list` | your connections, their status and the session each routes to | | `sky channel connect [--session sess_…] [--wait]` | connect a surface and print the link or code that finishes it; `--webhook ` routes it to a channel webhook instead, `--app ` runs it on your own bot or number, `--label` names it | | `sky channel bind --session sess_…` | route a connection to a session (or `--webhook `) | | `sky channel send ` | post a markdown message into a connected thread | | `sky channel history [--limit n] [--cursor token]` | read one authorized conversation history page as JSON; start with the newest page, then pass `next_cursor` to retrieve older messages | | `sky channel disconnect ` | disconnect a surface | | `sky channel apps` | your own bots and numbers registered as channel apps | | `sky channel identities` | chat identities whose messages run as you | | `sky channel unlink ` | unlink one; that person's messages stop running as you | History reads do not start a turn or download files. `history_source` distinguishes provider history from retained SDK messages; unsupported channels return 501. Cursors expire after an hour and stay bound to the same caller, thread, installation and page size. Repeating a cursor safely retries the same page. ## connector Apps your agents can use. Authorization runs in your browser; sky listens on a loopback port for its return and records the account. | command | what it does | |---------|--------------| | `sky connector apps [query]` | apps you can connect, searched by name (`--limit n`) | | `sky connector list` | the accounts you connected | | `sky connector connect ` | connect an app by its slug; `--universe ` connects it for the workspace itself (owner or admin), `--no-open` only prints the link | | `sky connector disconnect ` | disconnect one; agents lose it at once | | `sky connector repos` | repositories your GitHub connection can reach | | `sky connector workspace [slug]` | connections shared with a workspace (default: the current one) | | `sky connector share [--universe ]` | share one of yours with a workspace, so its sessions can call through it | | `sky connector unshare [--universe ]` | stop sharing it | | `sky connector shared` | everything you share, and the calls made through each | | `sky connector grant [--key ] [--universe ]` | let a workspace's API keys (or one key) call through one of your connections with `POST /connectors/proxy`; separate from sharing, which is for sessions | | `sky connector revoke [--key ] [--universe ]` | take that grant back; the service loses it at once | | `sky connector grants ` | who may call through one of your connections with a key, and how often they did | | `sky connector granted` | connections other people let this key call through | ## vault Credentials a session is granted when it starts (`vault_ids` on `POST /sessions`). A value is read from stdin when piped, otherwise from a prompt that does not echo; it is never printed back. | command | what it does | |---------|--------------| | `sky vault list` | the workspace's vaults | | `sky vault create ` | create one; prints its `vlt_` id | | `sky vault show ` | its secrets — names, kinds, hosts, status; never values | | `sky vault rm [-y]` | delete it and every secret in it | | `sky vault move --from [--dry-run]` | move the secrets of other vaults into this one | | `sky vault secret add --host ` | add an environment variable sent only to those hosts (`--header `, `--template 'Bearer {secret}'`); with `--mcp `, store a bearer token for that MCP server instead | | `sky vault secret rotate ` | replace its value in place, running sessions keep working (`--host` re-scopes, `--keep-value` changes only the hosts) | | `sky vault secret rm ` | delete a secret | | `sky vault secret validate ` | check that an MCP OAuth grant still works | ## catalog The agents you can hire by `@handle`. Reading needs no login; logged in, you also see your own workspace's listings. Hire one with `sky session create @handle --message "…"`. | command | what it does | |---------|--------------| | `sky catalog search [words…] [--category id] [--json]` | every hireable agent, or only those matching the words | | `sky catalog show [--json]` | what one agent does and does not do, with the command that hires it | | `sky catalog categories` | the categories a listing can carry | | `sky catalog publish --handle --tagline "…" [--description "…"] [--category id]… [--does "…"]… [--does-not "…"]… [--example "…"]…` | list one of your agents under a handle, visible to your workspace; publishing again replaces the listing. A handle is 2 to 40 lower-case letters, digits or underscores | | `sky catalog unpublish ` | take your listing down; the agent itself is untouched | | `sky catalog delist --reason "..."` | immediately hide a harness and reject new acquisition; platform operators only, verified after writing | | `sky catalog relist --reason "..."` | restore a runnable harness; platform operators only, verified after writing | ## skill Teaches the agents installed on this machine to hand work to AgentSky agents. | command | what it does | |---------|--------------| | `sky skill install [--agent claude_code\|codex\|cursor…] [--dry-run] [--json]` | add the AgentSky skill to each installed agent, or only the ones named; `--dry-run` writes nothing | ## goal Give a session a standing objective. After each turn the platform checks the work against the goal's criteria and either takes another turn or stops. | command | what it does | |---------|--------------| | `sky goal set "" [--criterion "…"]… [--check " :: "]… [--max-iterations n] [--max-spend-cents n] [--max-tokens n] [--max-minutes n]` | set the goal, replacing any existing one; work starts right away. Omit criteria and they are written from the objective. A `--check` command runs in the workspace and must exit 0 | | `sky goal get [--json]` | the goal, its limits and usage, and what the last check found | | `sky goal edit [--objective "…"] [--criterion …] [--check …] [limits]` | change it in place; iterations and spend so far carry over. A limit of `none` lifts it | | `sky goal pause ` | stop taking turns and interrupt the run the goal started | | `sky goal resume ` | start taking turns again; a deadline moves out by the paused time | | `sky goal clear ` | stop the goal for good | ## workspace `sky workspace` is the preferred user-facing alias for the `sky universe` commands. | command | what it does | |---------|--------------| | `sky workspace list` | workspaces you can reach (alias of `sky universe list`) | | `sky workspace delete [-y]` | permanently delete a workspace you own (alias of `sky universe delete`) | | `sky workspace rename ` | rename a workspace you own; the slug stays (alias of `sky universe rename`) | | `sky workspace context` | what every session in the workspace loads at start (AGENTS.md, MEMORY.md, the indexes, your member page) with sizes and limits | ## universe | command | what it does | |---------|--------------| | `sky universe list` | universes you can reach | | `sky universe create ` | create one | | `sky universe switch ` | scope every later command to it | | `sky universe show` | the current universe | | `sky universe members [slug]` | everyone in the workspace with their role, and the waiting invitations (owners and admins) | | `sky universe invite … [--role admin\|member] [--send-email] [--universe slug]` | invite people by email (owners and admins); prints each person's `sky universe join` token | | `sky universe uninvite [--universe slug]` | withdraw an invitation nobody has taken up | | `sky universe role [--universe slug]` | change a member's role, by user id or email (owner only) | | `sky universe remove [--universe slug]` | remove a member, by user id or email (owner only) | | `sky universe join ` | accept an invitation sent to your verified email | | `sky universe delete [-y]` | permanently delete a workspace you own and everything in it: prints what will go, then asks you to type the slug (`-y` skips that). Its sessions are stopped, its wallet's credit returns to you; not your default workspace | | `sky universe rename ` | rename a workspace you own (`PATCH /universes/{slug}`); the slug stays | ## policy Rules a workspace's sessions, or one session, run under. Without `--session` a command acts on the current workspace's rules. | command | what it does | |---------|--------------| | `sky policy list [--session [--effective]] [--universe slug]` | the rules; `--effective` shows every layer a session runs under | | `sky policy add --name "…" --kind TOOL_CALL\|SPEND_CAP\|CALL_COUNT\|CAPABILITY --config '' [--position n] [--disabled] [--session ]` | add a rule (workspace owner, or the session's owner). Configs: `{"action":"deny","matcher":"Bash","commandPatterns":["rm -rf *"]}`, `{"hardCapUsdMicros":5000000}`, `{"limitPerSession":200}`, `{"platform.orchestration":"deny"}` | | `sky policy enable [--session ]` | switch a rule on | | `sky policy disable [--session ]` | switch a rule off; it is kept so it can be switched on again | | `sky policy rm [--session ]` | delete a rule | ## secrets | command | what it does | |---------|--------------| | `sky secrets set KEY=VALUE …` | set values for keys declared in the spec (write-only) | | `sky secrets list ` | which keys are set — values never come back | | `sky secrets unset KEY` | remove one key | ## A full session ```bash $ npm i -g @agentsky/sky $ sky auth login Logged in as dev@example.com (universe: dev-3f2a) $ sky launch Scaffolded agent.toml — edit it anytime; re-apply with `sky deploy`. Launched agent_cmtb2f0a8m9k7x51q4ez6r0d (hermes · deepseek-v4-pro) # spec + first session $ sky agent message agent_cmtb2f0a8m9k7x51q4ez6r0d "What can you do?" I'm a persistent research agent — give me a topic and I'll dig in. $ sky secrets set agent_cmtb2f0a8m9k7x51q4ez6r0d NOTION_TOKEN=secret_… set NOTION_TOKEN ``` ## compare Run a task on two or more agents side-by-side and record your decision. A comparison is a playground with more than one column; each column is a session. | command | what it does | |---------|--------------| | `sky compare create --agent --llm --task "…" --compare …` | create a comparison: one primary agent and one or more compare agents, all starting the same task; prints the comparison id and column session ids | | `sky compare list [--archived] [--json]` | workspace comparisons, newest first | | `sky compare show [--json]` | one comparison: columns, arena status, and vote result if revealed | | `sky compare pick (--winner \| --tie \| --both-bad) [--json]` | record your winner pick; `--tie` or `--both-bad` for draws; prints the revealed agents once the vote is recorded | ## thread Conversations as the web rail shows them: one agent, or several side by side. Every command calls `/v1/threads…`; an agent column is named by the column id `sky thread show` prints. | command | what it does | |---------|--------------| | `sky thread list [--archived\|--all] [--json]` | workspace threads, newest first | | `sky thread start "" --agent [--llm ] [--effort ] [--vs …] [--saved-agent ] [--workspace ] [--plan] [--json]` | start a thread and send its first message, as the web composer does | | `sky thread show [--json]` | one thread: every agent and the session it runs in | | `sky thread rename ""` | rename a thread | | `sky thread pin <id>` | pin a thread to the top of the rail | | `sky thread unpin <id>` | unpin a thread | | `sky thread archive <id>` | put a thread away | | `sky thread unarchive <id>` | bring an archived thread back | | `sky thread mark-read <id>` | mark a thread read | | `sky thread mark-unread <id>` | mark a thread unread | | `sky thread delete <id>` | delete a thread with its sessions | | `sky thread send <id> "<text>" [--agent <columnId>…] [--json]` | send one message to every agent, or only the ones named | | `sky thread add-agent <id> --agent <type> [--llm <model>] [--effort <level>]` | add an agent to the thread | | `sky thread remove-agent <id> <columnId>` | take one agent out; its session is left as it is | | `sky thread stop <id> [--agent <columnId>]` | stop what the thread's agents are doing | | `sky thread fork <id> [--agent <columnId>] [--at <messageId>] [--to <type:model>]` | continue one agent's conversation in a thread of its own | | `sky thread model <id> [--agent <columnId>] [--llm <model>] [--effort <level>\|default]` | change the model or thinking level one agent runs on | | `sky thread search "<query>" [--archived\|--all] [--cursor <cursor>]` | search the words of your conversations | | `sky thread options` | what a new thread can start on: agents, models, thinking levels and grantable tools | | `sky thread join <sessionId>` | open a teammate's thread, or one shared with you | ## project The GitHub repositories a new thread can start on (`/v1/projects`). | command | what it does | |---------|--------------| | `sky project list [--json]` | repositories a new thread can start on, the last one chosen starred | | `sky project use [owner/name] [--branch <branch>]` | remember a repository for your next new thread, or none when omitted | ### session feedback Thumbs-up/down reactions on individual messages. | command | what it does | |---------|--------------| | `sky session feedback <id> [--json]` | read thumbs-up/down feedback recorded on this session's messages | ### session side-chat Ask a question about a session's transcript, answered outside the main conversation. | command | what it does | |---------|--------------| | `sky session side-chat <id> --question "…" --transcript "…" [--quote "…"] [--json]` | ask a side question; the answer is returned directly, never sent to the agent | ### session code-review Run the code-review plane on a diff or file, using the session's context. | command | what it does | |---------|--------------| | `sky session code-review <id> --diff "…" [--filename <name>] [--json]` | review a diff or file; returns a review summary and a list of findings | ### session snapshot Read or revoke the public share link for a session. | command | what it does | |---------|--------------| | `sky session snapshot <id> --thread <threadId> [--revoke] [--json]` | read the snapshot link; `--revoke` clears it | --- # agent.toml reference A fly.toml-style declaration of your agent. Optional at every layer — `sky launch` scaffolds it, a flags-only path exists for everything, and the file never crosses the wire (the CLI validates and inlines it client-side). ## Full schema Only `name` is required; `type` defaults to pi and `llm` to the type's default model. Long markdown can live inline in triple-quoted strings or in sibling files via `file =` / `prompt_file =` — the CLI inlines their content before calling the API. ```toml # agent.toml — everything the create wizard can set. Only `name` is required. name = "research-agent" description = "Web research with citations" type = "hermes" # hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode llm = "deepseek-flash" # must be compatible with type reasoning_effort = "high" # per harness; omit for the harness's own default capabilities = ["exa.search"] # capability ids from the catalog vcpus = 2 # optional machine size for launch's first session: 1|2|4|8 memory_mb = 4096 # 256MB steps, 256-2048MB per vCPU hiring_policy = "deny" # its sessions work alone; "allow" (default) lets them hire helpers environment = "env_..." # the environment its sessions boot in when they name none # The user prompt layer; the platform preamble is composed server-side. # Inline here, or `prompt_file = "prompt.md"`. prompt = """ You are a meticulous research agent… """ [[instructions]] # additional .md files for the agent name = "style.md" file = "instructions/style.md" # or inline: content = """…""" [[skills]] # a store skill, attached by reference type = "skill" skillId = "skill_…" # from POST /v1/skills version = "latest" # or a revision id (skr_…) to pin [[skills]] # or a GitHub repo: its root .claude/skills/ type = "github" # is loaded at every session start url = "https://github.com/org/repo" ref = "main" # optional branch or commit SHA [[customData]] id = "prices" name = "Price sheet" kind = "spreadsheet" # spreadsheet | doc | private_api | other uri = "https://…" ``` Secrets are not spec data and have no key here: set them after launch with `sky secrets set <id> KEY=VALUE` (values are write-only, encrypted at rest). ## Lifecycle `sky launch` creates the agent spec + its first session and stamps `id = "agent_…"` (the agent id — session ids never live in the file); from then on `sky deploy` re-applies your edits (prompt, display name, type, model, capabilities, skills, hiring policy, environment) to the spec, idempotently — the prompt applies to sessions started after the save. `-f <path>` overrides the `./agent.toml` discovery on both commands. A `slug` left over from before agents were id-addressed is ignored and dropped the next time the file is stamped; stamping rewrites only that line, so the file's comments stay.