Create a session
curl --request POST \
--url https://agp.eu.hcompany.ai/api/v2/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent": {},
"messages": {},
"max_steps": 123,
"max_time_s": 123,
"idle_timeout_s": 123,
"delete_after_min": 123,
"delete_screenshot_after_min": 123,
"queue": true,
"group_id": "<string>",
"parent_session_id": "<string>",
"overrides": {}
}
'import requests
url = "https://agp.eu.hcompany.ai/api/v2/sessions"
payload = {
"agent": {},
"messages": {},
"max_steps": 123,
"max_time_s": 123,
"idle_timeout_s": 123,
"delete_after_min": 123,
"delete_screenshot_after_min": 123,
"queue": True,
"group_id": "<string>",
"parent_session_id": "<string>",
"overrides": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
agent: {},
messages: {},
max_steps: 123,
max_time_s: 123,
idle_timeout_s: 123,
delete_after_min: 123,
delete_screenshot_after_min: 123,
queue: true,
group_id: '<string>',
parent_session_id: '<string>',
overrides: {}
})
};
fetch('https://agp.eu.hcompany.ai/api/v2/sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://agp.eu.hcompany.ai/api/v2/sessions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'agent' => [
],
'messages' => [
],
'max_steps' => 123,
'max_time_s' => 123,
'idle_timeout_s' => 123,
'delete_after_min' => 123,
'delete_screenshot_after_min' => 123,
'queue' => true,
'group_id' => '<string>',
'parent_session_id' => '<string>',
'overrides' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://agp.eu.hcompany.ai/api/v2/sessions"
payload := strings.NewReader("{\n \"agent\": {},\n \"messages\": {},\n \"max_steps\": 123,\n \"max_time_s\": 123,\n \"idle_timeout_s\": 123,\n \"delete_after_min\": 123,\n \"delete_screenshot_after_min\": 123,\n \"queue\": true,\n \"group_id\": \"<string>\",\n \"parent_session_id\": \"<string>\",\n \"overrides\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://agp.eu.hcompany.ai/api/v2/sessions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"agent\": {},\n \"messages\": {},\n \"max_steps\": 123,\n \"max_time_s\": 123,\n \"idle_timeout_s\": 123,\n \"delete_after_min\": 123,\n \"delete_screenshot_after_min\": 123,\n \"queue\": true,\n \"group_id\": \"<string>\",\n \"parent_session_id\": \"<string>\",\n \"overrides\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://agp.eu.hcompany.ai/api/v2/sessions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"agent\": {},\n \"messages\": {},\n \"max_steps\": 123,\n \"max_time_s\": 123,\n \"idle_timeout_s\": 123,\n \"delete_after_min\": 123,\n \"delete_screenshot_after_min\": 123,\n \"queue\": true,\n \"group_id\": \"<string>\",\n \"parent_session_id\": \"<string>\",\n \"overrides\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"request": {},
"status": {},
"agent_view_url": "<string>",
"created_at": "<string>"
}Sessions
Create a session
Launch a new agent run.
POST
/
api
/
v2
/
sessions
Create a session
curl --request POST \
--url https://agp.eu.hcompany.ai/api/v2/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent": {},
"messages": {},
"max_steps": 123,
"max_time_s": 123,
"idle_timeout_s": 123,
"delete_after_min": 123,
"delete_screenshot_after_min": 123,
"queue": true,
"group_id": "<string>",
"parent_session_id": "<string>",
"overrides": {}
}
'import requests
url = "https://agp.eu.hcompany.ai/api/v2/sessions"
payload = {
"agent": {},
"messages": {},
"max_steps": 123,
"max_time_s": 123,
"idle_timeout_s": 123,
"delete_after_min": 123,
"delete_screenshot_after_min": 123,
"queue": True,
"group_id": "<string>",
"parent_session_id": "<string>",
"overrides": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
agent: {},
messages: {},
max_steps: 123,
max_time_s: 123,
idle_timeout_s: 123,
delete_after_min: 123,
delete_screenshot_after_min: 123,
queue: true,
group_id: '<string>',
parent_session_id: '<string>',
overrides: {}
})
};
fetch('https://agp.eu.hcompany.ai/api/v2/sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://agp.eu.hcompany.ai/api/v2/sessions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'agent' => [
],
'messages' => [
],
'max_steps' => 123,
'max_time_s' => 123,
'idle_timeout_s' => 123,
'delete_after_min' => 123,
'delete_screenshot_after_min' => 123,
'queue' => true,
'group_id' => '<string>',
'parent_session_id' => '<string>',
'overrides' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://agp.eu.hcompany.ai/api/v2/sessions"
payload := strings.NewReader("{\n \"agent\": {},\n \"messages\": {},\n \"max_steps\": 123,\n \"max_time_s\": 123,\n \"idle_timeout_s\": 123,\n \"delete_after_min\": 123,\n \"delete_screenshot_after_min\": 123,\n \"queue\": true,\n \"group_id\": \"<string>\",\n \"parent_session_id\": \"<string>\",\n \"overrides\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://agp.eu.hcompany.ai/api/v2/sessions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"agent\": {},\n \"messages\": {},\n \"max_steps\": 123,\n \"max_time_s\": 123,\n \"idle_timeout_s\": 123,\n \"delete_after_min\": 123,\n \"delete_screenshot_after_min\": 123,\n \"queue\": true,\n \"group_id\": \"<string>\",\n \"parent_session_id\": \"<string>\",\n \"overrides\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://agp.eu.hcompany.ai/api/v2/sessions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"agent\": {},\n \"messages\": {},\n \"max_steps\": 123,\n \"max_time_s\": 123,\n \"idle_timeout_s\": 123,\n \"delete_after_min\": 123,\n \"delete_screenshot_after_min\": 123,\n \"queue\": true,\n \"group_id\": \"<string>\",\n \"parent_session_id\": \"<string>\",\n \"overrides\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"request": {},
"status": {},
"agent_view_url": "<string>",
"created_at": "<string>"
}Creates a new session that runs an agent against the given task. When a slot is available the session starts in
pending status and transitions to running once the agent launches. A create above your concurrency limit is accepted as queued and starts automatically when a slot frees up (set queue: false to get a 429 instead).
Returns the created Session object with status pending or queued.
Headers
string
Optional idempotency key (max 255 characters). Safe retry within 24 hours: reusing the same key with a different body returns
422.Request body
string | object
required
Either a catalog identifier (string, e.g. Inline agent, with environments nested under it:See Browser for its config and fields.
"h/web-surfer-flash") or an inline Agent object. An inline agent must include its own environments (at most one per kind), unless it is a pure manager that only delegates to subagents; the session resolves only agent and reads everything else (environments, skills, subagents) from there.Using a catalog id (environments come from the agent’s stored spec):"agent": "h/web-surfer-flash"
"agent": {
"name": "weather-agent",
"description": "Looks up weather by city",
"environments": [
{
"id": "browser",
"kind": "web",
"mode": {"type": "visual", "width": 1280, "height": 720},
"start_url": "https://www.bing.com/"
}
]
}
string | object | array
Initial messages queued before the agent’s first step. Usually a single user message describing the task. A plain string is accepted as shorthand for one user message.Each message object has:
type(string, optional):"user_message", the default.message(string): The instruction or task description.images(array, optional): Base64 data URIs to attach (e.g.data:image/png;base64,...).caller_id(string, optional): Identifies the message sender. Defaults touser; leave it unset for normal user input.
"messages": [
{"type": "user_message", "message": "Book a flight from Paris to Tokyo on June 15"}
]
integer
Cap on the number of steps the agent may take, where each step is one decide-and-act cycle. On reaching the cap the agent is asked to produce a final answer from what it has so far (it is not hard-killed), so you still get a structured result. Omit it to run uncapped.
number
Cap on wall-clock seconds. On reaching the cap, like
max_steps, the agent is asked for a final answer rather than terminated abruptly. Omit it to run uncapped.integer
Switches between one-shot and interactive. Leave it
null for a one-shot task: the session ends as soon as the agent answers. Set it (in seconds) to keep the session open for follow-up messages: after each answer the session enters the idle status and waits this long for your next message before terminating.integer
default:"43200"
Minutes after the session finishes before it is automatically deleted, along with its events and screenshots. Defaults to 30 days. Set
null to keep the session forever.integer
default:"43200"
Minutes after the session finishes before its screenshots are deleted, so you can expire visual data sooner than the session record. Defaults to 30 days. Set
null to keep screenshots for the session’s lifetime.boolean
default:"true"
When you are at your concurrency limit, accept this session into a queue (status
queued) instead of rejecting it with 429. Queued sessions don’t count against your quota and start automatically, oldest first, as running sessions finish. Ideal for batch workloads: fire N tasks, then collect results via webhooks. Set false to fail fast with 429 when at capacity.string
Tag for grouping related sessions. You can later query all sessions with
GET /sessions?group_id=....string
ID of a parent session, for multi-agent orchestration. The parent’s status endpoint will include this session in its
subagent_session_ids list. Child sessions never queue: at capacity the create fails with 429 even when queue is true, because queueing a child behind its own parent’s slot would deadlock the parent.object
Per-run tweaks applied after the agent (and its environments, skills, and subagents) are resolved, so you can adjust a catalog agent for a single run without editing its stored spec. Keys are dotted paths into the request. List members are addressed with an explicit
[field=value] selector. Each value is validated against the field its path targets, so an unknown path or a wrong type is rejected with 422 at creation.Common uses: point the browser at a different start URL, or ask a catalog agent for structured output by overriding its answer_format."overrides": {
"agent.environments[kind=web].start_url": "https://www.bing.com/",
"agent.answer_format": {
"type": "object",
"properties": {"price": {"type": "number"}},
"required": ["price"]
}
}
Response
string
Unique session identifier.
object
The original session request body.
object
Session status object with
status: "pending" for a newly created session, or "queued" when the create was accepted above your concurrency limit.string
Link to the session’s Agent View page for live viewing and replay.
string
ISO 8601 timestamp.
Examples
Basic session
Reference a catalog agent. Its stored spec supplies the environments:curl -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
-H "Authorization: Bearer $HAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "h/web-surfer-flash",
"messages": [
{"type": "user_message", "message": "Find the best-rated sushi restaurants in San Francisco"}
]
}'
from hai_agents import Client
client = Client()
session = client.sessions.create_session(
agent="h/web-surfer-flash",
messages="Find the best-rated sushi restaurants in San Francisco",
)
print(session.id)
import { HaiAgentsClient } from "hai-agents";
const client = new HaiAgentsClient();
const session = await client.sessions.createSession({
body: {
agent: "h/web-surfer-flash",
messages: "Find the best-rated sushi restaurants in San Francisco",
},
});
console.log(session.id);
# `hai run` creates the session and blocks until the agent answers
hai run "Find the best-rated sushi restaurants in San Francisco" \
--agent h/web-surfer-flash
Response
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"request": {
"agent": "h/web-surfer-flash",
"messages": [
{"type": "user_message", "message": "Find the best-rated sushi restaurants in San Francisco"}
]
},
"status": {
"status": "pending",
"error": null,
"steps": 0,
"usage_per_model": [],
"subagent_session_ids": []
},
"agent_view_url": "https://platform.hcompany.ai/agents/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"latest_answer": null,
"created_at": "2026-05-07T14:30:00Z",
"started_at": null,
"finished_at": null
}
With idempotency key
curl -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
-H "Authorization: Bearer $HAI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-unique-key-123" \
-d '{
"agent": "h/web-surfer-flash",
"messages": [
{"type": "user_message", "message": "Search for direct flights from CDG to NRT on June 15"}
]
}'
session = client.sessions.create_session(
agent="h/web-surfer-flash",
messages="Search for direct flights from CDG to NRT on June 15",
idempotency_key="my-unique-key-123",
)
const session = await client.sessions.createSession({
idempotencyKey: "my-unique-key-123",
body: {
agent: "h/web-surfer-flash",
messages: "Search for direct flights from CDG to NRT on June 15",
},
});
With an inline agent and explicit browser environment
Pass an inlineAgent instead of a catalog id when you want to override the environments (or any other field) on a per-session basis. Environments must be nested under agent.
curl -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
-H "Authorization: Bearer $HAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": {
"name": "web-price-finder",
"description": "Web browsing agent with a custom browser config",
"environments": [
{"id": "browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 720}, "start_url": "https://www.bing.com"}
]
},
"messages": [
{"type": "user_message", "message": "Find the current price of the Framework 13 laptop and report the lowest you find"}
]
}'
session = client.sessions.create_session(
agent={
"name": "web-price-finder",
"description": "Web browsing agent with a custom browser config",
"environments": [
{
"id": "browser",
"kind": "web",
"mode": {"type": "visual", "width": 1280, "height": 720},
"start_url": "https://www.bing.com",
}
],
},
messages="Find the current price of the Framework 13 laptop and report the lowest you find",
)
const session = await client.sessions.createSession({
body: {
agent: {
name: "web-price-finder",
description: "Web browsing agent with a custom browser config",
environments: [
{
id: "browser",
kind: "web",
mode: { type: "visual", width: 1280, height: 720 },
startUrl: "https://www.bing.com",
},
],
},
messages: "Find the current price of the Framework 13 laptop and report the lowest you find",
},
});
With per-run overrides
Reuse a catalog agent but tweak it for this run only: here we send it to a different start URL without editing its stored spec.curl -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
-H "Authorization: Bearer $HAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "h/web-surfer-flash",
"messages": [
{"type": "user_message", "message": "Find the cheapest direct flight CDG to NRT next month"}
],
"overrides": {
"agent.environments[kind=web].start_url": "https://www.google.com/travel/flights"
}
}'
session = client.sessions.create_session(
agent="h/web-surfer-flash",
messages="Find the cheapest direct flight CDG to NRT next month",
overrides={
"agent.environments[kind=web].start_url": "https://www.google.com/travel/flights",
},
)
const session = await client.sessions.createSession({
body: {
agent: "h/web-surfer-flash",
messages: "Find the cheapest direct flight CDG to NRT next month",
overrides: {
"agent.environments[kind=web].start_url": "https://www.google.com/travel/flights",
},
},
});
hai run "Find the cheapest direct flight CDG to NRT next month" \
--agent h/web-surfer-flash \
--override 'agent.environments[kind=web].start_url=https://www.google.com/travel/flights'
Child session (multi-agent)
See Multi-agent for the full picture.curl -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
-H "Authorization: Bearer $HAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "h/web-surfer-flash",
"parent_session_id": "parent-session-uuid",
"group_id": "trip-planning-001",
"messages": [
{"type": "user_message", "message": "Find hotels near Shinjuku station under $150/night"}
]
}'
session = client.sessions.create_session(
agent="h/web-surfer-flash",
parent_session_id="parent-session-uuid",
group_id="trip-planning-001",
messages="Find hotels near Shinjuku station under $150/night",
)
const session = await client.sessions.createSession({
body: {
agent: "h/web-surfer-flash",
parentSessionId: "parent-session-uuid",
groupId: "trip-planning-001",
messages: "Find hotels near Shinjuku station under $150/night",
},
});
Errors
| Status | Cause |
|---|---|
400 | The resolved request is invalid (for example an override producing an impossible configuration). |
402 | Your organization’s monthly token budget is exhausted. See Plans and limits. |
404 | The referenced agent doesn’t exist or isn’t visible to you. |
409 | An Idempotency-Key from a still in-flight request was reused before it completed. Retry after Retry-After. |
422 | Request body failed validation, or an Idempotency-Key was reused with a different body. |
429 | Concurrency quota exceeded with queue: false, the queue itself is full, or the create carries a parent_session_id while at capacity. See Plans and limits. |
Last modified on September 11, 2026
Was this page helpful?