# About the Models API Source: https://hub.hcompany.ai/about-the-models-api OpenAI-compatible access to the Holo Vision-Language Models for computer use. The H Company Models API gives developers access to the Holo Vision-Language Models: multimodal models trained to operate real user interfaces across web, desktop, and mobile. Through a single OpenAI-compatible API, you send text, images, or both, and receive structured outputs you can act on directly. Two models are served today: the fast, open-weight `holo3-1-35b-a3b` (free tier) and the maximum-performance `holo3-122b-a10b`. See [Models](/models) for capabilities, limits, and pricing. ## Two ways to use Holo | Mode | Pattern | Output | When to use | | :------------------------------------------------ | :------------------------------------------------------ | :-------------------------------------------------- | :--------------------------------------------------------------------------- | | [**Agent loop**](/agent-loop) | Multi-turn: conversation + screenshots → next tool call | `{note, thought, tool_call}` or native `tool_calls` | Holo as the brain of an autonomous browser or desktop agent | | [**Element localization**](/element-localization) | Single-turn: image + target description → coordinates | `{x, y}` in `[0, 1000]` | UI grounding inside any external agent or pipeline (yours or someone else's) | There is also a third, non-GUI pattern: [Document OCR](/document-ocr), the same endpoint used as a one-shot page transcriber. ## Get started First request in five minutes. What is served, limits, and pricing. Endpoint, conventions, and parameters. Use Holo in your computer-use harness. ## Model cards and benchmarks Model cards, weights, and quantized builds. Mobile, function calling, and local inference. 78.85% on OSWorld-Verified. Prefer to try the models without writing code? [HoloTab](https://hcompany.ai/meet-holotab) runs Holo directly in your browser without any setup. # Agent loop Source: https://hub.hcompany.ai/agent-loop Holo is trained to act as a multi-step agent inside a specific harness, and a few of those conventions have to come along for the model to behave well in yours: an output format, a chat layout for screenshots and tool results, an image budget, and a coordinate convention. Skip any one and quality suffers. Holo supports two output formats, and which ones are available depends on the model: * **Structured outputs**: the model returns a single constrained JSON object per step. Works on Holo3.1 and Holo3. * **Native function calling**: the model returns OpenAI-style `tool_calls`. Holo3.1 only; Holo3 does not support it. Pick one and stay in it. The reasoning channel, coordinate convention, and image budget below are identical either way; only how you declare tools and read the model's output changes. See [Output format and tool calls](#output-format-and-tool-calls). Set up the OpenAI client first by following the [Quickstart](/quickstart). ## Reasoning Holo returns two streams on every call: a thinking trace in `message.reasoning` and the action in `content`. Reasoning is essential in agent mode (Holo was trained to plan before each step), so leave it on; `reasoning_effort: "medium"` is a sensible default. ```python Python theme={null} extra_body={"chat_template_kwargs": {"enable_thinking": True}} ``` ```typescript TypeScript theme={null} // H-specific fields are passed through in the request body ...({ chat_template_kwargs: { enable_thinking: true } } as any) ``` Past reasoning is dropped between turns by the [Qwen 3.5 chat template](https://huggingface.co/Qwen/Qwen3.5-35B-A3B/blob/main/chat_template.jinja) Holo inherits, so anything the model needs to remember has to flow through `content` (that is what the `note` field, below, is for). When re-adding the assistant message to the conversation, push only the parsed output; do not splice the reasoning back in. ## Coordinates in `[0, 1000]` Send a screenshot at any size. Holo returns coordinates as integers in `[0, 1000]`, normalized to that image. Scale back to pixels using its dimensions: ```python Python theme={null} abs_x = int((x / 1000) * screenshot.width) abs_y = int((y / 1000) * screenshot.height) ``` ```typescript TypeScript theme={null} const absX = Math.round((x / 1000) * screenshot.width); const absY = Math.round((y / 1000) * screenshot.height); ``` Origin is top-left. Send and scale against the same image bytes; any resize, crop, or DPI mismatch will misclick. Pick one pixel unit (CSS or device) and stay in it end to end. ## Image budget Keep at most the last 3 screenshots in context; more degrades accuracy. Older screenshots should be replaced with a short text placeholder, while keeping the `` wrapper. This works the same in both output formats, since observations are always `user` messages: ```python Python theme={null} def trim_to_last_n_images(messages, n=3): seen = 0 for msg in reversed(messages): if msg["role"] != "user" or not isinstance(msg["content"], list): continue for chunk in msg["content"]: if chunk.get("type") != "image_url": continue seen += 1 if seen > n: chunk["type"] = "text" chunk["text"] = "[screenshot evicted]" chunk.pop("image_url", None) ``` ```typescript TypeScript theme={null} function trimToLastNImages(messages: any[], n = 3) { let seen = 0; for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (msg.role !== "user" || !Array.isArray(msg.content)) continue; for (const chunk of msg.content) { if (chunk.type !== "image_url") continue; seen += 1; if (seen > n) { chunk.type = "text"; chunk.text = "[screenshot evicted]"; delete chunk.image_url; } } } } ``` ## Output format and tool calls The model is constrained, at the decoding level, to emit a single JSON object matching a schema you provide. Tool calls are fields inside that object, so output is always valid JSON. ### Output JSON Each step, the model emits one object with three fields: ```json theme={null} { "note": "Submit succeeded; receipt URL is /orders/8421.", "thought": "Recording the receipt URL before navigating away.", "tool_call": { "tool_name": "click", "element": "Continue button at the bottom right", "x": 932, "y": 880 } } ``` `note` is the model's durable memory: anything from the current screen that future steps will need (URLs, IDs, intermediate answers). Set it to `null` when nothing new is worth recording. `thought` is a one-line plan for the next action. `tool_call` is flat: `tool_name` is a sibling of the arguments, not nested in an `args` object. ### Constrain output to a tool union Define each tool as a Pydantic model with a `Literal[tool_name]` field, then use their union as the response schema. The server's constrained decoder ensures the model emits exactly one variant, and `tool_name` is the tag you dispatch on at execution time. The example below ships three tools (click, write, answer) for illustration; real agents register a wider toolbox following the same pattern. ```python Python theme={null} from typing import Literal from pydantic import BaseModel, Field class ClickArgs(BaseModel): """Click at (x, y) coordinates""" tool_name: Literal["click"] element: str = Field(description="Detailed description of the target UI element to click on") x: int = Field(description="X coordinate as integer in [0, 1000]") y: int = Field(description="Y coordinate as integer in [0, 1000]") class WriteArgs(BaseModel): """Type text into the currently focused element without clicking first""" tool_name: Literal["write"] content: str = Field(description="Content to write") press_enter: bool = Field(default=False, description="Whether to press Enter after typing") class AnswerArgs(BaseModel): """Provide a final answer""" tool_name: Literal["answer"] content: str = Field(description="The answer content") class Step(BaseModel): note: str | None = Field(default=None, description="Task-relevant information from the previous observation. Empty if nothing new.") thought: str = Field(description="Reasoning about next steps") tool_call: ClickArgs | WriteArgs | AnswerArgs ``` ```typescript TypeScript theme={null} // JSON Schema for the Step union; tool_name is the discriminator you dispatch on const schema = { type: "object", properties: { note: { type: ["string", "null"], description: "Task-relevant information from the previous observation. Empty if nothing new.", }, thought: { type: "string", description: "Reasoning about next steps" }, tool_call: { oneOf: [ { type: "object", description: "Click at (x, y) coordinates", properties: { tool_name: { const: "click" }, element: { type: "string", description: "Detailed description of the target UI element to click on" }, x: { type: "integer", description: "X coordinate as integer in [0, 1000]" }, y: { type: "integer", description: "Y coordinate as integer in [0, 1000]" }, }, required: ["tool_name", "element", "x", "y"], }, { type: "object", description: "Type text into the currently focused element without clicking first", properties: { tool_name: { const: "write" }, content: { type: "string", description: "Content to write" }, press_enter: { type: "boolean", description: "Whether to press Enter after typing" }, }, required: ["tool_name", "content"], }, { type: "object", description: "Provide a final answer", properties: { tool_name: { const: "answer" }, content: { type: "string", description: "The answer content" }, }, required: ["tool_name", "content"], }, ], }, }, required: ["thought", "tool_call"], }; ``` Embed the same schema inside the system prompt under an `` block (shown in [the loop](#a-complete-loop) below). The model was trained with the schema visible in both the prompt and `structured_outputs`, and dropping either copy noticeably hurts reliability. Use `extra_body={"structured_outputs": {"json": ...}}`, not OpenAI native function calling (`tools=[...]` / `tool_choice=...`). In this mode the model emits a flat `{note, thought, tool_call}` object in `content`, not a `tool_calls` array. Pass the schema to `structured_outputs`, then parse `content` back into your models. Because `tool_name` is a discriminator, the parsed `tool_call` narrows to exactly one variant, which is what you dispatch on: ```python Python theme={null} schema = Step.model_json_schema() resp = client.chat.completions.create( model=MODEL_NAME, messages=messages, # the system prompt embeds this same schema; see the loop below extra_body={"structured_outputs": {"json": schema}}, ) step = Step.model_validate_json(resp.choices[0].message.content) # step.tool_call is now typed as the matching variant (ClickArgs, WriteArgs, ...) if step.tool_call.tool_name == "answer": print(step.tool_call.content) else: execute(step.tool_call) ``` ```typescript TypeScript theme={null} const resp = await client.chat.completions.create({ model: MODEL_NAME, messages, // the system prompt embeds this same schema; see the loop below ...({ structured_outputs: { json: schema } } as any), }); const step = JSON.parse(resp.choices[0].message.content!); // step.tool_call.tool_name tells you which variant the model picked if (step.tool_call.tool_name === "answer") { console.log(step.tool_call.content); } else { execute(step.tool_call); } ``` ### Chat layout User observations alternate with assistant JSON; tool results come back as `user` messages: | Role | Body | | :---------- | :------------------------------------------------------------ | | `system` | your prompt, then the appended `` schema block | | `user` | `` + screenshot and/or text + `` | | `assistant` | the JSON object: `{note, thought, tool_call}` | | `user` | `` + result + `` | | `user` | next `` | | `assistant` | next JSON | Wrap tool results as `user` messages with ``, not as OpenAI `tool`-role messages. ### A complete loop Plug in your own `screenshot()` (browser, OS, emulator) and `execute(...)` dispatcher. ````python Python theme={null} import json, base64 schema = Step.model_json_schema() system = render_prompt(tools=...) + f"\n\n\n```json\n{json.dumps(schema)}\n```\n" messages = [{"role": "system", "content": system}] for _ in range(MAX_STEPS): image_bytes = screenshot() b64 = base64.b64encode(image_bytes).decode() messages.append({"role": "user", "content": [ {"type": "text", "text": "\n"}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}, {"type": "text", "text": "\n"}, ]}) trim_to_last_n_images(messages, n=3) resp = client.chat.completions.create( model=MODEL_NAME, messages=messages, temperature=0.8, extra_body={"structured_outputs": {"json": schema}}, ) step = Step.model_validate_json(resp.choices[0].message.content) messages.append({"role": "assistant", "content": step.model_dump_json()}) if step.tool_call.tool_name == "answer": return step.tool_call.content result = execute(step.tool_call) messages.append({ "role": "user", "content": f'\n{result}\n', }) ```` ```typescript TypeScript theme={null} const system = renderPrompt() + `\n\n\n\`\`\`json\n${JSON.stringify(schema)}\n\`\`\`\n`; const messages: any[] = [{ role: "system", content: system }]; for (let i = 0; i < MAX_STEPS; i++) { const b64 = (await screenshot()).toString("base64"); messages.push({ role: "user", content: [ { type: "text", text: "\n" }, { type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } }, { type: "text", text: "\n" }, ], }); trimToLastNImages(messages, 3); const resp = await client.chat.completions.create({ model: MODEL_NAME, messages, temperature: 0.8, ...({ structured_outputs: { json: schema } } as any), }); const step = JSON.parse(resp.choices[0].message.content!); messages.push({ role: "assistant", content: JSON.stringify(step) }); if (step.tool_call.tool_name === "answer") { return step.tool_call.content; } const result = await execute(step.tool_call); messages.push({ role: "user", content: `\n${result}\n`, }); } ``` Holo3.1 supports the standard OpenAI tool-calling API. Tools are passed via the `tools` parameter, and the model replies with `tool_calls` in the assistant message. This is the most natural format for the model and integrates directly with agent frameworks that already speak OpenAI function calling. ### Declare tools Pass tool schemas via `tools`, and set `tool_choice="required"` so the model acts on every step. Do not put tool-format examples in the system prompt; the model renders the call in its own native format from `tools` alone, and a conflicting example degrades quality. ```python Python theme={null} tools = [ { "type": "function", "function": { "name": "click", "description": "Click at (x, y) coordinates", "parameters": { "type": "object", "properties": { "element": {"type": "string", "description": "Detailed description of the target UI element"}, "x": {"type": "integer", "description": "X coordinate as integer in [0, 1000]"}, "y": {"type": "integer", "description": "Y coordinate as integer in [0, 1000]"}, }, "required": ["element", "x", "y"], }, }, }, { "type": "function", "function": { "name": "answer", "description": "Provide a final answer", "parameters": { "type": "object", "properties": {"content": {"type": "string", "description": "The answer content"}}, "required": ["content"], }, }, }, ] ``` ```typescript TypeScript theme={null} const tools = [ { type: "function", function: { name: "click", description: "Click at (x, y) coordinates", parameters: { type: "object", properties: { element: { type: "string", description: "Detailed description of the target UI element" }, x: { type: "integer", description: "X coordinate as integer in [0, 1000]" }, y: { type: "integer", description: "Y coordinate as integer in [0, 1000]" }, }, required: ["element", "x", "y"], }, }, }, { type: "function", function: { name: "answer", description: "Provide a final answer", parameters: { type: "object", properties: { content: { type: "string", description: "The answer content" } }, required: ["content"], }, }, }, ] as const; ``` The model returns its action in `message.tool_calls`; each call carries a `function.name`, a JSON `function.arguments` string, and a unique `id`. There is no `note`/`thought` JSON here: planning lives in `message.reasoning`, and any fact the model must carry across turns should be written into the assistant `content`, since the reasoning trace is dropped between turns. Pass `tools` with `tool_choice="required"`, then read the first call and dispatch on `function.name`: ```python Python theme={null} resp = client.chat.completions.create( model=MODEL_NAME, messages=messages, tools=tools, tool_choice="required", ) call = resp.choices[0].message.tool_calls[0] args = json.loads(call.function.arguments) if call.function.name == "answer": print(args["content"]) else: execute(call.function.name, args) ``` ```typescript TypeScript theme={null} const resp = await client.chat.completions.create({ model: MODEL_NAME, messages, tools, tool_choice: "required", }); const call = resp.choices[0].message.tool_calls![0]; const args = JSON.parse(call.function.arguments); if (call.function.name === "answer") { console.log(args.content); } else { execute(call.function.name, args); } ``` ### Chat layout Tool results go back as `tool`-role messages keyed by `tool_call_id`, not as `user` messages: | Role | Body | | :---------- | :---------------------------------------------------------- | | `system` | your prompt (no tool-format examples) | | `user` | `` + screenshot and/or text + `` | | `assistant` | `tool_calls=[{id, function: {name, arguments}}]` | | `tool` | `tool_call_id` matching the call + result | | `user` | next `` | | `assistant` | next `tool_calls` | ### A complete loop Plug in your own `screenshot()` and `execute(name, args)` dispatcher. ```python Python theme={null} import json, base64 messages = [{"role": "system", "content": render_prompt()}] for _ in range(MAX_STEPS): image_bytes = screenshot() b64 = base64.b64encode(image_bytes).decode() messages.append({"role": "user", "content": [ {"type": "text", "text": "\n"}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}, {"type": "text", "text": "\n"}, ]}) trim_to_last_n_images(messages, n=3) resp = client.chat.completions.create( model=MODEL_NAME, messages=messages, tools=tools, tool_choice="required", temperature=0.8, ) msg = resp.choices[0].message messages.append(msg) call = msg.tool_calls[0] args = json.loads(call.function.arguments) if call.function.name == "answer": return args["content"] result = execute(call.function.name, args) messages.append({ "role": "tool", "tool_call_id": call.id, "content": str(result), }) ``` ```typescript TypeScript theme={null} const messages: any[] = [{ role: "system", content: renderPrompt() }]; for (let i = 0; i < MAX_STEPS; i++) { const b64 = (await screenshot()).toString("base64"); messages.push({ role: "user", content: [ { type: "text", text: "\n" }, { type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } }, { type: "text", text: "\n" }, ], }); trimToLastNImages(messages, 3); const resp = await client.chat.completions.create({ model: MODEL_NAME, messages, tools, tool_choice: "required", temperature: 0.8, }); const msg = resp.choices[0].message; messages.push(msg); const call = msg.tool_calls![0]; const args = JSON.parse(call.function.arguments); if (call.function.name === "answer") { return args.content; } const result = await execute(call.function.name, args); messages.push({ role: "tool", tool_call_id: call.id, content: String(result), }); } ``` ## Common pitfalls | Symptom | Likely cause | | :---------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Clicks land far from the target | Coordinates not scaled to screenshot dimensions, or the screenshot was resized between send and execute | | Model loops, forgets earlier facts | Durable facts not carried forward (`note` empty in structured mode, or nothing written to `content` in function-calling mode), or older `` wrappers dropped instead of stripped to text | | Context window fills up | Image budget not enforced | | Reasoning leaks into the action | `...` written inline in `content` instead of read from `message.reasoning` | | Quality collapses after one bad step | Raw model output replayed in history instead of the parsed result | | (Structured) Model returns free-form text | `extra_body.structured_outputs.json` is missing, or the schema lacks `Literal[tool_name]` discrimination | | (Structured) Tool result has no effect | Sent as a `tool`-role message instead of a `user` message with a `` wrapper | | (Function calling) Tool calls come back as plain text | `tool_choice` not set to `required`, or the system prompt contains conflicting tool-format examples | | (Function calling) Tool result ignored | Sent as a `user` message instead of a `tool`-role message with a matching `tool_call_id` | ## Next steps Get click coordinates from a screenshot. Endpoint, models, parameters, and limits. Back to setup and your first call. # API reference Source: https://hub.hcompany.ai/api-reference Endpoint, authentication, conventions, and the Holo-specific request surface. The Models API is OpenAI-compatible: point the official OpenAI client (or any compatible library) at H Company's endpoint. You opt into Holo-specific behavior (structured outputs, reasoning, and the coordinate convention) through a few extra request fields and conventions documented here. ## Endpoints The inference endpoint: parameters, response fields, streaming. Discover served models, limits, pricing, and deprecation dates at runtime. ## Endpoint and auth | | | | :------- | :----------------------------------------------------------------------------------- | | Base URL | `https://api.hcompany.ai/v1/` | | Auth | `Authorization: Bearer $HAI_API_KEY` (handled by the OpenAI client) | | Keys | Create one on [Portal-H](https://portal.hcompany.ai/?product=modelsapi\&source=docs) | ```python Python theme={null} import os from openai import OpenAI client = OpenAI( base_url="https://api.hcompany.ai/v1/", api_key=os.environ["HAI_API_KEY"], ) ``` ```typescript TypeScript theme={null} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.hcompany.ai/v1/", apiKey: process.env.HAI_API_KEY, }); ``` Model IDs, per-model limits, pricing, and tiers live on the [Models](/models) page. ## The two response channels Holo returns two streams on every call: The action: the structured JSON object (structured-output mode) or the assistant text. The thinking trace, when thinking is enabled. Read it for visibility; do not feed it back into the conversation. The thinking trace is dropped between turns by the chat template Holo inherits. Anything the model must remember has to flow through `content`. See the [Agent loop](/agent-loop#reasoning) for how to carry state forward. ## Conventions Holo returns click positions as integers normalized to the image you sent. Scale back to pixels with the image's own dimensions. Origin is top-left. Send and scale against the same image bytes: any resize, crop, or DPI mismatch will misplace the point. Keep at most the last 3 screenshots in context for best accuracy, even though a request accepts up to 5 images. See [the trim helper](/agent-loop#image-budget) in the agent loop. Structured outputs work on both models; native function calling (`tools` / `tool_calls`) is `holo3-1-35b-a3b` only. Pick one and stay in it: [Agent loop](/agent-loop#output-format-and-tool-calls). `structured_outputs` and `chat_template_kwargs` are top-level body fields on the wire. The OpenAI SDKs do not know them, so pass them via `extra_body` (Python) or an untyped spread (TypeScript); the SDK merges them into the request body. ## Next steps Full parameter and response reference. IDs, limits, pricing, lifecycle. How to use Holo in your computer-use harness. # Create an agent Source: https://hub.hcompany.ai/computer-use-agents/agents/create POST /api/v2/agents Create a reusable agent in your own catalog. Creates a new custom agent in your catalog. Once created, reference it by `name` (e.g. `"agent": "my-research-bot"`) when [creating a session](/computer-use-agents/sessions/create), and its environments, skills, and subagents are pulled from the stored configuration. **Returns** `201` with the created [Agent](/computer-use-agents/agents/overview) object. *** ## Request body The body is the [Agent](/computer-use-agents/agents/overview) object. See that page for the full meaning of each field; the constraints that matter when creating one are below. Catalog identifier, kebab-case with an optional single `org/` namespace prefix. The `h/` prefix is reserved for H's catalog (rejected with `403`) and marks the agent as reserved; any other name creates a custom agent, private to your organization. 1 to 127 characters, immutable after creation. What the agent does. Read by parent agents to decide what to delegate. At most one per kind. Each item is a string catalog id or an inline [Browser environment](/computer-use-agents/browser/configuration) spec. Required unless the agent only delegates to `subagents` (a pure [manager](/computer-use-agents/multi-agent) needs none). Holo model that runs the agent. Defaults to `holo3-122b-a10b`; pass any Holo model id (for example `holo3-1-35b-a3b`) listed in the [Models API](/models). Omit to take the default. Steering text appended to the system prompt. Skills available to the agent, as catalog id strings or inline [Skill](/computer-use-agents/skills/overview) specs. Agents this one can delegate to, as catalog id strings or inline agent specs. A [JSON Schema](https://json-schema.org/) the agent's final answer must conform to. When set, the agent returns [structured output](/computer-use-agents/structured-output) matching the schema instead of free-form text. Omit it for free-form text; callers can also set or override it per run with session [`overrides`](/computer-use-agents/sessions/create). [Custom tools](/computer-use-agents/custom-tools) the agent can call from your own code. *** ## Examples ```bash cURL theme={null} curl -X POST https://agp.eu.hcompany.ai/api/v2/agents \ -H "Authorization: Bearer $HAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "my-web-agent", "description": "Custom web researcher", "environments": [ { "id": "browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 720}, "start_url": "https://news.ycombinator.com" } ] }' ``` ```python Python theme={null} from hai_agents import Client client = Client() agent = client.agents.create_agent( name="my-web-agent", description="Custom web researcher", environments=[ { "id": "browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 720}, "start_url": "https://news.ycombinator.com", } ], ) print(agent.name) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const agent = await client.agents.createAgent({ name: "my-web-agent", description: "Custom web researcher", environments: [ { id: "browser", kind: "web", mode: { type: "visual", width: 1280, height: 720 }, startUrl: "https://news.ycombinator.com", }, ], }); console.log(agent.name); ``` ```json Response theme={null} { "name": "my-web-agent", "description": "Custom web researcher", "environments": [ { "id": "browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 720}, "start_url": "https://news.ycombinator.com" } ], "model": null, "instructions": null, "skills": null, "subagents": null, "answer_format": null } ``` *** ## Errors | Status | Cause | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `403` | Attempted to use the reserved `h/` namespace. | | `409` | An agent with this `name` already exists in your catalog. | | `422` | Body fails validation; common cases: `environments` empty on an agent that has no `subagents`, a duplicate environment kind, or an invalid `name` shape. | # Delete an agent Source: https://hub.hcompany.ai/computer-use-agents/agents/delete DELETE /api/v2/agents/{agent_name} Remove an agent from your catalog. Removes an agent from your catalog. Sessions already running against it are unaffected; new sessions can no longer reference it by name. **Returns** `204 No Content` on success. *** ## Path parameters The agent's `name` (e.g. `my-research-bot` or `myorg/web-helper`). Slash-containing names are supported. *** ## Examples ```bash cURL theme={null} curl -X DELETE https://agp.eu.hcompany.ai/api/v2/agents/my-research-bot \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() client.agents.delete_agent(agent_name="my-research-bot") ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); await client.agents.deleteAgent({ agentName: "my-research-bot" }); ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------- | | `403` | The agent is reserved (`h/`) and read-only. | | `404` | Agent not found or you don't have access. | # List agents Source: https://hub.hcompany.ai/computer-use-agents/agents/list GET /api/v2/agents Discover available agents from your catalog and the H preset catalog. Returns a paginated list of agents visible to you: both your custom agents and the built-in H preset catalog. **Returns** a paginated list of [Agent](/computer-use-agents/agents/overview) objects. *** ## Query parameters Page number (1-based). Items per page. Maximum: `1000`. Sort order. Options: `created_at`, `-created_at`, `agent_name`, `-agent_name`. *** ## Examples ### List all available agents ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/agents" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() agents = client.agents.list_agents() print(agents.items) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const agents = await client.agents.listAgents(); console.log(agents.items); ``` Each item is a full [Agent](/computer-use-agents/agents/overview) object. ```json Response theme={null} { "items": [ { "name": "h/web-surfer-flash", "description": "General-purpose web browsing agent.", "environments": ["h/browser"], "model": null, "instructions": null, "skills": ["web-browse", "screenshot"], "subagents": null, "answer_format": null }, { "name": "price-checker", "description": "Compares product prices across e-commerce sites.", "environments": ["h/browser"], "model": null, "instructions": null, "skills": null, "subagents": null, "answer_format": null } ], "page": 1, "total": 8 } ``` # Agents Source: https://hub.hcompany.ai/computer-use-agents/agents/overview An agent is a reusable configuration that defines what an AI agent can do. An `agent` holds the configuration a run needs: the environment it acts in, the model that drives it, and optional skills and instructions. Start with a [pre-built agent](#pre-built-agents) from H, or create your own. Names are scoped to your organization: the agents, skills, and environments you create are visible only within it. H's pre-built agents and environments live under the reserved `h/` namespace (for example `h/web-surfer-flash` and `h/browser`); they are available to every organization and read-only, so modifying one returns `403`. Agents you create have no prefix. ## Configure an agent | Field | Required | Description | | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Yes | Identifies the agent in your catalog and is the value you pass as `agent` when creating a session. | | `environments` | Conditional | The surfaces it acts on, like a [browser](/computer-use-agents/browser/configuration). Required unless the agent is a pure [manager](/computer-use-agents/multi-agent) that only delegates to `subagents`. | | `description` | Yes | A one-line summary of what the agent does. Also used for delegation: a parent agent reads it to decide whether to hand off a task. | | `model` | No | The Holo model that runs the agent. Defaults to `holo3-122b-a10b`; pass any Holo model id from the [Models API](/models), for example `holo3-1-35b-a3b` for the faster Holo3.1. | | `instructions` | No | Appended to the system prompt to steer behavior. | | `skills` | No | Reusable [instruction fragments](/computer-use-agents/skills/overview) the agent loads on demand. | | `tools` | No | Extra tools the agent can call from your own code. See [Custom tools](/computer-use-agents/custom-tools). | | `subagents` | No | Specialist agents this one can delegate to. Each runs as its own child session, in parallel, and returns a single answer the manager folds into its own. See [Multi-agent](/computer-use-agents/multi-agent). | | `answer_format` | No | A [JSON Schema](https://json-schema.org/) the final answer must conform to. When set, the agent returns [structured output](/computer-use-agents/structured-output) matching the schema instead of free-form text. Leave it unset for free-form text; override it per run with session [`overrides`](/computer-use-agents/sessions/create). | Each `environments`, `skills`, or `subagents` entry is either a string catalog id or an inline object. A reference keeps the definition central and reusable; an inline object is handy for one-offs. For exact field constraints, see [Create an agent](/computer-use-agents/agents/create). ## Create your own Create an agent once, then reference it by `name` in every session: ```bash cURL theme={null} curl -X POST https://agp.eu.hcompany.ai/api/v2/agents \ -H "Authorization: Bearer $HAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "web-price-finder", "description": "Finds and reports prices for products, flights, or services on the public web.", "instructions": "When the user asks for a price, return a single concise line with the amount, currency, and key context (vendor or airline, date, link). Prefer the cheapest matching option. If the price is not visible without login or payment, say so explicitly rather than guessing.", "model": "holo3-122b-a10b", "environments": [ { "id": "price-browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 800}, "start_url": "https://www.google.com/travel/flights" } ], "skills": ["extract-data"] }' ``` ```python Python theme={null} from hai_agents import Client client = Client() client.agents.create_agent( name="web-price-finder", description="Finds and reports prices for products, flights, or services on the public web.", instructions=( "When the user asks for a price, return a single concise line with the amount, " "currency, and key context (vendor or airline, date, link). Prefer the cheapest " "matching option. If the price is not visible without login or payment, say so " "explicitly rather than guessing." ), model="holo3-122b-a10b", environments=[ { "id": "price-browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 800}, "start_url": "https://www.google.com/travel/flights", } ], skills=["extract-data"], ) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); await client.agents.createAgent({ name: "web-price-finder", description: "Finds and reports prices for products, flights, or services on the public web.", instructions: "When the user asks for a price, return a single concise line with the amount, " + "currency, and key context (vendor or airline, date, link). Prefer the cheapest " + "matching option. If the price is not visible without login or payment, say so " + "explicitly rather than guessing.", model: "holo3-122b-a10b", environments: [ { id: "price-browser", kind: "web", mode: { type: "visual", width: 1280, height: 800 }, startUrl: "https://www.google.com/travel/flights", }, ], skills: ["extract-data"], }); ``` ## Pre-built agents H maintains a catalog of configured agents under the `h/` namespace. You can run them as-is, with no setup. Catalog agents that delegate to subagents (such as `h/deep-search-pro`) build on [Multi-agent](/computer-use-agents/multi-agent). List them anytime with [`GET /api/v2/agents`](/computer-use-agents/agents/list): ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/agents" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() agents = client.agents.list_agents() ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const agents = await client.agents.listAgents(); ``` Reference one by name when you create a [session](/computer-use-agents/sessions/overview), and the platform supplies its full configuration: ```bash CLI theme={null} hai run "Top 3 stories on Hacker News right now?" \ --agent h/web-surfer-flash ``` ```bash cURL theme={null} 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": "Top 3 stories on Hacker News right now?"}] }' ``` ```python Python theme={null} client.sessions.create_session( agent="h/web-surfer-flash", messages=[{"type": "user_message", "message": "Top 3 stories on Hacker News right now?"}], ) ``` ```typescript TypeScript theme={null} await client.sessions.createSession({ body: { agent: "h/web-surfer-flash", messages: [{ type: "user_message", message: "Top 3 stories on Hacker News right now?" }], }, }); ``` ## Endpoints | Method | Path | Description | | -------- | ----------------------- | --------------------------------------------------------- | | `POST` | `/api/v2/agents` | [Create an agent](/computer-use-agents/agents/create) | | `GET` | `/api/v2/agents` | [List agents](/computer-use-agents/agents/list) | | `GET` | `/api/v2/agents/{name}` | [Retrieve an agent](/computer-use-agents/agents/retrieve) | | `PUT` | `/api/v2/agents/{name}` | [Update an agent](/computer-use-agents/agents/update) | | `PATCH` | `/api/v2/agents/{name}` | [Patch an agent](/computer-use-agents/agents/patch) | | `DELETE` | `/api/v2/agents/{name}` | [Delete an agent](/computer-use-agents/agents/delete) | The list is paginated (`page`, `size`) and returns an `items` / `page` / `total` envelope; sort it with `sort=created_at` or `sort=agent_name`, prefixed with `-` for descending. # Patch an agent Source: https://hub.hcompany.ai/computer-use-agents/agents/patch PATCH /api/v2/agents/{agent_name} Change individual fields of an agent without resending the full object. Partial update: only the fields you send change, everything else is preserved. Send a field as `null` to clear it. The merged result is validated like a [full update](/computer-use-agents/agents/update), and `name` is not patchable (renames are not supported). **Returns** the updated [Agent](/computer-use-agents/agents/overview) object. *** ## Path parameters The agent's `name` (e.g. `my-research-bot` or `myorg/web-helper`). Slash-containing names are supported. *** ## Request body Any subset of the [Agent](/computer-use-agents/agents/overview) object's fields except `name`: `description`, `environments`, `model`, `instructions`, `subagents`, `skills`, `answer_format`, `tools`. *** ## Examples Change the instructions and nothing else: ```bash cURL theme={null} curl -X PATCH https://agp.eu.hcompany.ai/api/v2/agents/my-research-bot \ -H "Authorization: Bearer $HAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{"instructions": "Always cite the page you took each claim from."}' ``` ```python Python theme={null} from hai_agents import Client client = Client() agent = client.agents.patch_agent( agent_name="my-research-bot", instructions="Always cite the page you took each claim from.", ) print(agent.instructions) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const agent = await client.agents.patchAgent({ agentName: "my-research-bot", instructions: "Always cite the page you took each claim from.", }); console.log(agent.instructions); ``` *** ## Errors | Status | Cause | | ------ | -------------------------------------------------------------------------------------------------- | | `403` | The agent is reserved (`h/`) and read-only. | | `404` | Agent not found (or a referenced skill, environment, or subagent isn't), or you don't have access. | | `422` | The merged spec fails validation, for example `environments` set to an empty list. | # Retrieve an agent Source: https://hub.hcompany.ai/computer-use-agents/agents/retrieve GET /api/v2/agents/{agent_name} Get the full specification of a single agent. Retrieves the complete [Agent](/computer-use-agents/agents/overview) object, including its full specification: environments, skills, instructions, and subagent configuration. **Returns** the [Agent](/computer-use-agents/agents/overview) object if the name is valid and you have access. Returns `404` otherwise. *** ## Path parameters The agent's `name` (e.g., `h/web` or `my-custom-bot`). Slash-containing names are supported. *** ## Query parameters When `true`, string references in `environments`, `skills`, and `subagents` are expanded into their full specs in the response. When `false` (the default), they are returned as stored, keeping catalog ids as plain strings. *** ## Examples ### Retrieve an H catalog agent ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/agents/h/web-surfer-flash" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() agent = client.agents.get_agent(agent_name="h/web-surfer-flash") print(agent.name) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const agent = await client.agents.getAgent({ agentName: "h/web-surfer-flash" }); console.log(agent.name); ``` ```json Response theme={null} { "name": "h/web-surfer-flash", "description": "General-purpose web browsing agent. Navigates websites, extracts information, fills forms, and completes multi-step web tasks.", "environments": [ { "id": "h/browser", "kind": "web", "mode": {"type": "visual", "width": 1280, "height": 720}, "start_url": "https://www.bing.com" } ], "model": null, "instructions": null, "skills": ["web-browse", "screenshot", "form-fill", "extract-data"], "subagents": null, "answer_format": null } ``` ### Retrieve a custom agent ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/agents/price-checker" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} agent = client.agents.get_agent("price-checker") ``` ```typescript TypeScript theme={null} const agent = await client.agents.getAgent({ agentName: "price-checker" }); ``` *** ## Use case: inspect before using Before using an agent in a session, you can inspect its capabilities: ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/agents/$AGENT_NAME" \ -H "Authorization: Bearer $HAI_API_KEY" \ | jq '{name, description, environments, skills, subagents}' ``` ```python Python theme={null} agent = client.agents.get_agent(agent_name) print(agent.description, agent.skills) ``` ```typescript TypeScript theme={null} const agent = await client.agents.getAgent({ agentName }); console.log(agent.description, agent.skills); ``` *** ## Errors | Status | Cause | | ------ | --------------------------------------------------------------------------------------- | | `404` | Agent not found, or you don't have access (e.g., querying another team's custom agent). | # Update an agent Source: https://hub.hcompany.ai/computer-use-agents/agents/update PUT /api/v2/agents/{agent_name} Replace the configuration of an agent in your catalog. Updates an existing agent. This is a **full replacement** of the [Agent](/computer-use-agents/agents/overview) object. The `name` must match the URL identifier: renames are not supported. **Returns** the updated [Agent](/computer-use-agents/agents/overview) object. *** ## Path parameters The agent's `name` (e.g. `my-research-bot` or `myorg/web-helper`). Slash-containing names are supported. *** ## Request body A **full replacement** of the [Agent](/computer-use-agents/agents/overview) object. The `name` in the body must equal the URL identifier. Any field you omit is reset to its default, not preserved: leaving out `instructions`, `model`, `skills`, or `answer_format` clears them. To change individual fields without resending the rest, use [Patch](/computer-use-agents/agents/patch) instead. *** ## Examples ```bash cURL theme={null} curl -X PUT https://agp.eu.hcompany.ai/api/v2/agents/my-research-bot \ -H "Authorization: Bearer $HAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "my-research-bot", "description": "Researches a topic and returns a sourced summary.", "environments": ["h/browser"], "instructions": "Always cite the page you took each claim from.", "skills": ["web-browse", "extract-data"] }' ``` ```python Python theme={null} from hai_agents import Client client = Client() agent = client.agents.update_agent( agent_name="my-research-bot", name="my-research-bot", description="Researches a topic and returns a sourced summary.", environments=["h/browser"], instructions="Always cite the page you took each claim from.", skills=["web-browse", "extract-data"], ) print(agent.name) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const agent = await client.agents.updateAgent({ agentName: "my-research-bot", body: { name: "my-research-bot", description: "Researches a topic and returns a sourced summary.", environments: ["h/browser"], instructions: "Always cite the page you took each claim from.", skills: ["web-browse", "extract-data"], }, }); console.log(agent.name); ``` ```json Response theme={null} { "name": "my-research-bot", "description": "Researches a topic and returns a sourced summary.", "environments": ["h/browser"], "model": null, "instructions": "Always cite the page you took each claim from.", "skills": ["web-browse", "extract-data"], "subagents": null, "answer_format": null } ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------------------------------------------------- | | `400` | The `name` in the body does not match the URL identifier (renames are not supported). | | `403` | The agent is reserved (`h/`) and read-only. | | `404` | Agent not found or you don't have access. | | `422` | Body fails validation; common cases: `environments` empty, invalid `name` shape. | # Complete a profile upload Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/complete-upload POST /api/v2/browser-profiles/{profile_id}/complete-upload Finalize a browser profile after its archive is uploaded. Finalizes a profile after you have uploaded its archive to the presigned target from [Initiate upload](/computer-use-agents/browser-profiles/initiate-upload). The platform verifies the uploaded archive and records the profile metadata. **Returns** `201` with the created profile object (see [Retrieve](/computer-use-agents/browser-profiles/retrieve) for the full field list). *** ## Path parameters The `profile_id` returned by [Initiate upload](/computer-use-agents/browser-profiles/initiate-upload). *** ## Request body Human-readable label for the profile. Browser the profile was captured with (for example `chromium`, `firefox`, `webkit`, or `selenium`). Validated against the set of supported browsers. Browser version string the profile was captured with (for example `131`). Optional free-text description. Optional key-value metadata for your own bookkeeping. *** ## Examples ```bash cURL theme={null} curl -X POST "https://agp.eu.hcompany.ai/api/v2/browser-profiles/a1b2c3d4-5678-90ab-cdef-1234567890ab/complete-upload" \ -H "Authorization: Bearer $HAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "acme-prod-login", "browser_name": "chromium", "browser_version": "131", "labels": {"team": "qa"} }' ``` ```python Python theme={null} from hai_agents import Client client = Client() profile = client.browser_profiles.complete_browser_profile_upload( profile_id="a1b2c3d4-5678-90ab-cdef-1234567890ab", name="acme-prod-login", browser_name="chromium", browser_version="131", labels={"team": "qa"}, ) print(profile.id) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const profile = await client.browserProfiles.completeBrowserProfileUpload({ profileId: "a1b2c3d4-5678-90ab-cdef-1234567890ab", name: "acme-prod-login", browserName: "chromium", browserVersion: "131", labels: { team: "qa" }, }); console.log(profile.id); ``` ```json Response theme={null} { "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "name": "acme-prod-login", "description": null, "browser_name": "chromium", "browser_version": "131", "s3_path": "browser-profiles/finished/org_123/a1b2c3d4-5678-90ab-cdef-1234567890ab/profile.zip", "file_size_bytes": 102400, "checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "usage_count": 0, "last_used_at": null, "labels": {"team": "qa"}, "is_default": false, "created_at": "2026-06-16T14:30:00Z", "updated_at": "2026-06-16T14:30:00Z" } ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------------------------------------------------------------------------ | | `404` | No pending upload found for this `profile_id`, or you don't have access. | | `422` | Body failed validation (for example an unsupported `browser_name`), or the archive was not found in storage. | # Delete a browser profile Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/delete DELETE /api/v2/browser-profiles/{profile_id} Remove a browser profile. Deletes a browser profile and its stored archive. Agents in your organization can no longer load it into future sessions. **Returns** `204 No Content` on success. *** ## Path parameters The profile's `id` (UUID). *** ## Examples ```bash cURL theme={null} curl -X DELETE https://agp.eu.hcompany.ai/api/v2/browser-profiles/a1b2c3d4-5678-90ab-cdef-1234567890ab \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() client.browser_profiles.delete_browser_profile(profile_id="a1b2c3d4-5678-90ab-cdef-1234567890ab") ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); await client.browserProfiles.deleteBrowserProfile({ profileId: "a1b2c3d4-5678-90ab-cdef-1234567890ab", }); ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------- | | `404` | Profile not found or you don't have access. | # Get the default browser profile Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/get-default GET /api/v2/browser-profiles/default Fetch your default profile for a browser. Fetches your [default profile](/computer-use-agents/browser/profiles#default-profiles) for a browser, or `404` when none exists yet. A default doesn't have to be set explicitly: the first session created with `use_default_browser_profile: true` auto-creates one. **Returns** the [profile object](/computer-use-agents/browser-profiles/retrieve). *** ## Query parameters Browser flavor to look up the default for, for example `chromium`. *** ## Examples ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/browser-profiles/default?browser_name=chromium" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() profile = client.browser_profiles.get_default_browser_profile(browser_name="chromium") print(profile.id, profile.name) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const profile = await client.browserProfiles.getDefaultBrowserProfile({ browserName: "chromium", }); console.log(profile.id, profile.name); ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------- | | `404` | No default profile is set for this browser. | # Initiate a profile upload Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/initiate-upload POST /api/v2/browser-profiles/initiate-upload Get a presigned URL to upload a browser profile archive. Starts the [profile upload flow](/computer-use-agents/browser/profiles#uploading-a-profile). Returns a presigned upload target so you can send the profile archive **directly to object storage**, and the bytes never pass through the Agent API. No request body is required. After uploading the archive, call [Complete upload](/computer-use-agents/browser-profiles/complete-upload) with the returned `profile_id` to finalize the profile. **Returns** `200` with the presigned upload target. `upload_url` and `upload_fields` are passed through from object storage unchanged, so send them exactly as returned, with the file as the last form field. They expire after `upload_expires_in` seconds. *** ## Response Id (UUID) reserved for the profile. Pass it to [Complete upload](/computer-use-agents/browser-profiles/complete-upload). Presigned object-storage URL to `POST` the archive to. Form fields that must accompany the upload, sent before the file as `multipart/form-data`. Seconds until the presigned target expires. *** ## Examples ```bash cURL theme={null} curl -X POST https://agp.eu.hcompany.ai/api/v2/browser-profiles/initiate-upload \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() upload = client.browser_profiles.initiate_browser_profile_upload() print(upload.profile_id, upload.upload_url) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const upload = await client.browserProfiles.initiateBrowserProfileUpload(); console.log(upload.profileId, upload.uploadUrl); ``` ```json Response theme={null} { "profile_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "upload_url": "https://storage.eu.hcompany.ai/browser-profiles", "upload_fields": { "key": "browser-profiles/pending/org_123/a1b2c3d4-5678-90ab-cdef-1234567890ab/profile.zip", "Content-Type": "application/zip", "x-amz-checksum-algorithm": "SHA256", "tagging": "statuspending", "bucket": "browser-profiles", "policy": "eyJ...", "x-amz-algorithm": "AWS4-HMAC-SHA256", "x-amz-credential": "AKIA.../20260206/eu-west-1/s3/aws4_request", "x-amz-date": "20260206T000000Z", "x-amz-signature": "abcd1234" }, "upload_expires_in": 3600 } ``` # List browser profiles Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/list GET /api/v2/browser-profiles Browse your organization's browser profiles. Returns the browser profiles owned by your organization, with offset-based pagination. **Returns** an object with `total`, `limit`, `offset`, and a `profiles` array of [profile objects](/computer-use-agents/browser-profiles/retrieve). *** ## Query parameters Maximum number of profiles to return. Between `1` and `1000`. Number of profiles to skip before collecting the page. *** ## Examples ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/browser-profiles?limit=10&offset=0" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() page = client.browser_profiles.list_browser_profiles(limit=10, offset=0) for profile in page.profiles: print(profile.id, profile.name) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const page = await client.browserProfiles.listBrowserProfiles({ limit: 10, offset: 0 }); for (const profile of page.profiles) { console.log(profile.id, profile.name); } ``` ```json Response theme={null} { "total": 1, "limit": 10, "offset": 0, "profiles": [ { "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "name": "acme-prod-login", "description": null, "browser_name": "chromium", "browser_version": "131", "s3_path": "browser-profiles/finished/org_123/a1b2c3d4-5678-90ab-cdef-1234567890ab/profile.zip", "file_size_bytes": 102400, "checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "usage_count": 3, "last_used_at": "2026-06-16T13:05:00Z", "labels": {"team": "qa"}, "is_default": false, "created_at": "2026-06-16T14:30:00Z", "updated_at": "2026-06-16T14:30:00Z" } ] } ``` # Retrieve a browser profile Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/retrieve GET /api/v2/browser-profiles/{profile_id} Fetch a browser profile by id. Fetches a single browser profile. **Returns** the profile object. *** ## Path parameters The profile's `id` (UUID). *** ## The browser profile object Unique profile identifier (UUID). Human-readable label. Optional free-text description. Browser the profile was captured with (for example `chromium`). Browser version string the profile was captured with. Internal storage path of the profile archive. Size of the stored archive in bytes. SHA-256 checksum of the stored archive, computed at upload time. Number of times the profile has been loaded into a session. ISO 8601 timestamp of the last time the profile was loaded, or `null` if never used. Key-value metadata you set on the profile. Whether this profile is your [default](/computer-use-agents/browser/profiles#default-profiles) for its `browser_name`. Set and cleared via the [set-default](/computer-use-agents/browser-profiles/set-default) and [unset-default](/computer-use-agents/browser-profiles/unset-default) endpoints. ISO 8601 creation timestamp. ISO 8601 timestamp of the last change. *** ## Examples ```bash cURL theme={null} curl "https://agp.eu.hcompany.ai/api/v2/browser-profiles/a1b2c3d4-5678-90ab-cdef-1234567890ab" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() profile = client.browser_profiles.get_browser_profile(profile_id="a1b2c3d4-5678-90ab-cdef-1234567890ab") print(profile.name, profile.browser_name, profile.usage_count) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const profile = await client.browserProfiles.getBrowserProfile({ profileId: "a1b2c3d4-5678-90ab-cdef-1234567890ab", }); console.log(profile.name, profile.browserName, profile.usageCount); ``` ```json Response theme={null} { "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "name": "acme-prod-login", "description": null, "browser_name": "chromium", "browser_version": "131", "s3_path": "browser-profiles/finished/org_123/a1b2c3d4-5678-90ab-cdef-1234567890ab/profile.zip", "file_size_bytes": 102400, "checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "usage_count": 3, "last_used_at": "2026-06-16T13:05:00Z", "labels": {"team": "qa"}, "is_default": false, "created_at": "2026-06-16T14:30:00Z", "updated_at": "2026-06-16T14:30:00Z" } ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------- | | `404` | Profile not found or you don't have access. | # Set the default browser profile Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/set-default PUT /api/v2/browser-profiles/{profile_id}/default Mark a profile as your default for its browser. Marks a profile as your [default](/computer-use-agents/browser/profiles#default-profiles) for its `browser_name`. Sessions created with `use_default_browser_profile: true` on the [Browser environment](/computer-use-agents/browser/configuration) load this profile. You have at most one default per browser, so setting a new default replaces the previous one for the same `browser_name` — no need to unset it first. **Returns** the updated [profile object](/computer-use-agents/browser-profiles/retrieve) with `is_default: true`. *** ## Path parameters The profile's `id` (UUID). *** ## Examples ```bash cURL theme={null} curl -X PUT "https://agp.eu.hcompany.ai/api/v2/browser-profiles/a1b2c3d4-5678-90ab-cdef-1234567890ab/default" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() profile = client.browser_profiles.set_default_browser_profile( profile_id="a1b2c3d4-5678-90ab-cdef-1234567890ab", ) print(profile.is_default) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const profile = await client.browserProfiles.setDefaultBrowserProfile({ profileId: "a1b2c3d4-5678-90ab-cdef-1234567890ab", }); console.log(profile.isDefault); ``` ```json Response theme={null} { "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "name": "acme-prod-login", "description": null, "browser_name": "chromium", "browser_version": "131", "s3_path": "browser-profiles/finished/org_123/a1b2c3d4-5678-90ab-cdef-1234567890ab/profile.zip", "file_size_bytes": 102400, "checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "usage_count": 3, "last_used_at": "2026-06-16T13:05:00Z", "labels": {"team": "qa"}, "is_default": true, "created_at": "2026-06-16T14:30:00Z", "updated_at": "2026-07-08T09:00:00Z" } ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------- | | `404` | Profile not found or you don't have access. | # Unset the default browser profile Source: https://hub.hcompany.ai/computer-use-agents/browser-profiles/unset-default DELETE /api/v2/browser-profiles/{profile_id}/default Clear the default flag on a profile. Clears the [default](/computer-use-agents/browser/profiles#default-profiles) flag on a profile. The profile itself is untouched; only the default marking is removed. The next session created with `use_default_browser_profile: true` for that browser auto-creates a fresh empty default rather than reusing this profile. **Returns** the updated [profile object](/computer-use-agents/browser-profiles/retrieve) with `is_default: false`. *** ## Path parameters The profile's `id` (UUID). *** ## Examples ```bash cURL theme={null} curl -X DELETE "https://agp.eu.hcompany.ai/api/v2/browser-profiles/a1b2c3d4-5678-90ab-cdef-1234567890ab/default" \ -H "Authorization: Bearer $HAI_API_KEY" ``` ```python Python theme={null} from hai_agents import Client client = Client() profile = client.browser_profiles.unset_default_browser_profile( profile_id="a1b2c3d4-5678-90ab-cdef-1234567890ab", ) print(profile.is_default) ``` ```typescript TypeScript theme={null} import { HaiAgentsClient } from "hai-agents"; const client = new HaiAgentsClient(); const profile = await client.browserProfiles.unsetDefaultBrowserProfile({ profileId: "a1b2c3d4-5678-90ab-cdef-1234567890ab", }); console.log(profile.isDefault); ``` *** ## Errors | Status | Cause | | ------ | ------------------------------------------- | | `404` | Profile not found or you don't have access. | # Configure the browser Source: https://hub.hcompany.ai/computer-use-agents/browser/configuration Define a Browser environment: its fields, modes, and actions. The Browser (`kind: "web"`) is the environment H ships today: a managed web browser the platform provisions per session, or, with [local control](/computer-use-agents/browser/local-control), Chrome on your own machine. Reference a [built-in browser](/computer-use-agents/environments/overview#built-in-environments) by catalog name (like `"h/browser"`), or define one inline in an agent's `environments` list. When you define a Browser inline, only `id` is required; every other field has a default. Reference a catalog entry instead and it supplies them for you. | Field | Default | Description | | ----------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | required | Catalog identifier for the environment. | | `kind` | `"web"` | Environment type; a browser is `web`. | | `start_url` | `"https://www.bing.com"` | Initial URL to open. | | `headless` | `false` | Run the browser without a visible window. | | `mode` | `{"type": "visual"}` | How the agent perceives and drives the browser. An object keyed by `type` (`visual` or `text`); see [Modes](#modes). | | `vault_id` | `null` | Id of a [vault](/computer-use-agents/vaults/overview) to bind to this browser, letting the agent sign in to sites with secrets resolved from the vault. Must reference a vault in your organization. Cloud-hosted browsers only. Omit to run without secret access. | | `browser_profile_id` | `null` | Id of a [browser profile](/computer-use-agents/browser/profiles) to load into this browser, restoring saved cookies and storage so the agent starts the session already signed in. Must reference a profile in your organization. Cloud-hosted browsers only. Omit to start with a fresh profile. | | `use_default_browser_profile` | `false` | Load your [default browser profile](/computer-use-agents/browser/profiles#default-profiles) for this browser instead of naming one, auto-creating an empty one on first use. The session saves its final state back automatically when it ends (best-effort: concurrent sessions run read-only). Mutually exclusive with `browser_profile_id`. Cloud-hosted browsers only. | | `persist_browser_profile` | `false` | Save the session's final browser state (cookies, storage) back into the loaded profile when it ends. Requires `browser_profile_id` or `use_default_browser_profile`. Best-effort: only one active session at a time may persist a given profile — if another writer is active, the session starts read-only instead of failing. See [Persisting state back](/computer-use-agents/browser/profiles#persisting-state-back-into-the-profile). | | `network` | `null` | Network settings for the session. Holds `managed_proxy` to have H provision a proxy for the browser's egress, or `proxy_url` to route through your own (set only one). Applied when a cloud browser session is provisioned. See [Proxy](/computer-use-agents/browser/proxy). | | `host` | `"cloud"` | Where the browser runs: `cloud` on H infrastructure, or `user_device` for Chrome on your own machine. See [Local browser](/computer-use-agents/browser/local-control). | | `session_id` | `null` | Id of the command channel a `user_device` browser is served on. The Python SDK sets it when it starts the connection for you; set it yourself only to attach to one you run with [`hai local browser`](/computer-use-agents/browser/local-control#run-a-session). | ## Modes At each step the agent receives a fresh observation of the page, then chooses one action. The `mode` field sets what that observation contains and which [actions](#actions) are on the table. | Mode | What the agent sees | How it acts | Reach for it when | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | | `visual` (default) | A screenshot of the viewport (`1200×1200` by default), with the current URL and open tabs. Set `markdown: true` to also include the page's text in the same observation. | Points at on-screen targets; the platform resolves each one to exact click and type coordinates. | General web work: clicking, filling forms, anything that needs the rendered page. | | `text` | The page as text only, no screenshot, split into chunks of about `chunk_size` characters (`20000` by default). | Reads and pages through the chunks and follows links by URL. Read-only: no clicking or typing. | Reading and research at scale: search, scraping, link-heavy navigation. | * **Cost and speed track the screenshots.** `text` mode sends no images, so it uses the fewest tokens and runs fastest; `visual` sends a screenshot every step, and `markdown: true` adds the full page text on top of it. Default to `visual`, and switch to `text` when the task is pure reading. * **`text` mode pages through long content.** Rather than scrolling, the page is cut into `chunk_size`-sized chunks; the agent moves between them and each observation tells it which chunk it is on. Raise `chunk_size` to fit more per step, at the cost of more tokens per observation. * **Watch what the agent saw.** Every observation rides the [event stream](/computer-use-agents/sessions/events#observation-shapes), as a `web` observation in `visual` mode and a `textual_web` observation in `text` mode, so you can replay each step exactly as the agent perceived it. ### Mode fields `mode` is an object selected by `type`. Each shape carries only the fields that apply to it, so illegal combinations cannot be expressed. `visual` (default) renders screenshots and acts by viewport coordinates: | Field | Default | Description | | ---------- | ---------- | ----------------------------------------------------------------------- | | `type` | `"visual"` | Selects visual mode. | | `width` | `1200` | Viewport width in pixels. Must be a positive integer. | | `height` | `1200` | Viewport height in pixels. Must be a positive integer. | | `markdown` | `false` | Also include the viewport's text as markdown alongside each screenshot. | `text` serves read-only paginated markdown with no screenshots: | Field | Default | Description | | ------------ | -------- | ------------------------------------------------------------------- | | `type` | `"text"` | Selects text mode. | | `chunk_size` | `20000` | Characters of page text shown per page. Must be a positive integer. | ```json Text-mode browser theme={null} { "id": "research-browser", "kind": "web", "mode": { "type": "text", "chunk_size": 20000 } } ``` ## Actions Each mode fixes the set of actions available to the agent. It chooses them autonomously as it works; you never call them directly and there is no per-agent tool list to configure. To shape how it uses them, set the agent's [`instructions`](/computer-use-agents/agents/overview). | Action | Description | Visual | Text | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----: | :--: | | `go_to_web` | Navigate to a URL. | ✓ | ✓ | | `go_back_web` | Go back in the browser history. | ✓ | ✓ | | `refresh_web` | Refresh the current page. | ✓ | ✓ | | `switch_tab_web` | Switch to another tab, or open a new one. | ✓ | ✓ | | `close_tab_web` | Close a tab. | ✓ | ✓ | | `click_web` | Click at viewport coordinates. | ✓ | | | `write` | Focus an input at coordinates and type into it. | ✓ | | | `fill_secret_at` | Fill a vault-resolved secret (e.g. `password`, `totp`) into a field at coordinates so the agent can sign in on your behalf. The value is injected directly into the page and never enters the agent's context. Offered only when a [vault](/computer-use-agents/vaults/overview) is bound to the browser via `vault_id` and can match a credential for the current page. | ✓ | | | `select_option` | Pick an option from a native `