> ## Documentation Index
> Fetch the complete documentation index at: https://hub.hcompany.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> H Platform has four products: the Agents API (managed computer-use agents, base URL https://agp.eu.hcompany.ai/api/v2 or https://agp.hcompany.ai/api/v2 for the US), the Models API (OpenAI-compatible Holo vision-language models at https://api.hcompany.ai/v1), HoloDesktop CLI (Holo on the user's own desktop), and HoloTab (a free no-code Chrome extension that runs Holo in the user's browser, with recordable routines and schedules).
> Authenticate with a bearer API key from the HAI_API_KEY environment variable. SDKs: `pip install hai-agents` (Python, `from hai_agents import Client`) and `npm install hai-agents` (TypeScript, `import { HaiAgentsClient } from "hai-agents"`). CLI: `hai`.
> Agents do work in a browser or on a desktop; describe the task as an imperative instruction. To run a task quickly, prefer the pre-built agent `h/web-surfer-flash`. Read results from the session's `latest_answer` after it reaches a terminal status.
> Sessions are the unit of work; wait for a terminal status (completed, failed, timed_out, interrupted) before reading the answer. Use webhooks or the `changes` long-poll endpoint to follow progress.

# Get typed answers

> Get the agent's final answer as typed, schema-validated JSON: pass a Pydantic or Zod schema in the SDK, or set answer_format as a JSON Schema.

export const StructuredOutput = () => {
  const stroke = {
    fill: "none",
    stroke: "currentColor",
    strokeWidth: 1.75,
    strokeLinecap: "round",
    strokeLinejoin: "round"
  };
  const S = c => ({
    className: c,
    ...stroke
  });
  const icons = {
    schema: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" /></svg>,
    agent: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2M20 14h2M15 13v2M9 13v2" /></svg>,
    code: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="m16 18 6-6-6-6" /><path d="m8 6-6 6 6 6" /></svg>
  };
  const Card = ({icon, title, sub, children}) => <div className="flex shrink-0 flex-col self-center rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
      <div className="flex items-center gap-2.5">
        <span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200">{icon("h-5 w-5")}</span>
        <div>
          <div className="whitespace-nowrap text-base font-semibold leading-6 text-zinc-900 dark:text-zinc-100">{title}</div>
          <div className="whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400">{sub}</div>
        </div>
      </div>
      {children}
    </div>;
  const Arrow = ({top}) => <div className="flex min-w-[104px] flex-1 flex-col items-stretch justify-center gap-1 px-3 text-center text-xs leading-4 text-zinc-500 dark:text-zinc-400">
      <span className="whitespace-nowrap">{top}</span>
      <div className="flex items-center text-zinc-400 dark:text-zinc-600">
        <span className="h-px flex-1 bg-current" />
        <svg className="-ml-px h-3 w-2 shrink-0" viewBox="0 0 8 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M1 1.5 6 6l-5 4.5" /></svg>
      </div>
    </div>;
  const Chip = ({children}) => <span className="whitespace-nowrap rounded-md bg-zinc-100 px-2 py-0.5 font-mono text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300">{children}</span>;
  return <div className="not-prose my-8 overflow-x-auto">
      <div className="flex min-w-[680px] items-stretch">
        <Card icon={icons.schema} title="Your schema" sub="the answer's shape">
          <div className="mt-4 flex gap-1.5">
            <Chip>Pydantic</Chip>
            <Chip>Zod</Chip>
            <Chip>JSON Schema</Chip>
          </div>
        </Card>

        <Arrow top="answer_format" />

        <Card icon={icons.agent} title="Agent" sub="answers in that shape">
          <div className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Off-shape answers<br />are retried</div>
        </Card>

        <Arrow top="validated" />

        <Card icon={icons.code} title="Your code" sub="typed, not a string">
          <div className="mt-4">
            <Chip>result.answer</Chip>
          </div>
        </Card>
      </div>
    </div>;
};

<StructuredOutput />

By default the agent answers in free-form text. Give it a schema and the answer becomes JSON that conforms to it.

## With the SDKs

Pass a [Pydantic](https://docs.pydantic.dev) model as `answer_schema` or a [Zod v4](https://zod.dev) schema as `answerSchema`. The SDK derives the JSON Schema, sends it, and parses the final answer into a typed instance.

<CodeGroup>
  ```python Python theme={"system"}
  from pydantic import BaseModel
  from hai_agents import Client

  class Story(BaseModel):
      title: str
      url: str

  class Stories(BaseModel):
      stories: list[Story]

  client = Client()
  result = client.run_session(
      agent="h/web-surfer-flash",
      messages="Open Hacker News and list the top 3 stories with their URLs.",
      answer_schema=Stories,
  )

  for story in result.answer.stories:  # result.answer is a Stories instance
      print(story.title, story.url)
  ```

  ```typescript TypeScript theme={"system"}
  import { HaiAgentsClient } from "hai-agents";
  import { z } from "zod";

  const Stories = z.object({
    stories: z.array(z.object({ title: z.string(), url: z.string() })),
  });

  const client = new HaiAgentsClient();
  const result = await client.runSession({
    agent: "h/web-surfer-flash",
    messages: "Open Hacker News and list the top 3 stories with their URLs.",
    answerSchema: Stories,
  });

  for (const story of result.answer?.stories ?? []) {
    console.log(story.title, story.url); // typed via z.infer
  }
  ```
</CodeGroup>

| Case | Behavior |
| - | - |
| `completed`, answer missing or not matching | Raises `AnswerValidationError` with the raw payload attached. |
| Not `completed` (failed, or `idle` without an answer) | No validation. The answer passes through as-is, `None` / `undefined` when absent. |
| Raw wire value | Always on `final_changes` / `finalChanges`, next to the parsed answer. |
| Schema plus an `agent.answer_format` override | Rejected: both set the same field. |

## With a JSON Schema

Without an SDK, set a JSON Schema as [`answer_format`](/agents-api/agents/overview) on the agent, or per run with the `agent.answer_format` [override](/agents-api/sessions/overview#overrides). The settled answer (`latest_answer` on the session, `answer` on [`changes`](/agents-api/sessions/changes)) is a JSON object matching it.

<CodeGroup>
  ```bash CLI theme={"system"}
  hai run "Open Hacker News and list the top 3 story titles." --agent h/web-surfer-flash \
    --override 'agent.answer_format={"type":"object","properties":{"titles":{"type":"array","items":{"type":"string"}}},"required":["titles"]}'
  ```

  ```bash cURL theme={"system"}
  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": "Open Hacker News and list the top 3 story titles.",
      "overrides": {
        "agent.answer_format": {
          "type": "object",
          "properties": {"titles": {"type": "array", "items": {"type": "string"}}},
          "required": ["titles"]
        }
      }
    }'
  ```
</CodeGroup>

If every attempt fails to match the schema, the session fails with [`error_code: answer_validation`](/agents-api/errors#error-codes).

## Chaining agents

A typed answer makes an agent callable like a function: one agent's output feeds the next. Here a scout finds sources, then readers run in parallel (each counts against your [concurrency limit](/agents-api/plans-and-limits#concurrent-sessions)):

<CodeGroup>
  ```python Python theme={"system"}
  import asyncio
  from pydantic import BaseModel
  from hai_agents import AsyncClient

  class Sources(BaseModel):
      urls: list[str]

  class Brief(BaseModel):
      key_facts: list[str]

  async def main() -> None:
      client = AsyncClient()
      scout = await client.run_session(
          agent="h/web-surfer-flash",
          messages="Find the 5 best sources on EU AI Act enforcement",
          answer_schema=Sources,
      )
      briefs = await asyncio.gather(*(
          client.run_session(agent="h/web-surfer-flash", messages=f"Extract the key facts from {url}", answer_schema=Brief)
          for url in scout.answer.urls
      ))

  asyncio.run(main())
  ```

  ```typescript TypeScript theme={"system"}
  const Sources = z.object({ urls: z.array(z.string()) });
  const Brief = z.object({ keyFacts: z.array(z.string()) });

  const scout = await client.runSession({
    agent: "h/web-surfer-flash",
    messages: "Find the 5 best sources on EU AI Act enforcement",
    answerSchema: Sources,
  });
  const briefs = await Promise.all(
    (scout.answer?.urls ?? []).map((url) =>
      client.runSession({ agent: "h/web-surfer-flash", messages: `Extract the key facts from ${url}`, answerSchema: Brief }),
    ),
  );
  ```
</CodeGroup>

Related: [Configure an agent](/agents-api/agents/overview#configure-an-agent), [Parallelize work with subagents](/agents-api/multi-agent)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.