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

# API Client & REST Endpoints

> SDK API client and the public runtime REST surface (execution reviews, traces, dashboards)

The Runflow runtime API is callable two ways:

1. **From SDK code** — use `createRunflowAPIClient()` (and the higher-level `Reviews` / `Memory` / `Knowledge` modules built on top of it).
2. **From any HTTP client** — talk to the runtime endpoints directly, e.g. from a service that doesn't run the SDK.

This page covers both — the SDK exports first, then the raw REST surface.

## SDK API Client

```typescript theme={null}
import { createRunflowAPIClient } from '@runflow-ai/sdk';

const api = createRunflowAPIClient(); // reads RUNFLOW_API_URL, RUNFLOW_API_KEY, RUNFLOW_AGENT_ID
```

The factory follows a config priority: explicit options → `rf.json` → env vars → defaults. The resulting client exposes typed methods for `chat`, `chatStream`, `vectorSearch`, `connector`, `memory.*`, `documents.*`, `prompts.*`, and `reviews.*` (since SDK ≥ `1.1.13`).

Higher-level modules — `Reviews`, `Memory`, `Knowledge`, `RAG`, `LLM` — wrap this client with stricter argument validation and typed errors. Prefer them in agent code; reach for the raw client only when you need something the modules don't cover.

## Authentication

All runtime endpoints accept an API key in the `x-api-key` header (or as a `Bearer` token in `Authorization`):

```http theme={null}
GET /api/v1/runtime/v1/observability/reviews
x-api-key: rf_live_...
```

Get an API key from the portal under **Settings → API Keys**. Each key is tenant-scoped; the platform derives `tenantId` from the key and applies it to every query.

The `reviewedBy` / `updatedBy` audit fields on execution reviews are auto-populated from the **API key label** — name your keys descriptively (e.g. `qa-bot`, `nightly-export`) so the audit trail is readable.

## Base URL

```
https://api.runflow.ai
```

The runtime surface lives under the `/api/v1/runtime/v1/observability/...` prefix. Paths below are shown relative to the base URL.

## Execution Reviews

CRUD + stats + training-data export for the execution-review feedback loop. The SDK's [`Reviews`](/api-reference/observability#execution-reviews) class wraps these endpoints — use it from agent code unless you need raw HTTP.

### Create a review

```http theme={null}
POST /api/v1/runtime/v1/observability/executions/:executionId/reviews
```

```json theme={null}
{
  "agentId": "agent-uuid",
  "rating": "bad",
  "comment": "Bot gave wrong business hours (≥ 10 chars required)",
  "priority": "high",
  "tags": ["hours_wrong"]
}
```

Each execution can only have **one** review. A second `POST` returns `409 Conflict`. Use the `exists` endpoint below for an idempotent check.

### Check if an execution already has a review

```http theme={null}
GET /api/v1/runtime/v1/observability/executions/:executionId/reviews/exists
```

Response: `{ "exists": true, "review": { "id", "rating", "status", "comment", "createdAt" } }`.

### List reviews

```http theme={null}
GET /api/v1/runtime/v1/observability/reviews
```

**Query params (all optional):**

| Param                 | Type     | Description                                              |
| --------------------- | -------- | -------------------------------------------------------- |
| `agentId`             | string   | Filter by agent.                                         |
| `status`              | enum     | `pending_review`, `in_progress`, `resolved`, `wont_fix`. |
| `rating`              | enum     | `good`, `bad`, `needs_improvement`.                      |
| `priority`            | enum     | `low`, `medium`, `high`, `critical`.                     |
| `dateFrom` / `dateTo` | ISO 8601 | Date range.                                              |
| `search`              | string   | Portuguese full-text search across comments.             |
| `limit`               | number   | Default 50, capped at 100.                               |
| `offset`              | number   | Pagination offset.                                       |

### Get a single review

```http theme={null}
GET /api/v1/runtime/v1/observability/reviews/:reviewId
```

### Update a review

```http theme={null}
PATCH /api/v1/runtime/v1/observability/reviews/:reviewId
```

Partial update — send only the fields you want to change. Allowed fields: `status`, `actionTaken`, `resolutionNotes`, `correctedOutput`, `comment`, `priority`, `tags`. Setting `status: "resolved"` auto-stamps `resolvedBy` and `resolvedAt`.

### Delete a review

```http theme={null}
DELETE /api/v1/runtime/v1/observability/reviews/:reviewId
```

Hard delete.

### Stats

```http theme={null}
GET /api/v1/runtime/v1/observability/reviews/stats?agentId=...
```

`agentId` is required. Response: `{ total, pending, inProgress, resolved, badReviews, needsImprovement, critical, highPriority, avgResolutionHours }`.

### Export for training

```http theme={null}
GET /api/v1/runtime/v1/observability/reviews/export/training-data?agentId=...&status=resolved
```

Returns an array of OpenAI conversational fine-tuning examples — one per matching review, with the `correctedOutput` substituted into the assistant turn when present.

```json theme={null}
{
  "training_examples": [
    {
      "messages": [
        { "role": "user", "content": "..." },
        { "role": "assistant", "content": "..." }
      ],
      "metadata": {
        "review_id": "...",
        "execution_id": "...",
        "rating": "bad",
        "was_corrected": true
      }
    }
  ],
  "total": 1,
  "format": "openai-conversational",
  "generated_at": "2026-05-20T12:00:00.000Z"
}
```

Typical filter: `{ agentId, status: 'resolved' }` to pull only human-reviewed and corrected examples.

## Dashboards & Events

The pieces of the metrics pipeline that are exposed to **API-key callers** (SDK, CLI, server-to-server):

### Ingest events

```http theme={null}
POST /api/v1/runtime/v1/observability/events
```

Used by the SDK's `track()` and CLI tools to push raw events. The events are written async (fire-and-forget) and become queryable within a few seconds.

```json theme={null}
{
  "events": [
    {
      "eventName": "alert_received",
      "properties": { "severity": "high", "company": "NW" },
      "timestamp": "2026-05-20T12:00:00.000Z"
    },
    {
      "eventName": "ticket_resolved",
      "properties": { "duration": 45, "category": "network" }
    }
  ]
}
```

`tenantId` and `agentId` are derived from the API key — you don't need to pass them.

### Recent events feed

```http theme={null}
GET /api/v1/runtime/v1/observability/events/feed
```

Most-recent-first list of events for an agent. Useful for debugging emissions, building external "live event" widgets, or sanity-checking that `track()` calls landed.

**Query params:**

| Param       | Type   | Description                                           |
| ----------- | ------ | ----------------------------------------------------- |
| `agentId`   | string | Optional. Defaults to the agent bound to the API key. |
| `eventName` | string | Optional filter.                                      |
| `limit`     | number | Default 50, capped at 100.                            |
| `offset`    | number | Pagination offset.                                    |

Response:

```json theme={null}
{
  "events": [
    {
      "id": "ev-uuid",
      "eventName": "alert_received",
      "properties": { "severity": "high", "company": "NW" },
      "threadId": "thread-uuid",
      "executionId": "exec-uuid",
      "timestamp": "2026-05-20T12:00:00.000Z"
    }
  ],
  "total": 142
}
```

### Query events (raw or grouped)

```http theme={null}
POST /api/v1/runtime/v1/observability/events/query
```

Table-style query against the event store. Two modes:

* **`mode: 'raw'`** (default) — returns individual events with the columns you ask for. Use for backfill jobs, exports, or anything that needs the underlying rows.
* **`mode: 'aggregate'`** — groups rows by a single property key and applies one or more metrics (`count`, `sum`, `avg`, `min`, `max`). Use for table-shaped reports.

The KPI-card style standalone aggregate (one number per query — sum/avg/count/rate of a metric) remains portal-only for now; this endpoint is for the table-shaped reads.

**Body:**

| Field                  | Type                                          | Description                                                                                          |
| ---------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `agentId`              | string                                        | Optional. Defaults to the API-key agent.                                                             |
| `eventName`            | string                                        | **Required**.                                                                                        |
| `mode`                 | `'raw' \| 'aggregate'`                        | Default `'raw'`.                                                                                     |
| `columns`              | `string[]`                                    | (raw mode) Property keys to project. Omit for all.                                                   |
| `groupBy`              | string                                        | (aggregate mode) Property key to group on.                                                           |
| `metrics`              | `Array<{ aggregation, propertyKey?, alias }>` | (aggregate mode) One per output column. `aggregation` is one of `count`, `sum`, `avg`, `min`, `max`. |
| `filters`              | `Array<{ propertyKey, operator, value }>`     | Operators: `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `contains`, `in`.                                  |
| `dateFrom` / `dateTo`  | ISO 8601                                      | Date range.                                                                                          |
| `sortBy` / `sortOrder` | string / `asc \| desc`                        | Result ordering.                                                                                     |
| `limit` / `offset`     | number                                        | Pagination.                                                                                          |

Response:

```json theme={null}
{
  "rows": [
    { "severity": "high", "count": 42, "total_duration": 1860 }
  ],
  "total": 1,
  "columns": [
    { "key": "severity", "type": "string" },
    { "key": "count", "type": "number" },
    { "key": "total_duration", "type": "number" }
  ]
}
```

Example — raw mode for a backfill job:

```json theme={null}
{
  "eventName": "sale",
  "mode": "raw",
  "columns": ["amount", "category", "customer_id"],
  "filters": [{ "propertyKey": "category", "operator": "eq", "value": "electronics" }],
  "dateFrom": "2026-05-01T00:00:00Z",
  "limit": 500
}
```

Example — aggregate mode for a "sales by category" table:

```json theme={null}
{
  "eventName": "sale",
  "mode": "aggregate",
  "groupBy": "category",
  "metrics": [
    { "aggregation": "count", "alias": "orders" },
    { "aggregation": "sum", "propertyKey": "amount", "alias": "revenue" }
  ],
  "dateFrom": "2026-05-01T00:00:00Z"
}
```

### List dashboard cards

```http theme={null}
GET /api/v1/runtime/observability/dashboard-cards?agentId=...
```

Returns every card configured for the agent, with its `cardType`, `config`, and `gridLayout`. Useful for read-only views of what's published, or for snapshotting a dashboard into version control.

```json theme={null}
{
  "success": true,
  "total": 5,
  "cards": [
    {
      "id": "card-uuid",
      "agentId": "agent-uuid",
      "title": "Funil de checkout",
      "cardType": "funnel",
      "config": { /* card-type-specific shape */ },
      "sortOrder": 0,
      "tabId": "tab-uuid"
    }
  ]
}
```

### Upsert a dashboard card

```http theme={null}
POST /api/v1/runtime/observability/dashboard-cards
```

Idempotent on `(agentId, eventName, cardType, aggregation, propertyKey)`. This is the endpoint `metrics.sync()` (SDK) and `rf metrics sync` (CLI) call under the hood — you can call it directly from server-to-server jobs.

```json theme={null}
{
  "agentId": "agent-uuid",
  "title": "Total alerts",
  "cardType": "number",
  "config": { "eventName": "alert_received", "aggregation": "count" },
  "tabId": "tab-uuid",
  "gridLayout": { "x": 0, "y": 0, "w": 4, "h": 3 },
  "idempotencyKey": "number:Total alerts:Overview"
}
```

`cardType` must be one of `number`, `rate`, `line`, `bar`, `pie`, `table`, `funnel`, `gauge`. The per-type `config` shape is validated server-side; see the portal Metrics tab for the canonical UI.

### List dashboard tabs

```http theme={null}
GET /api/v1/runtime/observability/dashboard-tabs?agentId=...
```

Returns the tabs configured for the agent, ordered by `sortOrder` ASC.

```json theme={null}
{
  "success": true,
  "total": 2,
  "tabs": [
    { "id": "tab-uuid", "name": "Overview", "sortOrder": 0 },
    { "id": "tab-uuid", "name": "Vendas", "sortOrder": 1 }
  ]
}
```

### Upsert a dashboard tab

```http theme={null}
POST /api/v1/runtime/observability/dashboard-tabs
```

Idempotent on `(agentId, name)`. This is the endpoint `metrics.defineTab(...) → metrics.sync()` (SDK) and `rf metrics sync` (CLI) call.

```json theme={null}
{ "agentId": "agent-uuid", "name": "Vendas", "sortOrder": 0 }
```

Response: `{ "success": true, "created": true, "id": "tab-uuid", "tab": { ... } }`. `created` is `false` on subsequent calls with the same name; if `sortOrder` is provided and differs, it gets updated.

### Aggregating event values via MCP

KPI-style aggregation (single number — `sum`, `avg`, `count`, `rate`, `distinct_count`, `group_by`) is **not exposed over REST**, but it's available to MCP-compatible clients (Claude, Claude Code, Cursor, anything connecting through the public MCP connector) via three tools:

| Tool                    | Scope      | What it does                                                                                                                                                                    |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aggregate_events`      | `mcp:read` | Single-number KPI or time/category series. Supports `dateGrouping` for time series and `group_by` for distributions.                                                            |
| `query_events`          | `mcp:read` | Table-style read — `mode='raw'` for individual rows, `mode='aggregate'` with `groupBy` + `metrics` for grouped reports.                                                         |
| `render_dashboard_card` | `mcp:read` | Pass a `cardId` and get back the current rendered value, dispatching on the card's `cardType` and `config`. Supports number / rate / line / bar / pie / gauge / table / funnel. |

These mirror the runtime endpoints `events/query` and `events/feed` (which **are** REST), but add the standalone aggregate that REST doesn't cover. See [MCP → Available MCP Tools](/core-concepts/mcp#available-mcp-tools-runflow-public-mcp) for the full catalogue.

### What's still portal-only

Over **REST**, the KPI-style standalone aggregate (e.g. "sum of `amount` where `category = electronics` for the last 7 days") and the discovery endpoints (`get_event_names`, `get_event_properties`, `property-values`) remain behind `Auth0JwtGuard`. If you need them server-to-server today:

* Use **MCP** (`aggregate_events` covers exactly this shape).
* Or use the REST `events/query` endpoint with `mode: 'aggregate'` + `groupBy` — the same numbers come back as rows.

## Knowledge Ingestion (async)

Background ingestion for large knowledge-base files (50k-row catalogs, big documents). The upload returns immediately with a job id; batched embedding runs server-side with checkpoint resume. The SDK's [`Knowledge.ingestFile`](/core-concepts/knowledge-rag#async-ingestion-for-large-files) (SDK ≥ `1.3.2`) and `rf kb upload` (CLI ≥ `0.3.22`) wrap these endpoints.

### Start an ingestion

```http theme={null}
POST /api/v1/runtime/v1/vectors/files/ingest
Content-Type: multipart/form-data
```

**Form fields:**

| Field                        | Required | Description                                        |
| ---------------------------- | -------- | -------------------------------------------------- |
| `file`                       | yes      | The file (PDF, DOCX, TXT, MD, JSON, CSV).          |
| `vectorStore`                | yes      | Vector store **name**.                             |
| `metadata`                   | no       | JSON string merged into every document's metadata. |
| `csvDelimiter`               | no       | CSV delimiter override (sniffed when omitted).     |
| `csvContentColumns`          | no       | Comma-separated columns to embed (default: all).   |
| `csvMetadataColumns`         | no       | Comma-separated columns copied to metadata.        |
| `hygieneStripHtml`           | no       | `true` to strip HTML tags and decode entities.     |
| `hygieneRemoveUrls`          | no       | `true` to remove URLs from content.                |
| `hygieneDropEmptyValues`     | no       | `true` to drop empty/placeholder values.           |
| `hygieneNormalizeWhitespace` | no       | `true` to collapse repeated whitespace.            |
| `hygieneDedupeUnits`         | no       | `true` to drop duplicate rows/chunks.              |

CSV files are ingested **one document per row**. Response is `202 Accepted`:

```json theme={null}
{
  "success": true,
  "jobId": "b8b2e1f0-...",
  "status": "queued",
  "statusUrl": "/runtime/v1/vectors/ingestion-jobs/b8b2e1f0-...",
  "vectorStore": "product-catalog",
  "message": "File accepted; processing in background. Poll statusUrl for progress."
}
```

### Poll a job

```http theme={null}
GET /api/v1/runtime/v1/vectors/ingestion-jobs/:jobId
```

```json theme={null}
{
  "jobId": "b8b2e1f0-...",
  "status": "processing",
  "originalName": "catalog.csv",
  "totalChunks": 53068,
  "processedChunks": 31488,
  "progress": 0.5934,
  "documentId": null,
  "error": null,
  "startedAt": "2026-07-01T18:20:11.000Z",
  "completedAt": null,
  "createdAt": "2026-07-01T18:20:04.000Z"
}
```

`status` transitions `queued → processing → completed | failed`. `documentId` is set on completion; `error` on failure. Ingestion resumes from a checkpoint if the worker restarts mid-job.

### List a store's jobs

```http theme={null}
GET /api/v1/runtime/v1/vectors/stores/:name/ingestion-jobs?status=&limit=&offset=
```

Returns `{ "success": true, "vectorStore": "...", "jobs": [ ...job objects... ] }`, newest first (default limit 50, max 100).

## Errors

The runtime API uses standard HTTP status codes:

| Status | Meaning                                                                                     |
| ------ | ------------------------------------------------------------------------------------------- |
| `400`  | Invalid input — usually a Zod/DTO validation failure. The body has a `message` array.       |
| `401`  | Missing / invalid API key.                                                                  |
| `403`  | API key is valid but lacks access to the requested resource (wrong tenant, disabled scope). |
| `404`  | Resource not found. For reviews, this also fires when the ID belongs to another tenant.     |
| `409`  | Conflict — for execution reviews, the execution already has one.                            |
| `422`  | Semantic validation error (e.g. `comment` shorter than 10 chars).                           |
| `5xx`  | Server error. Safe to retry with backoff.                                                   |

Error responses share this shape:

```json theme={null}
{ "statusCode": 409, "message": "Execution already has a review", "error": "Conflict" }
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Reviews module" icon="clipboard-check" href="/api-reference/observability#execution-reviews">
    Typed SDK wrapper for these endpoints
  </Card>

  <Card title="Observability Guide" icon="chart-line" href="/core-concepts/observability">
    Tracing, metrics, and the review feedback loop
  </Card>
</CardGroup>
