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

# Runs and jobs

> Measurement lifecycle: start, poll, cancel, and read run detail.

A measurement run executes a set of prompts against multiple AI answer engines and search engines, collecting brand mentions, citations, and sentiment data.

## Endpoints

| Method | Path                                              | Scope        | Description               |
| ------ | ------------------------------------------------- | ------------ | ------------------------- |
| GET    | `/api/v1/workspaces/{workspace}/runs`             | `runs:read`  | List workspace runs       |
| POST   | `/api/v1/workspaces/{workspace}/runs`             | `runs:write` | Start a run               |
| GET    | `/api/v1/workspaces/{workspace}/runs/{id}`        | `runs:read`  | Poll a run job            |
| DELETE | `/api/v1/workspaces/{workspace}/runs/{id}`        | `runs:write` | Cancel a run              |
| GET    | `/api/v1/workspaces/{workspace}/runs/{id}/detail` | `runs:read`  | Get persisted run results |

## Presets

| Preset     | Active prompt cap      | Use case                 |
| ---------- | ---------------------- | ------------------------ |
| `smoke`    | 2                      | Quick connectivity check |
| `lite`     | 50                     | Fast partial refresh     |
| `standard` | 150 (lower per-engine) | Full measurement         |
| `exa`      | 150                    | All prompts against Exa  |

## Start a run

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs \
  -H "Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"preset": "standard"}'
```

### Request body

<ParamField name="preset" type="string" required>
  One of `smoke`, `lite`, `standard`, `exa`.
</ParamField>

<ParamField name="idempotencyKey" type="string" optional>
  Makes the start safe for retries.
</ParamField>

Response (202):

```json theme={null}
{
  "id": "run_abc123",
  "brand": "{workspace}",
  "kind": "measurement",
  "pid": 12345,
  "logPath": "/tmp/geo/run_{workspace}_20260810_100000.log",
  "startedAt": "2026-08-10T10:00:00.000Z",
  "finishedAt": null,
  "status": "running",
  "progress": {
    "completedCalls": 0,
    "totalCalls": 150,
    "percent": 0,
    "source": "calls"
  },
  "lastLines": []
}
```

### Idempotency

Pass an `idempotencyKey` in the body to make the start safe for network retries:

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs \
  -H "Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"preset": "standard", "idempotencyKey": "my-unique-key-1"}'
```

## Poll a run

```bash theme={null}
curl -H "Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
     https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs/{id}
```

The `status` field cycles through `running`, `succeeded`, `failed`, `cancelled`. The `progress` object shows `completedCalls` / `totalCalls` and a `percent` (0–100).

Terminal response:

```json theme={null}
{
  "id": "run_abc123",
  "brand": "{workspace}",
  "kind": "measurement",
  "status": "succeeded",
  "progress": {
    "completedCalls": 150,
    "totalCalls": 150,
    "percent": 100,
    "source": "calls"
  },
  "startedAt": "2026-08-10T10:00:00.000Z",
  "finishedAt": "2026-08-10T10:15:00.000Z",
  "pid": 12345,
  "logPath": "/tmp/geo/run_{workspace}_20260810_100000.log",
  "lastLines": ["2026-08-10T10:15:00 Measurement complete"]
}
```

## Cancel a run

```bash theme={null}
curl -X DELETE https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs/{id} \
  -H "Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Returns the final `JobStatus` with `status: "cancelled"`.

## Run detail

```bash theme={null}
curl -H "Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
     https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs/{id}/detail
```

Returns persisted results grouped by engine, with brand mentions, citations, sentiment data, and visible prompt results for each engine.

## Concurrency

One workspace can have at most one running measurement, one running prompt-regeneration job, and one running page-evaluation job concurrently.

| Conflict                    | HTTP | Code                        | Body                                                                               |
| --------------------------- | ---- | --------------------------- | ---------------------------------------------------------------------------------- |
| Measurement already running | 409  | `job_already_running`       | `{"error":{"code":"job_already_running","message":"...","requestId":"..."}}`       |
| Pipeline worker cap reached | 429  | `pipeline_capacity_reached` | `{"error":{"code":"pipeline_capacity_reached","message":"...","requestId":"..."}}` |

`pipeline_capacity_reached` comes from the `GEO_MAX_PIPELINE_JOBS` worker cap (an integer between 1 and 32). Retry after a backoff when the cluster has capacity.
