2. In the API Tokens section, click Create Token and give it a label (e.g., "production").
3. Copy the token immediately — it's only shown once. Store it securely in your server environment variables.
Authentication
All API requests are authenticated using a Bearer token in the Authorization header.
Authorization: Bearer ha_live_your_token_here
Tokens are scoped to your organization. Any agent created via the API will appear in your dashboard.
API Reference
POST/api/v1/agents
Create a new coding agent with an optional initial prompt. The endpoint provisions a Docker container, waits for it to be ready (up to 60s), and returns the agent URL and any sessions created.
Request Headers
Authorization
Bearer ha_live_... (required)
Content-Type
application/json
Request Body
Field
Type
Description
name
string
Agent name (1-100 characters). Required.
initialPrompt
string
Instructions sent to the agent on startup as a visible user message. Optional.
hiddenPrompt
string
System-level instructions injected as a hidden context file. Not visible to the user in the chat UI. Optional.
scopeId
string
Assign the agent to a scope by ID. The agent will only receive secrets from this scope. Optional.
scope
string
Assign the agent to a scope by name (alternative to scopeId). Optional.
model
string
Override the default model for this agent. If omitted, the org default is used. Available models:
Send a message to an agent. If no sessionId is provided, a new session is created automatically. The message is sent asynchronously — the endpoint returns immediately and the agent begins processing in the background.
Request Body
Field
Type
Description
message
string
The message to send. Required.
sessionId
string
Target session ID. Optional — if omitted, a new session is created.
Response (200)
{
"sessionId": "ses_abc123...",
"sent": true
}
sessionId
The session the message was sent to (useful when a new session was auto-created).
sent
Always true on success.
Error Responses
401
Invalid or missing API token.
400
Invalid JSON body, missing message, or agent is not running.
404
Agent not found.
502
Agent is not responding or failed to create session / send message.
POST/api/v1/agents/:agentId/start
Start a STOPPED agent. The endpoint restarts the agent's container and waits up to 30s for it to become healthy before returning. If the agent is already RUNNING, the call succeeds with started: false.
true if the container was started, false if it was already running.
Error Responses
401
Invalid or missing API token.
400
Agent is not in a startable state (e.g. PROVISIONING or ERROR).
404
Agent not found.
503
Container failed to start, or concurrent agent cap reached.
POST/api/v1/agents/:agentId/stop
Stop a RUNNING agent. The container is gracefully stopped (3s timeout) and the agent's status is set to STOPPED. The agent's sessions and history are preserved and it can be restarted later.
true if the container was stopped, false if it was already stopped.
Error Responses
401
Invalid or missing API token.
400
Agent is not in a stoppable state (e.g. PROVISIONING or ERROR).
404
Agent not found.
503
Container failed to stop.
POST/api/v1/agents/:agentId/restart
Restart an agent. If the agent is RUNNING, it is stopped first, then started again. If already STOPPED, it is started directly. Waits up to 30s for the container to become healthy before returning.
Per-session breakdown with the same cost/tokens structure.
Error Responses
401
Invalid or missing API token.
400
Agent is not running.
404
Agent not found.
502
Agent is not responding.
POST/api/v1/agents/:agentId/abort
Cancel an in-progress generation — the equivalent of pressing Esc in the OpenCode TUI. The agent stays running and can receive new messages immediately. To stop the agent container entirely, use the stop endpoint instead.
Path Parameters
agentId
The agent ID. Required.
Request Body (optional)
Field
Type
Description
sessionId
string
Abort a specific session. If omitted, aborts all sessions on the agent.
// Abort a specific session
{ "sessionId": "ses_abc123..." }
// Abort all sessions (empty body or omit entirely)
{}
Response (200)
// With sessionId
{ "agentId": "clx1abc2def3...", "sessionId": "ses_abc123...", "aborted": true }
// Without sessionId — aborts all sessions
{ "agentId": "clx1abc2def3...", "aborted": ["ses_abc123...", "ses_def456..."] }
Error Responses
401
Invalid or missing API token.
400
Agent is not running.
404
Agent not found.
502
Agent is not responding.
POST/api/v1/agents/:agentId/files
Upload a file into the agent's filesystem at /workspace/uploads/. The agent can read and reference uploaded files in subsequent messages. Accepts multipart/form-data.
Path Parameters
agentId
The agent ID. Required.
Request Body (multipart/form-data)
Field
Type
Description
file
File
The file to upload. Max 50MB per file, 100MB total per agent.
Missing file, disallowed file type, file exceeds 50MB, or agent total exceeds 100MB.
404
Agent not found.
502
Failed to write file to agent container.
Permissions & Questions
Agents may pause and wait for human input in two ways: a permission request (the agent wants to perform an action and needs approval) or a question (the agent needs structured input before it can continue). Poll these endpoints while the agent is busy and respond when requests are pending.
GET/api/v1/agents/:agentId/permissions
Returns all pending permission requests for the agent. A permission request is created when the agent wants to run a tool (e.g. execute a shell command) that requires explicit approval. The agent is paused until a reply is submitted.
once — allow this one time. always — allow and remember for future requests matching this pattern. reject — deny the action.
message
string
Optional message to pass back to the agent (useful when rejecting to explain why).
Response (200)
{ "replied": true, "reply": "once" }
Errors
400
Agent is not running, or reply is not one of the accepted values.
404
Agent not found.
502
Agent container is not responding.
GET/api/v1/agents/:agentId/questions
Returns pending questions the agent is waiting for the user to answer. Questions are structured multiple-choice prompts. The agent is paused until a reply is submitted.
Response (200)
{
"questions": [
{
"id": "q_abc123",
"questions": [
{
"question": "How would you like me to implement this?",
"header": "Approach",
"options": [
{ "label": "Simple", "description": "Quick, minimal implementation" },
{ "label": "Full", "description": "Complete with tests and error handling" }
],
"multiSelect": false
}
]
}
]
}
Field
Description
id
Unique question ID. Pass this to the reply endpoint.
questions[].question
The question text shown to the user.
questions[].options
Available answer choices, each with a label and description.
questions[].multiSelect
If true, multiple options can be selected in the reply.
Submit answers to a pending question. The answers field is an array of arrays — one inner array per question in the questions array, containing the selected option label(s). The agent resumes immediately after a reply is submitted.
Request Body
{
"answers": [
["Simple"] // one inner array per question; include multiple labels for multiSelect
]
}
Each inner array corresponds to the question at the same index in questions[]. For single-select questions provide one label. For multiSelect questions provide multiple labels in the inner array.
Response (200)
{ "replied": true }
Errors
400
Agent is not running, or answers is missing or not an array.
404
Agent not found.
502
Agent container is not responding or question ID is invalid.
Scopes
Scopes let you group agents and secrets together. Agents in a scope only receive the secrets belonging to that scope. Agents and secrets without a scope operate at the organization level.
Delete a scope. Agents and secrets in the scope will become org-level (unscoped). Running containers are not affected.
Path Parameters
scopeId
The scope ID. Required.
Response (200)
{ "deleted": true }
Secrets
Secrets are encrypted environment variables injected into agent containers at startup. Org-level secrets are injected into unscoped agents. Scoped secrets are injected only into agents in that scope.
GET/api/v1/secrets
List org-level secrets (keys only, values are never returned).
# Get messages from a specific session
curl "https://hostedagents.ai/api/v1/agents/YOUR_AGENT_ID/messages?sessionId=ses_abc123" \
-H "Authorization: Bearer ha_live_your_token_here"
# Get messages from ALL sessions
curl https://hostedagents.ai/api/v1/agents/YOUR_AGENT_ID/messages \
-H "Authorization: Bearer ha_live_your_token_here"
curl -X POST https://hostedagents.ai/api/v1/agents \
-H "Authorization: Bearer ha_live_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-agent",
"scope": "production",
"initialPrompt": "Build a REST API"
}'
# The agent receives only secrets from the "production" scope
Polling for Completion
After sending a message, the agent processes it asynchronously. Use the busy field from the agent status endpoint to poll until the task is complete:
async function waitForAgent(agentId: string, token: string) {
const headers = { Authorization: `Bearer ${token}` };
const base = "https://hostedagents.ai/api/v1/agents";
while (true) {
const res = await fetch(`${base}/${agentId}`, { headers });
const data = await res.json();
if (!data.busy) {
// Agent is idle — task is complete
return data;
}
// Wait 2 seconds before checking again
await new Promise((r) => setTimeout(r, 2000));
}
}
Embedding in Your App
The agent's coding environment runs in a standard web page and can be embedded directly in an iframe. No authentication is required to access the agent once it's running.
Server-side: Create the Agent
Call the API from your backend to create an agent and get the session URL:
A complete example showing how to create an agent, send follow-up messages, poll for completion, and embed the environment:
const API = "https://hostedagents.ai/api/v1";
const TOKEN = process.env.HOSTEDAGENTS_API_TOKEN;
const headers = {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
};
// 1. Create an agent
const createRes = await fetch(`${API}/agents`, {
method: "POST",
headers,
body: JSON.stringify({
name: "build-agent",
initialPrompt: "Set up a Node.js Express project with TypeScript",
}),
});
const { agentId, sessions } = await createRes.json();
const sessionId = sessions[0]?.id;
// 2. Wait for the initial prompt to finish
async function waitUntilIdle() {
while (true) {
const res = await fetch(`${API}/agents/${agentId}`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const data = await res.json();
if (!data.busy) return data;
await new Promise((r) => setTimeout(r, 2000));
}
}
await waitUntilIdle();
// 3. Send a follow-up message to the same session
await fetch(`${API}/agents/${agentId}/messages`, {
method: "POST",
headers,
body: JSON.stringify({
sessionId,
message: "Now add user authentication with JWT",
}),
});
await waitUntilIdle();
// 4. Retrieve the full conversation
const msgRes = await fetch(
`${API}/agents/${agentId}/messages?sessionId=${sessionId}`,
{ headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { messages } = await msgRes.json();
console.log(`Total messages: ${messages.length}`);
Keep your API token on your server. Never expose it to the client. The session URL is safe to pass to the browser — it only grants access to that specific agent session.