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

# Connect a remote agent

> Expose your deployed agent as an HTTP endpoint, define its request schema, and register it as a remote agent in Arize.

Remote agent experiments call a `POST` endpoint that *you* host. Your agent can be built on any framework — Arize doesn't see it directly, only the requests and responses. This page covers what your endpoint needs to do and how to register it in Arize.

## Endpoint requirements

Your agent must expose an HTTP endpoint that:

1. Accepts `POST` requests with `Content-Type: application/json`.
2. Reads the request body as JSON.
3. Returns a JSON response body (any shape — Arize stores it verbatim).
4. Is reachable from Arize's coordinator over the public internet (or a VPC peering setup, for self-hosted deployments).

There are **no** requirements on response shape, status codes beyond 200 (failures are recorded with their error), or response time below the configured timeout.

## Request body shape

Arize sends the body you templated, hydrated with the current dataset row, with the fields at the **top level**. It adds one reserved key, `arize_metadata`, beside them:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "goal": "Plan a 3-day trip to Tokyo",
  "config": { "model": "claude-sonnet-4-6", "max_turns": 12 },
  "arize_metadata": {
    "space_id": "sp...",
    "agent_id": "ag...",
    "dataset_id": "ds...",
    "experiment_id": "exp...",
    "run_id": "run...",
    "example_id": "ex...",
    "project_name": "Agent Experiment Traces",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "span_id": "00f067aa0ba902b7",
    "requested_by": "someone@example.com"
  }
}
```

There is no `input` wrapper. Your agent reads `goal` and `config` directly from the body and ignores `arize_metadata` (or uses it for trace correlation).

`arize_metadata` fields:

| Field           | Always present | Notes                                                                                                                            |
| --------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `space_id`      | Yes            | The Arize space the experiment runs in.                                                                                          |
| `agent_id`      | Yes            | The remote agent configuration being called.                                                                                     |
| `dataset_id`    | Yes            |                                                                                                                                  |
| `experiment_id` | Yes            |                                                                                                                                  |
| `run_id`        | Yes            | Unique per dataset row per experiment.                                                                                           |
| `example_id`    | Yes            | The dataset row.                                                                                                                 |
| `project_name`  | When traced    | The project the experiment-run span lives in. Currently always `Agent Experiment Traces`.                                        |
| `trace_id`      | When traced    | 32-char hex trace id of the experiment-run span. Matches `traceparent`.                                                          |
| `span_id`       | When traced    | 16-char hex span id of the experiment-run span. Matches `traceparent`.                                                           |
| `requested_by`  | If enabled     | Email of the user who launched the experiment. Only sent when **Include requester email in agent requests** is on for the agent. |

Because `arize_metadata` is reserved, your input schema cannot declare it and your request body cannot include it. Arize rejects both at save time.

## Headers Arize sends

| Header                           | Purpose                                                                                                                                             |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type: application/json` | Body encoding.                                                                                                                                      |
| `Authorization` *(optional)*     | Bearer token or custom auth headers you configured.                                                                                                 |
| `traceparent`                    | W3C trace context, links your agent's spans to the experiment run. Only sent when tracing is enabled.                                               |
| `baggage`                        | OpenTelemetry baggage carrying the same keys as `arize_metadata` (except `requested_by`), unprefixed: `space_id=...,project_name=...,trace_id=...`. |

## A minimal Python agent endpoint

Here's a complete FastAPI example that accepts the Arize request, runs your agent code, and returns a response:

<CodeGroup>
  ```python FastAPI theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  import os
  from fastapi import FastAPI, HTTPException, Request
  from pydantic import BaseModel, ConfigDict

  app = FastAPI()

  class InvokeRequest(BaseModel):
      model_config = ConfigDict(extra="allow")

      goal: str
      config: dict = {}
      arize_metadata: dict | None = None

  @app.post("/invoke")
  async def invoke(req: InvokeRequest, request: Request):
      goal = req.goal
      config = req.config
      md = req.arize_metadata or {}

      # Run your agent here.
      result = await run_my_agent(goal=goal, **config)

      return {
          "final_response": result.text,
          "tool_calls": result.tool_calls,
          "trace_id": result.trace_id,
      }
  ```

  ```typescript Express theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  import express from "express";

  const app = express();
  app.use(express.json());

  app.post("/invoke", async (req, res) => {
    const { goal, config = {}, arize_metadata } = req.body;
    if (!goal) {
      return res.status(400).json({ error: "missing goal" });
    }

    const result = await runMyAgent({ goal, ...config });

    res.json({
      final_response: result.text,
      tool_calls: result.toolCalls,
      trace_id: result.traceId,
    });
  });

  app.listen(8000);
  ```
</CodeGroup>

<Callout type="info">
  Pydantic v2 ignores unknown extra fields by default — `arize_metadata` will pass cleanly even if you don't declare it. The example declares it explicitly so you can read from it later for tracing. If your model uses `extra="forbid"`, you must declare `arize_metadata` or every request will fail validation with a 422.
</Callout>

## Authentication

We recommend one of:

* **Bearer token** — `Authorization: Bearer <your-key>`. Simple, works with any HTTP client.
* **API key header** — a custom header like `X-API-Key: <your-key>`.
* **Custom headers** — multiple headers if your endpoint requires them.

Set these in the agent configuration's **Headers** section; Arize stores them encrypted and replays them on every request.

For internal-only endpoints (not exposed to the public internet), remote agent experiments are not currently supported for cloud-hosted Arize. Self-hosted deployments can use VPC peering.

## Register the agent in Arize

<Frame>
  <video
    src="https://storage.googleapis.com/arize-assets/doc-images/agent%20experiments/create-remote-agents.mp4"
    alt="Registering a remote agent in Arize from the Remote Agents page"
    width="100%"
    height="100%"
    style={{
  display: 'block',
  objectFit: 'fill',
  backgroundColor: 'transparent',
}}
    controls
    autoPlay
    muted
    loop
  />
</Frame>

<Steps>
  <Step title="Open Remote Agents from the left nav">
    In the left navigation, click **More > Remote Agents**, then **New Remote Agent** in the top right.
  </Step>

  <Step title="Name and describe">
    Give the agent a name (e.g. `customer-support-v2`) and a one-line description of what it does. This is what teammates will see in the agent picker.
  </Step>

  <Step title="Set endpoint URL">
    Paste the full URL, e.g. `https://my-agent.example.com/invoke`. Arize will not append paths — use the exact URL.
  </Step>

  <Step title="Add auth headers">
    Click **Add Header**, set `Authorization` (or your custom header), and paste the value. Add as many as you need.
  </Step>

  <Step title="Define input schema">
    The **Input Schema** is a JSON Schema that describes the top-level request body Arize sends. It must have `"type": "object"` and must not declare `arize_metadata` (Arize injects that key itself). This is what unlocks per-experiment config validation.

    A minimal schema for the FastAPI example above:

    ```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    {
      "type": "object",
      "properties": {
        "goal": {
          "type": "string",
          "description": "What the agent should do"
        },
        "config": {
          "type": "object",
          "properties": {
            "model": { "type": "string" },
            "max_turns": { "type": "integer" }
          },
          "additionalProperties": false
        }
      },
      "required": ["goal"],
      "additionalProperties": false
    }
    ```
  </Step>

  <Step title="(Optional) Add request presets">
    A **preset** is a named config payload your team can pick from when running an experiment, instead of writing JSON by hand. Each preset takes a name, an optional description, and a config payload.

    A preset is a *partial* request body: it can omit fields (including required ones like `goal`, which comes from the dataset), but every field it does include is validated against the input schema above. That means the config nests exactly as the schema does. For example:

    * `Production baseline` → `{ "config": { "model": "claude-sonnet-4-5", "max_turns": 12 } }`
    * `Opus comparison` → `{ "config": { "model": "claude-opus-4-7", "max_turns": 15 } }`
    * `Cost optimized` → `{ "config": { "model": "claude-haiku-4-5", "max_turns": 8 } }`

    Presets are what make agent experiments demo-able to PMs and non-engineers.
  </Step>

  <Step title="(Optional) Set runtime settings">
    * **Rate limit (requests/minute)** — caps how fast Arize calls your endpoint across an experiment. Leave blank for the system default. Use this if your agent's downstream API has a rate limit. Arize also backs off automatically when your endpoint returns `429`.
    * **Request timeout (seconds)** — per-request timeout. Default is 120s, maximum is 300s. Raise it for agents with long loops.

    These live on the agent configuration and apply to every experiment run against it.
  </Step>

  <Step title="(Optional) Include requester email">
    Turn on **Include requester email in agent requests** to add `arize_metadata.requested_by` (the email of the user who launched the experiment) to every request. Useful for per-user auditing or attribution inside your agent. Off by default.
  </Step>

  <Step title="Save">
    Click **Create Agent**. The agent now appears in the **Run in Agent Playground** picker on every dataset.
  </Step>
</Steps>

## Hydrating the body from dataset columns

When you run an experiment, your request body template uses `{{dataset.column_name}}` placeholders that get replaced with values from each dataset row:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "goal": "{{dataset.input}}",
  "config": { "model": "claude-sonnet-4-6" }
}
```

If your dataset has a column called `input`, `{{dataset.input}}` is replaced with that row's value. If the column is named differently — e.g. `question`, `user_prompt` — use `{{dataset.question}}` instead. Placeholder names must match column names exactly.

## Common issues

<AccordionGroup>
  <Accordion title="422 Unprocessable Entity from your endpoint">
    Almost always a body shape mismatch. Confirm your endpoint reads `goal` (or whatever you named it) at the **top level** of the body, not under an `input` key — Arize does not wrap the templated body. The full shape is `{ ...your templated fields, "arize_metadata": {...} }`.

    If your request model rejects unknown fields (e.g. Pydantic `extra="forbid"`), declare `arize_metadata` so it doesn't fail validation.
  </Accordion>

  <Accordion title="A literal placeholder arrives in your agent">
    The dataset column name doesn't match the placeholder. Check the column header in your dataset — if it's `prompt`, use `{{dataset.prompt}}`. Use `{{dataset.column_name}}`, where `column_name` matches the dataset column exactly.
  </Accordion>

  <Accordion title="Timeouts on long-running agents">
    The default request timeout is 120 seconds. For agents that legitimately take longer, raise **Request timeout** in the agent configuration, up to a maximum of 300 seconds. The call is synchronous: Arize records whatever your endpoint returns within the timeout as the run output. An async or callback mode for agents that need longer than 300 seconds per row is not yet supported.
  </Accordion>

  <Accordion title="Rate limits">
    Arize runs dataset rows in parallel. If your agent's downstream API has a rate limit, set **Rate limit (requests/minute)** in the agent configuration. Arize also adapts automatically when your endpoint returns `429`.
  </Accordion>
</AccordionGroup>

## Next: connect tracing

Your endpoint accepts requests and returns responses — but for the full picture (every LLM call, tool invocation, latency, token use) to land in Arize alongside the experiment, set up tracing next.

<Card title="Setting up tracing for agent experiments" href="/docs/ax/improve/agent-tracing-context">
  How `traceparent` propagation links your agent's spans to the experiment run, including dynamic per-request space routing.
</Card>
