> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.athenaintel.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.athenaintel.com/_mcp/server.

# Execute a Batch of Agent Operating Procedures

POST https://api.athenaintel.com/api/v0/aop/execute-batch
Content-Type: application/json

Start many Agent Operating Procedure (AOP) runs under one batch handle. Each run is queued exactly like `POST /aop/execute-async`; the response returns a `batch_id` so the caller polls `GET /aop/batches/{batch_id}` once per batch instead of once per thread. Pass the `batch_id` back to append more runs to the same batch. Runs are launched independently: a run that fails to launch is reported with an error and does not stop the others. Runs are idempotent within a batch: a run whose `idempotency_key` (or, when omitted, `client_ref`) was already launched into the same batch with the same parameters is not started again; its original outcome is replayed with `deduplicated: true`.

Reference: https://docs.athenaintel.com/api-reference/aop/execute-batch

## Authentication

- `X-API-KEY` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects an AOPBatchExecuteRequestIn.

- `runs` (list of AOPBatchRunIn, required) — Runs to launch (1-100 per request). Launch more into the same batch by repeating the call with the returned `batch_id`.
- `asset_id` (string, optional, nullable) — Default AOP asset ID for runs that omit their own `asset_id`
- `batch_id` (string, optional, nullable) — Existing batch to append these runs to (returned by a previous execute-batch call). Omit to start a new batch.
- `dry_run` (boolean, optional, default: false) — Execute every run in dry-run mode: side-effectful tool calls are validated and captured instead of executed.
- `run_budget` (RunBudget, optional, nullable) — Per-run spend cap (max_model_calls / max_cost_usd) applied to every run in the request.

## Response

### 200

Batch accepted; see per-run outcomes

- `failed` (integer, required) — Runs in this request that could not be launched
- `launched` (integer, required) — Runs accepted and queued in this request
- `runs` (list of AOPBatchRunLaunchOut, required) — Per-run launch outcome, in request order
- `batch_id` (string, optional, nullable) — Batch handle. Poll `GET /aop/batches/{batch_id}` for the whole batch instead of every thread, and pass it back to `POST /aop/execute-batch` to append more runs. Null when a new batch launched no runs: there is nothing to poll or append to, so no handle is issued.
- `deduplicated` (integer, optional, default: 0) — Runs in this request that replayed an earlier launch (same `idempotency_key` / `client_ref` in this batch) instead of starting

## Errors

### 400 Bad Request Error

Invalid request, or the batch would exceed its run limit

- `map from string to any`

### 401 Unauthorized Error

Unauthorized

- `message` (string, required)

### 404 Not Found Error

The batch id is not one of the caller's batches

- `message` (string, required)

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

### 500 Internal Server Error

Internal server error during launch

- `map from string to any`

## Types

### AOPBatchRunIn

One AOP run inside an execute-batch request.

- `asset_id` (string, optional, nullable) — ID of the AOP asset to execute for this run. Defaults to the batch-level `asset_id` when omitted.
- `client_ref` (string, optional, nullable) — Caller-supplied correlation key (e.g. a row id) echoed back on the run in the batch status response. Unique within a batch: a later run with the same `client_ref` in the same batch is not launched again; its outcome is replayed with `deduplicated: true`.
- `idempotency_key` (string, optional, nullable) — Optional per-run idempotency key, scoped to your account and the batch. Repeating a run with the same key (and same parameters) replays the original launch outcome instead of starting a duplicate. Defaults to `client_ref` when omitted.
- `user_inputs` (map from string to string, optional, nullable) — Optional user inputs to append to the AOP's prompt as key-value pairs

### RunBudget

The most one execution may spend before the runtime stops it. Both limits are optional individually; at least one must be set. A limit that is reached ends the run: the model call that would exceed ``max_model_calls`` is never made, and neither is the first call after accumulated provider cost reaches ``max_cost_usd`` — or the call projected to cross it (a cost cap can be overshot by at most one call's variance, since the provider bills after the fact).

- `max_cost_usd` (double, optional, nullable) — Maximum provider cost in USD this execution may accumulate, at Athena's in-house model pricing. The run stops before the model call that has reached, or is projected to cross, this amount; a single call can overshoot it by at most that call's cost.
- `max_model_calls` (integer, optional, nullable) — Maximum number of model calls (top-level and sub-agent) this execution may make. The run stops before the call that would exceed it.

### AOPBatchRunLaunchOut

Outcome of launching one run of an execute-batch request.

- `index` (integer, required) — Position of the run in the request's `runs` list
- `aop_asset_id` (string, optional, nullable) — ID of the AOP asset the run executes
- `client_ref` (string, optional, nullable) — Caller-supplied correlation key from the request
- `deduplicated` (boolean, optional, default: false) — True when this run replays an earlier launch with the same `idempotency_key` / `client_ref` in this batch; no new run was started.
- `error` (string, optional, nullable) — Why the run could not be launched; null on success
- `thread_id` (string, optional, nullable) — Thread ID of the launched run; null when the launch failed

### ValidationError

- `loc` (list of ValidationErrorLocItems, required)
- `msg` (string, required)
- `type` (string, required)
- `ctx` (ValidationErrorCtx, optional)
- `input` (any, optional)

### ValidationErrorLocItems

- string
- integer

### ValidationErrorCtx

## Examples

**Request**

```json
{
  "runs": [
    {
      "client_ref": "row-1",
      "user_inputs": {
        "company": "Acme Corp"
      }
    },
    {
      "client_ref": "row-2",
      "user_inputs": {
        "company": "Globex"
      }
    }
  ],
  "asset_id": "asset_9249292-d118-42d3-95b4-00eccfe0754f",
  "run_budget": {
    "max_cost_usd": 30,
    "max_model_calls": 80
  }
}
```

**Response**

```json
{
  "failed": 0,
  "launched": 2,
  "runs": [
    {
      "index": 0,
      "aop_asset_id": "asset_9249292-d118-42d3-95b4-00eccfe0754f",
      "client_ref": "row-1",
      "thread_id": "thread_12345-6789-abcd-efgh"
    },
    {
      "index": 1,
      "aop_asset_id": "asset_9249292-d118-42d3-95b4-00eccfe0754f",
      "client_ref": "row-2",
      "thread_id": "thread_22345-6789-abcd-efgh"
    }
  ],
  "batch_id": "batch_1f3c1b9e-7f7d-4c2c-9d5e-2b1f0a9c8d7e"
}
```

**SDK Code**

```python
import requests

url = "https://api.athenaintel.com/api/v0/aop/execute-batch"

payload = {
    "runs": [
        {
            "client_ref": "row-1",
            "user_inputs": { "company": "Acme Corp" }
        },
        {
            "client_ref": "row-2",
            "user_inputs": { "company": "Globex" }
        }
    ],
    "asset_id": "asset_9249292-d118-42d3-95b4-00eccfe0754f",
    "run_budget": {
        "max_cost_usd": 30,
        "max_model_calls": 80
    }
}
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.athenaintel.com/api/v0/aop/execute-batch';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"runs":[{"client_ref":"row-1","user_inputs":{"company":"Acme Corp"}},{"client_ref":"row-2","user_inputs":{"company":"Globex"}}],"asset_id":"asset_9249292-d118-42d3-95b4-00eccfe0754f","run_budget":{"max_cost_usd":30,"max_model_calls":80}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.athenaintel.com/api/v0/aop/execute-batch"

	payload := strings.NewReader("{\n  \"runs\": [\n    {\n      \"client_ref\": \"row-1\",\n      \"user_inputs\": {\n        \"company\": \"Acme Corp\"\n      }\n    },\n    {\n      \"client_ref\": \"row-2\",\n      \"user_inputs\": {\n        \"company\": \"Globex\"\n      }\n    }\n  ],\n  \"asset_id\": \"asset_9249292-d118-42d3-95b4-00eccfe0754f\",\n  \"run_budget\": {\n    \"max_cost_usd\": 30,\n    \"max_model_calls\": 80\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.athenaintel.com/api/v0/aop/execute-batch")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"runs\": [\n    {\n      \"client_ref\": \"row-1\",\n      \"user_inputs\": {\n        \"company\": \"Acme Corp\"\n      }\n    },\n    {\n      \"client_ref\": \"row-2\",\n      \"user_inputs\": {\n        \"company\": \"Globex\"\n      }\n    }\n  ],\n  \"asset_id\": \"asset_9249292-d118-42d3-95b4-00eccfe0754f\",\n  \"run_budget\": {\n    \"max_cost_usd\": 30,\n    \"max_model_calls\": 80\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.athenaintel.com/api/v0/aop/execute-batch")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"runs\": [\n    {\n      \"client_ref\": \"row-1\",\n      \"user_inputs\": {\n        \"company\": \"Acme Corp\"\n      }\n    },\n    {\n      \"client_ref\": \"row-2\",\n      \"user_inputs\": {\n        \"company\": \"Globex\"\n      }\n    }\n  ],\n  \"asset_id\": \"asset_9249292-d118-42d3-95b4-00eccfe0754f\",\n  \"run_budget\": {\n    \"max_cost_usd\": 30,\n    \"max_model_calls\": 80\n  }\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.athenaintel.com/api/v0/aop/execute-batch', [
  'body' => '{
  "runs": [
    {
      "client_ref": "row-1",
      "user_inputs": {
        "company": "Acme Corp"
      }
    },
    {
      "client_ref": "row-2",
      "user_inputs": {
        "company": "Globex"
      }
    }
  ],
  "asset_id": "asset_9249292-d118-42d3-95b4-00eccfe0754f",
  "run_budget": {
    "max_cost_usd": 30,
    "max_model_calls": 80
  }
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.athenaintel.com/api/v0/aop/execute-batch");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"runs\": [\n    {\n      \"client_ref\": \"row-1\",\n      \"user_inputs\": {\n        \"company\": \"Acme Corp\"\n      }\n    },\n    {\n      \"client_ref\": \"row-2\",\n      \"user_inputs\": {\n        \"company\": \"Globex\"\n      }\n    }\n  ],\n  \"asset_id\": \"asset_9249292-d118-42d3-95b4-00eccfe0754f\",\n  \"run_budget\": {\n    \"max_cost_usd\": 30,\n    \"max_model_calls\": 80\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "runs": [
    [
      "client_ref": "row-1",
      "user_inputs": ["company": "Acme Corp"]
    ],
    [
      "client_ref": "row-2",
      "user_inputs": ["company": "Globex"]
    ]
  ],
  "asset_id": "asset_9249292-d118-42d3-95b4-00eccfe0754f",
  "run_budget": [
    "max_cost_usd": 30,
    "max_model_calls": 80
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.athenaintel.com/api/v0/aop/execute-batch")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```