# Run Droid with the Execute API

Start a headless Droid turn with one API call, on a fresh computer from an Environment, an existing computer, or an existing session.

The Droid Execute API runs a Droid turn with a single request. You send a prompt and a target, and Factory finds or provisions the computer, starts or continues a session, and sends your prompt. You don't need to create a computer or session first.

Use it to trigger Droid from CI pipelines, internal tools, webhooks, or scripts. The [Execute a Droid turn](/api-reference/droid#post-api-v0-droid-execute) reference lists every request field, response field, and error status. This page explains how to use it.

## Authentication

Every request needs a Factory API key in the `Authorization: Bearer` header. Generate one in the <a href="https://app.factory.ai/settings/api-keys">Factory API keys settings</a> and export it as `FACTORY_API_KEY`.

[Service account](/api-reference/service-accounts) API keys work too. The session, and any computer created for it, is owned by the service account.

## Execute a Droid turn

```text
POST https://api.factory.ai/api/v0/droid/execute
```

The request returns `202 Accepted` once the turn has started. Droid keeps working in the background, so [poll the session](#poll-for-results) to follow progress and read the results.

The response includes `sessionId`, `computerId`, and `messageId`, the ID of the user message created from your `prompt`.

`model` and `reasoningEffort` apply only when the request starts a new session. Sending either one with a `session` target returns `400`.

### Targets

`target` is an object with a `kind` field and exactly one matching ID field.

| `kind`        | ID field        | Behavior                                                                                                                    |
| :------------ | :-------------- | :-------------------------------------------------------------------------------------------------------------------------- |
| `environment` | `environmentId` | Provisions a fresh computer from the Environment's active build, then starts a new session on it.                           |
| `computer`    | `computerId`    | Starts a new session on an existing computer you own.                                                                       |
| `session`     | `sessionId`     | Sends the prompt as a follow-up message in an existing session you own. The session keeps its computer, model, and history. |

<Note>
  An `environment` target waits for the new computer to finish provisioning before it returns, so expect that request to take longer than the other targets. The computer is not listed in your Droid Computers and is cleaned up after it goes idle. To keep working on it, send follow-ups with a `session` target and the returned `sessionId`.
</Note>

Look up IDs with the rest of the API: [`GET /api/v0/environments`](/api-reference/environments#get-api-v0-environments) lists Environments, and [`GET /api/v0/computers`](/api-reference/computers#get-api-v0-computers) lists computers.

### Examples

Start a session on a fresh computer from an Environment:

```bash
curl -X POST "https://api.factory.ai/api/v0/droid/execute" \
  -H "Authorization: Bearer $FACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Fix the failing unit tests in packages/api and open a pull request.",
    "target": { "kind": "environment", "environmentId": "<environment-id>" },
    "model": "claude-opus-5-5",
    "reasoningEffort": "medium"
  }'
```

Start a session on a computer you already have:

```bash
curl -X POST "https://api.factory.ai/api/v0/droid/execute" \
  -H "Authorization: Bearer $FACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Summarize the open TODOs in this repository.",
    "target": { "kind": "computer", "computerId": "<computer-id>" }
  }'
```

Send a follow-up to an existing session:

```bash
curl -X POST "https://api.factory.ai/api/v0/droid/execute" \
  -H "Authorization: Bearer $FACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Also add a regression test for the bug you fixed.",
    "target": { "kind": "session", "sessionId": "<session-id>" }
  }'
```

## Poll for results

The execute call only starts the turn. Use the [Droid Sessions API](/api-reference/sessions) with the returned IDs to follow it:

<Steps>
  <Step title="Poll the session status">
    Call [`GET /api/v0/sessions/{sessionId}?computerId={computerId}`](/api-reference/sessions#get-api-v0-sessions-sessionid). The `status` field is `pending` or `running` while Droid works and `idle` when it stops.
  </Step>
  <Step title="Read the transcript">
    Call [`GET /api/v0/sessions/{sessionId}/messages?computerId={computerId}`](/api-reference/sessions#get-api-v0-sessions-sessionid-messages) to read the messages.
  </Step>
</Steps>

Right after the call, the session can still report `idle` from the previous turn, especially when you send a follow-up to an existing session. Treat the turn as complete only when the session is `idle` **and** the transcript has an assistant message after the user message whose `id` is the returned `messageId`.

A newly created session can briefly return `404` before it is visible. Retry until it appears.

```javascript title="execute-and-poll.mjs"
const headers = {
  Authorization: `Bearer ${process.env.FACTORY_API_KEY}`,
  "Content-Type": "application/json",
};
const base = "https://api.factory.ai/api/v0";

const started = await fetch(`${base}/droid/execute`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    prompt: "Upgrade lodash to the latest version and run the tests.",
    target: { kind: "environment", environmentId: "<environment-id>" },
  }),
});
if (started.status !== 202) throw new Error(await started.text());
const { sessionId, computerId, messageId } = await started.json();

const query = `computerId=${encodeURIComponent(computerId)}`;
let replies = [];
for (;;) {
  await new Promise((resolve) => setTimeout(resolve, 5000));
  const session = await fetch(`${base}/sessions/${sessionId}?${query}`, {
    headers,
  });
  if (!session.ok) continue;
  const { status } = await session.json();
  if (status !== "idle") continue;

  const { messages } = await fetch(
    `${base}/sessions/${sessionId}/messages?${query}&limit=100`,
    { headers },
  ).then((response) => response.json());
  const promptIndex = messages.findIndex((message) => message.id === messageId);
  replies =
    promptIndex === -1
      ? []
      : messages
          .slice(promptIndex + 1)
          .filter((message) => message.role === "assistant");
  if (replies.length > 0) break;
}
console.log(replies);
```

<RelatedLinks>
  <RelatedLink href="/api-reference/droid" title="Droid Execute API">
    Full request, response, and error schemas for the execute call.
  </RelatedLink>
  <RelatedLink href="/api-reference/sessions" title="Droid Sessions API">
    Poll status, read messages, and send follow-ups.
  </RelatedLink>
  <RelatedLink href="/environments/overview" title="Environments">
    Prepare the baseline that environment targets launch from.
  </RelatedLink>
  <RelatedLink href="/droid-exec/overview" title="Droid Exec (Headless)">
    Run headless Droid from the CLI instead of the API.
  </RelatedLink>
</RelatedLinks>
