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

# Run Execution

Use this API to start an asynchronous agent execution. The engine creates an execution document in Firestore and dispatches a Cloud Task for processing. The response returns immediately with the `executionId`. Poll [Get Execution](/docs/api-reference/rest-apis/v2/agents/execution/get) to track progress and fetch findings.

The `apiKey` is injected from request headers. `organizationId` and `documentId` are required and identify the document the execution runs against: the document must already exist, and findings are persisted to it as comment annotations. Cross-page execution is controlled via the `crossPageExecute` boolean; there is no separate endpoint.

The schema uses `.passthrough()` so any additional fields are forwarded.

# Endpoint

`POST https://api.velt.dev/v2/agents/execution/run`

# Headers

<ParamField header="x-velt-api-key" type="string" required>
  Your API key.
</ParamField>

<ParamField header="x-velt-auth-token" type="string" required>
  Your [Auth Token](/docs/security/auth-tokens).
</ParamField>

# Body

#### Params

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="agentId" type="string" required>
      Min 1 char. Agent ID to execute.
    </ParamField>

    <ParamField body="url" type="string" required>
      Valid URL. The seed URL to process.
    </ParamField>

    <ParamField body="crossPageExecute" type="boolean">
      When true, the engine crawls the seed URL and processes up to `maxUrlsToProcess` pages. Default: `false`.
    </ParamField>

    <ParamField body="maxUrlsToProcess" type="number">
      Max URLs to process when `crossPageExecute: true`. Positive integer. Default: 50.
    </ParamField>

    <ParamField body="deviceType" type="string">
      Device mode to emulate for this execution: `"mobile"` or `"desktop"`. Default: `"desktop"`. Drives the Puppeteer viewport/user-agent used by all context-gathering strategies and pin resolution, and sets `pageInfo.deviceInfo.deviceType` on every annotation the execution creates.
    </ParamField>

    <ParamField body="organizationId" type="string" required>
      Organization ID. Identifies the organization the execution runs against; findings are persisted to it as comment annotations.
    </ParamField>

    <ParamField body="documentId" type="string" required>
      Document ID. The document must already exist (unknown documents return `NOT_FOUND`); findings are persisted to it as comment annotations.
    </ParamField>

    <ParamField body="annotationVisibility" type="string">
      Sets who can see the comment annotations this execution creates: `"public"` or `"private"`. Default: `"private"`.

      With `"private"`, each annotation is created with visibility `{ type: "organizationPrivate", organizationId }`. Only members and admins of the execution's organization see the finding pins.

      With `"public"`, each annotation is created with visibility `{ type: "public" }`. Anyone with access to the document sees the finding pins, including users outside the organization.

      These two are the only accepted values; request validation rejects any other string. Omitting the field resolves to `"private"`. The execution never overrides an annotation that already carries its own `visibility`.
    </ParamField>

    <ParamField body="trigger" type="string">
      `"standalone"` (default) or `"workflow"`. Use `"workflow"` when running an agent as part of a Review Workflow Builder workflow node.
    </ParamField>

    <ParamField body="workflowExecutionId" type="string">
      Parent workflow execution ID. Pair with `trigger: "workflow"`.
    </ParamField>

    <ParamField body="ranBy" type="object">
      User who triggered the execution.

      | Field    | Type   | Required       | Description                  |
      | -------- | ------ | -------------- | ---------------------------- |
      | `userId` | string | yes (if ranBy) | User ID                      |
      | `name`   | string | no             | Display name. Default: `""`. |
      | `email`  | string | no             | Email. Default: `""`.        |
    </ParamField>

    <ParamField body="userContext" type="object">
      Runtime values for the agent's `userContextFields`. Keys must match the field IDs declared in the agent's `input.userContextFields`.
    </ParamField>

    <ParamField body="aiConfig" type="object">
      Per-execution LLM override. Omit it to run on the provider and model the platform resolves for the agent.

      | Field           | Type   | Required | Description                                                                                                                                      |
      | --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
      | `provider`      | string | no       | `"gemini"`, `"claude"`, or `"openai"`. Any other value is rejected.                                                                              |
      | `model`         | string | no       | Pins one model for this run. Must be on the allowlist (see below). Pair it with `provider`.                                                      |
      | `defaultModels` | object | no       | Per-provider model policy, `{ "<provider>": "<model>" }`. Each key must be a valid provider and each value must be on that provider's allowlist. |
      | `maxToolTurns`  | number | no       | Tool-loop turn budget for the `mcp-tools` execution strategy. Integer `1..16`.                                                                   |

      <Note>
        **`model` and `defaultModels` solve different problems.** `model` pins a single model for this run, which is only correct when you know the agent's provider. `defaultModels` is the per-provider form: send it when you reuse one `aiConfig` across many runs whose agents sit on different providers, so each run picks up the right model for whichever provider its agent resolves to. Sending both is allowed.
      </Note>

      <Note>
        **Allowed models.** A request cannot point an execution at an arbitrary model string. `model` is validated against the allowlist for the supplied `provider`, or against the union of every provider's allowlist when `provider` is omitted. The allowlist is deliberately narrow, currently one model per provider:

        | Provider | Allowed model      |
        | -------- | ------------------ |
        | `gemini` | `gemini-3.6-flash` |
        | `claude` | `claude-sonnet-5`  |
        | `openai` | `gpt-5.6-luna`     |

        Sending `model` without `provider` only passes a weaker check. The model is re-checked at execution time against the provider the agent actually resolves to, and if it does not match, the override is dropped and the run falls back to that provider's default model. The request still returns `200` and nothing in the response tells you the pin was ignored. Send `provider` alongside `model`, or use `defaultModels`, to guarantee the pin.
      </Note>

      Validation rules specific to this object:

      * Unlike the rest of the request body, `aiConfig` rejects unknown keys. A typo such as `modell` returns `INVALID_ARGUMENT` instead of being silently ignored.
      * At least one of `provider`, `model`, `defaultModels`, or `maxToolTurns` must be present. An empty `aiConfig: {}` is rejected.
      * `defaultModels` cannot be an empty object.
    </ParamField>
  </Expandable>
</ParamField>

## **Example Requests**

#### 1. Single page execution

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com/pricing",
    "organizationId": "org_001",
    "documentId": "doc_001",
    "deviceType": "desktop",
    "ranBy": {
      "userId": "user_123",
      "name": "Jane Doe",
      "email": "jane@example.com"
    }
  }
}
```

#### 2. Cross-page execution with user context (mobile)

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com",
    "crossPageExecute": true,
    "maxUrlsToProcess": 25,
    "deviceType": "mobile",
    "organizationId": "org_001",
    "documentId": "doc_001",
    "trigger": "standalone",
    "userContext": {
      "brand_color": "#1A73E8",
      "brand_font": "Inter",
      "check_images": true
    },
    "ranBy": {
      "userId": "user_123",
      "name": "Jane Doe",
      "email": "jane@example.com"
    }
  }
}
```

#### 3. Workflow-triggered execution

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com",
    "trigger": "workflow",
    "workflowExecutionId": "wf_exec_789",
    "organizationId": "org_001",
    "documentId": "doc_001",
    "ranBy": { "userId": "user_123" }
  }
}
```

#### 4. Execution with public annotations

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com/pricing",
    "organizationId": "org_001",
    "documentId": "doc_001",
    "annotationVisibility": "public",
    "ranBy": { "userId": "user_123" }
  }
}
```

#### 5. Pinning a provider and model for one run

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com/pricing",
    "organizationId": "org_001",
    "documentId": "doc_001",
    "aiConfig": {
      "provider": "claude",
      "model": "claude-sonnet-5"
    }
  }
}
```

#### 6. Per-provider model policy across mixed agents

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com",
    "organizationId": "org_001",
    "documentId": "doc_001",
    "aiConfig": {
      "defaultModels": {
        "gemini": "gemini-3.6-flash",
        "claude": "claude-sonnet-5"
      },
      "maxToolTurns": 12
    }
  }
}
```

#### 7. Minimal request

```JSON theme={null}
{
  "data": {
    "agentId": "abc123def456",
    "url": "https://example.com",
    "organizationId": "org_001",
    "documentId": "doc_001"
  }
}
```

# Response

#### Success Response

```JSON theme={null}
{
  "result": {
    "status": "success",
    "message": "Agent execution created successfully",
    "data": {
      "executionId": "exec_1711900000000_abc123def456"
    }
  }
}
```

| Field              | Type   | Description                                                                                             |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------- |
| `data.executionId` | string | Unique execution ID. Use to poll via [Get Execution](/docs/api-reference/rest-apis/v2/agents/execution/get). |

#### Failure Response

```JSON theme={null}
{
  "error": {
    "message": "ERROR_MESSAGE",
    "status": "INVALID_ARGUMENT"
  }
}
```

**Errors:**

* `INVALID_ARGUMENT`: invalid URL; missing `agentId`, `organizationId`, or `documentId`; or an invalid `trigger`, `deviceType`, or `annotationVisibility`.
* `INVALID_ARGUMENT`: an `aiConfig` that is empty, carries an unknown key, names a provider outside `gemini` / `claude` / `openai`, names a model outside the allowlist, supplies an empty `defaultModels`, or sets `maxToolTurns` outside `1..16`.
* `NOT_FOUND`: store database not found, or the target document does not exist.
* `ALREADY_EXISTS`: an execution is already running for this agent and document combination. The error message includes the running execution's ID.
* `RESOURCE_EXHAUSTED`: the workspace's AI credits are exhausted.

<ResponseExample>
  ```js theme={null}
  {
    "result": {
      "status": "success",
      "message": "Agent execution created successfully",
      "data": {
        "executionId": "exec_1711900000000_abc123def456"
      }
    }
  }
  ```
</ResponseExample>
