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

# Build with the API

> One short program that creates an agent, has a conversation, and reads the reply, with every call explained. Python or TypeScript.

New to Orca? Take the [ten-minute tour](/start-here) first. This page is the code path: you write one short program with the official `openai` package that:

1. Creates an **agent** (the model and its instructions).
2. Opens a **session** (a conversation) and sends a first message.
3. Waits for the agent to finish its **turn**.
4. Prints the saved conversation.
5. Sends a follow-up message in the same conversation.

It takes about five minutes. No sandbox is involved, so it uses no machine time. If any of these words are new, [how Orca works](/concepts) explains them, but you do not need it to follow along.

<Steps>
  <Step title="Get your connection details">
    You need three connection details:

    * **Base URL**: `https://api.orcapods.ai/v1`. Not `/agents`, and not `api.openai.com`.
    * **API key**: create an Orca key in the dashboard under Settings, API keys (it is shown once). An OpenAI key does not work here.
    * **Model**: `orca/openrouter/<OpenRouter id>` to run on your Orca credit, or `<provider>/<id>` with your own provider key. If you are unsure, [list the Orca credit models](/providers#discover-orca-credit-models).

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
    export ORCA_BASE_URL="https://api.orcapods.ai/v1"
    export ORCA_API_KEY="YOUR_ORCA_KEY"
    export ORCA_MODEL="orca/openrouter/YOUR_OPENROUTER_MODEL_ID"
    ```

    Treat the key like a password: keep it in your backend or a secret manager, never in browser code or source control.
  </Step>

  <Step title="Install the OpenAI client">
    Orca works with the official `openai` package. These docs are tested against the versions below; the Agents API is still a beta, so other versions may differ.

    <CodeGroup>
      ```bash Python theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
      python3 -m venv .venv
      source .venv/bin/activate
      python -m pip install openai==3.13.0
      ```

      ```bash TypeScript theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
      npm init -y
      npm pkg set type=module
      npm install openai@7.15.0
      ```
    </CodeGroup>

    For Python, use Python 3.10 or later: the pinned `openai` package does not install on older versions, such as the `python3` that comes with macOS. For TypeScript, use Node 22.18+ or Node 24+, which can run `.ts` files directly, or your usual TypeScript runner.
  </Step>

  <Step title="Write the program">
    Save this as `quickstart.py` or `quickstart.ts`. The comments mark each of the five steps above. The `finally` block deletes the agent and session at the end so the demo leaves nothing behind.

    <Tabs>
      <Tab title="Python">
        ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
        """Create an agent, run one turn, and read the answer.

        Needs: an Orca API key. Set ORCA_BASE_URL, ORCA_API_KEY, and ORCA_MODEL.
        """

        import os
        import time

        import openai

        MODEL = os.environ["ORCA_MODEL"]
        client = openai.OpenAI(
            api_key=os.environ["ORCA_API_KEY"],
            base_url=os.environ["ORCA_BASE_URL"],
            max_retries=0,
            timeout=60,
        )
        agents = client.beta.agents
        sessions = agents.sessions
        agent_id = session_id = None


        def finished(session_id, after_turn_id=None):
            """Poll until the newest turn reaches a terminal state."""
            deadline = time.monotonic() + 300
            while time.monotonic() < deadline:
                # Skip subagent turns: they finish before the turn that spawned them.
                turns = [t for t in sessions.turns.list(session_id).data if t.subagent_id is None]
                if (turns and turns[0].id != after_turn_id
                        and turns[0].status in ("completed", "failed", "cancelled")):
                    return turns[0]
                time.sleep(0.5)
            raise SystemExit("Turn did not finish in time")


        try:
            # An agent holds the reusable configuration: model, instructions, tools.
            agent = agents.create(
                model=MODEL,
                name="quickstart",
                instructions="You are a concise assistant.",
            )
            agent_id = agent.id
            print("agent:", agent.id, agent.model)

            # A session holds one conversation. Passing input starts the first turn.
            # 'none' means this session gets no sandbox; see our environments guide.
            session = sessions.create(
                agent_id=agent.id,
                environment={"type": "none"},
                input="Write a haiku about the ocean.",
            )
            session_id = session.id
            print("session:", session.id, session.status)

            turn = finished(session.id)
            print("turn:", turn.id, turn.status)
            if turn.usage:
                print(f"tokens: {turn.usage.input_tokens} in, {turn.usage.output_tokens} out")

            # Conversation history is durable and paginated; items include the reply.
            for item in reversed(sessions.items.list(session.id, limit=10).data):
                for part in getattr(item, "content", None) or []:
                    if getattr(part, "text", None):
                        print(f"{item.role}: {part.text}")

            # A follow-up turn resumes the same stored conversation.
            sessions.events.create(
                session.id,
                events=[
                    {
                        "type": "agent.session.input.message",
                        "input": [
                            {
                                "role": "user",
                                "content": [
                                    {
                                        "type": "input_text",
                                        "text": "And again, briefly.",
                                    }
                                ],
                            }
                        ],
                    }
                ],
            )
            print("follow-up turn:", finished(session.id, after_turn_id=turn.id).status)
            print("turns on this page:", len(sessions.turns.list(session.id).data))
        finally:
            if session_id:
                try:
                    sessions.delete(session_id)
                except openai.APIError:
                    pass
            if agent_id:
                try:
                    agents.delete(agent_id)
                except openai.APIError:
                    pass
            client.close()
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
        /**
         * Create an agent, run one turn, and read the answer.
         *
         * Needs: an Orca API key. Set ORCA_BASE_URL, ORCA_API_KEY, and ORCA_MODEL.
         */
        import OpenAI from 'openai';

        const MODEL = process.env.ORCA_MODEL!;
        const client = new OpenAI({
          apiKey: process.env.ORCA_API_KEY!,
          baseURL: process.env.ORCA_BASE_URL!,
          maxRetries: 0,
          timeout: 60_000,
        });
        const agents = client.beta.agents;
        const sessions = agents.sessions;
        let agentId: string | undefined;
        let sessionId: string | undefined;

        const pause = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));

        /** Poll until the newest turn reaches a terminal state. */
        async function finished(session: string, afterTurnId?: string) {
          for (let attempt = 0; attempt < 600; attempt++) {
            // Skip subagent turns: they finish before the turn that spawned them.
            const [turn] = (await sessions.turns.list(session)).data.filter(t => !t.subagent_id);
            const done = ['completed', 'failed', 'cancelled'].includes(turn?.status ?? '');
            if (turn && turn.id !== afterTurnId && done) return turn;
            await pause();
          }
          throw new Error('Turn did not finish in time');
        }

        try {
          // An agent holds the reusable configuration: model, instructions, tools.
          const agent = await agents.create({
            model: MODEL,
            name: 'quickstart',
            instructions: 'You are a concise assistant.',
          });
          agentId = agent.id;
          console.log('agent:', agent.id, agent.model);

          // A session holds one conversation. Passing input starts the first turn.
          // 'none' means this session gets no sandbox; see our environments guide.
          const session = await sessions.create({
            agent_id: agent.id,
            environment: { type: 'none' },
            input: 'Write a haiku about the ocean.',
          });
          sessionId = session.id;
          console.log('session:', session.id, session.status);

          const turn = await finished(session.id);
          console.log('turn:', turn.id, turn.status);
          if (turn.usage) console.log(`tokens: ${turn.usage.input_tokens} in, ${turn.usage.output_tokens} out`);

          // Conversation history is durable and paginated; items include the reply.
          const items = (await sessions.items.list(session.id, { limit: 10 })).data;
          for (const item of [...items].reverse()) {
            for (const part of (item as any).content ?? []) {
              if (part.text) console.log(`${(item as any).role}: ${part.text}`);
            }
          }

          // A follow-up turn resumes the same stored conversation.
          await sessions.events.create(session.id, {
            events: [
              {
                type: 'agent.session.input.message',
                input: [
                  { role: 'user', content: [{ type: 'input_text', text: 'And again, briefly.' }] },
                ],
              },
            ],
          });
          console.log('follow-up turn:', (await finished(session.id, turn.id)).status);
          console.log('turns on this page:', (await sessions.turns.list(session.id)).data.length);
        } finally {
          if (sessionId) await sessions.delete(sessionId).catch(() => {});
          if (agentId) await agents.delete(agentId).catch(() => {});
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Run it">
    <CodeGroup>
      ```bash Python theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
      python quickstart.py
      ```

      ```bash TypeScript theme={"theme":{"light":"vitesse-light","dark":"vitesse-black"}}
      node --experimental-strip-types quickstart.ts
      ```
    </CodeGroup>

    You should see the agent and session IDs, the turn finishing with status `completed`, token usage, the conversation (your message and the haiku), and then the follow-up turn completing.
  </Step>
</Steps>

## What just happened

**`agents.create`** saved a configuration. Nothing ran yet, and no tokens were used.

**`sessions.create` with `input`** opened a conversation and sent the first message. The call returned right away, while the agent was still working. That is why the program has a `finished()` helper: it checks the session's turn list every half second until the newest turn reaches a final status (`completed`, `failed`, or `cancelled`). [Turns and statuses](/turn-lifecycle) explains why Orca works this way and what each status means.

**`sessions.items.list`** read the saved history. The history includes your message, the agent's reply, and (for agents with tools) every tool call. The list is newest first, so the program reverses it to print in order.

**`sessions.events.create`** sent a follow-up message as an *input event*. Because it is the same session, the agent remembers the poem. The program passes the first turn's ID to `finished()` so it waits for the **new** turn instead of seeing the old one as already done.

Polling is the simplest way to wait. To show the reply word by word as it is generated, use [streaming](/guides/streaming) instead.

<Warning>
  In your own application, treat `failed` and `cancelled` as real outcomes, not just log lines. Also, if your wait times out, the turn is still running on the server: check its status before you send the same message again, or the agent will do the work twice.
</Warning>

## If something went wrong

| Symptom | Likely cause |
| - | - |
| `AttributeError: ... has no attribute 'agents'` | An older `openai` package. Install the version above. |
| `401` | The API key is wrong, or it is an OpenAI key instead of an Orca key. |
| `404` on every request | The base URL does not end in `/v1`, or has `/agents` appended. |
| The turn ends `failed` | Print `turn.error`. A common cause is a model name Orca does not offer; [list the available models](/providers#discover-orca-credit-models). |

More in [troubleshooting](/troubleshooting).

## Next steps

<CardGroup cols={2}>
  <Card title="Stream responses" icon="bolt" href="/guides/streaming">Show text as it is generated instead of polling.</Card>
  <Card title="Give your agent tools" icon="wrench" href="/guides/function-tools">Let the agent call functions in your code.</Card>
  <Card title="Run code in a sandbox" icon="cube" href="/guides/environments">Let the agent run commands and produce files.</Card>
  <Card title="How Orca works" icon="diagram-project" href="/concepts">The full picture of agents, sessions, turns, and tools.</Card>
</CardGroup>


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