# Serve as an API (/use-cases/product-agents/serve-as-an-api)



Product teams can connect web, mobile, and server-side clients to the same AgentOS backend. Registered agents become FastAPI services with streaming run routes, while AgentOS manages sessions, memory, knowledge, traces, evaluations, and approvals through the same API.

```python title="customer_agent.py"
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")

agent = Agent(
    id="customer-agent",
    model=OpenAIResponses(id="gpt-5.5"),
    db=db,
    add_history_to_context=True,
    enable_agentic_memory=True,
)

agent_os = AgentOS(
    agents=[agent],
    db=db,
    cors_allowed_origins=[
        "http://localhost:3000",
        "https://app.yourproduct.com",
        "https://os.agno.com",
    ],
)
app = agent_os.get_app()

if __name__ == "__main__":
    agent_os.serve(app="customer_agent:app", port=7777)
```

Supplying `cors_allowed_origins` replaces the AgentOS defaults. Include every browser and AgentOS UI origin that needs to call the backend.

## Run the API [#run-the-api]

<Steps>
    <Step title="Set up your virtual environment">
      <CodeBlockTabs defaultValue="Mac">
        <CodeBlockTabsList>
          <CodeBlockTabsTrigger value="Mac">
            Mac
          </CodeBlockTabsTrigger>

          <CodeBlockTabsTrigger value="Windows">
            Windows
          </CodeBlockTabsTrigger>
        </CodeBlockTabsList>

        <CodeBlockTab value="Mac">
          ```bash
          uv venv --python 3.12
          source .venv/bin/activate
          ```
        </CodeBlockTab>

        <CodeBlockTab value="Windows">
          ```bash
          uv venv --python 3.12
          .venv\Scripts\activate
          ```
        </CodeBlockTab>
      </CodeBlockTabs>
    </Step>

  <Step title="Install dependencies">
    ```bash
    uv pip install -U "agno[os]" openai sqlalchemy "psycopg[binary]"
    ```
  </Step>

  <Step title="Export your OpenAI API key">
    <CodeBlockTabs defaultValue="macOS / Linux">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="macOS / Linux">
          macOS / Linux
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Windows">
          Windows
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="macOS / Linux">
        ```bash
        export OPENAI_API_KEY="your_openai_api_key_here"
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Windows">
        ```powershell
        $Env:OPENAI_API_KEY="your_openai_api_key_here"
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Step>

    <Step title="Run PgVector">
      <CodeBlockTabs defaultValue="macOS / Linux">
        <CodeBlockTabsList>
          <CodeBlockTabsTrigger value="macOS / Linux">
            macOS / Linux
          </CodeBlockTabsTrigger>

          <CodeBlockTabsTrigger value="Windows">
            Windows
          </CodeBlockTabsTrigger>
        </CodeBlockTabsList>

        <CodeBlockTab value="macOS / Linux">
          ```bash
          docker run -d \
            -e POSTGRES_DB=ai \
            -e POSTGRES_USER=ai \
            -e POSTGRES_PASSWORD=ai \
            -e PGDATA=/var/lib/postgresql \
            -v pgvolume:/var/lib/postgresql \
            -p 5532:5432 \
            --name pgvector \
            agnohq/pgvector:18
          ```
        </CodeBlockTab>

        <CodeBlockTab value="Windows">
          ```powershell
          docker run -d `
            -e POSTGRES_DB=ai `
            -e POSTGRES_USER=ai `
            -e POSTGRES_PASSWORD=ai `
            -e PGDATA=/var/lib/postgresql `
            -v pgvolume:/var/lib/postgresql `
            -p 5532:5432 `
            --name pgvector `
            agnohq/pgvector:18
          ```
        </CodeBlockTab>
      </CodeBlockTabs>
    </Step>

  <Step title="Start AgentOS">
    Save the code as `customer_agent.py`, then run:

    ```bash
    python customer_agent.py
    ```
  </Step>
</Steps>

## Calling it from a surface [#calling-it-from-a-surface]

Every run endpoint takes the same shape, whether the caller is a browser widget or a backend job.

```bash
curl -X POST http://localhost:7777/agents/customer-agent/runs \
  -F 'message=Summarize this thread' \
  -F 'user_id=sarah@acme.com' \
  -F 'session_id=thread-42' \
  -F 'stream=false'
```

```json
{
  "run_id": "run_abc123",
  "session_id": "thread-42",
  "user_id": "sarah@acme.com",
  "agent_id": "customer-agent",
  "status": "COMPLETED",
  "content": "..."
}
```

After enabling authorization, a browser widget can stream with a JWT:

```javascript
async function askCustomerAgent(message, threadId, jwt) {
  const body = new FormData();
  body.append("message", message);
  body.append("session_id", threadId);
  body.append("stream", "true");

  const res = await fetch("https://os.yourproduct.com/agents/customer-agent/runs", {
    method: "POST",
    headers: { "Authorization": `Bearer ${jwt}` },
    body,
  });
  return res.body; // SSE stream into the UI
}
```

| Want                        | Pass                                            |
| --------------------------- | ----------------------------------------------- |
| Token stream for a live UI  | `stream=true` (Server-Sent Events, the default) |
| A single JSON response      | `stream=false`                                  |
| Long job, poll later        | `background=true` and `stream=false`            |
| User and thread attribution | `user_id` and a per-thread `session_id`         |

## AgentOS endpoints [#agentos-endpoints]

| Endpoint group     | Covers                                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
| Runs               | Create, stream, cancel, run in background, resume disconnected streams                                       |
| Sessions           | Create, list, rename, delete, and pull every run in a session. Per-user enforcement requires user isolation. |
| Memory             | Create, update, delete, search user memories                                                                 |
| Knowledge          | Add, update, search, and delete indexed content                                                              |
| Traces and metrics | Per-run spans when tracing is enabled, plus token usage and model metrics                                    |
| Evaluations        | Run and retrieve agent and team evaluation results                                                           |
| Approvals          | List and resolve paused approval requests                                                                    |
| Schedules          | Create, update, trigger, enable, disable, and delete recurring runs                                          |

Browse the live OpenAPI spec at the `/docs` endpoint of your running AgentOS.

## Custom routes [#custom-routes]

`AgentOS` is a FastAPI app. Add routes for webhooks, dashboards, or product-specific endpoints. The agent is a regular Python object you can call from anywhere.

<Warning>
  A public Stripe webhook must read the raw request body and verify its `Stripe-Signature` with the endpoint secret before parsing or passing an event to the agent. Never accept an unverified decoded dictionary. See [Stripe's webhook signature guide](https://docs.stripe.com/webhooks/signature).
</Warning>

## Auth [#auth]

Set `authorization=True` to require a valid JWT on protected central routes. Set `JWT_VERIFICATION_KEY` or `JWT_JWKS_FILE` before building the app. Add `user_isolation=True` to filter user-scoped data for non-admin callers.

```python
from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
    authorization_config=AuthorizationConfig(user_isolation=True),
    cors_allowed_origins=[
        "http://localhost:3000",
        "https://app.yourproduct.com",
        "https://os.agno.com",
    ],
)
```

AgentOS validates the token before agent code runs. The JWT `sub` pins `user_id`, and the `scopes` claim drives RBAC. An optional `session_id` claim overrides the form value; otherwise the client supplies the session ID with the request.

## Next steps [#next-steps]

| Task                          | Guide                                              |
| ----------------------------- | -------------------------------------------------- |
| Add Slack or browser surfaces | [Interfaces](/use-cases/product-agents/interfaces) |
| Lock down endpoints           | [Security and auth](/features/security-and-auth)   |

## Developer Resources [#developer-resources]

* [Agent API](/features/api)
* [Connect your AgentOS](/agent-os/connect-your-os)
* [Deploy](/deploy/introduction)
