# REST API

Plain JSON over HTTPS for everything the MCP server can do. Every route below is also an MCP tool of the same name: one implementation, one scope map, one rate limit, reachable from curl, a cron job or an automation platform.

## Conventions

| Item | Rule |
| --- | --- |
| Base URL | `https://app.streamagent.io/api/v1` |
| Authentication | `Authorization: Bearer sa_live_…` with an API key, or a `sat_` assistant token. See [Authentication](/docs/authentication). |
| Content | JSON in, JSON out. Send `Content-Type: application/json` on writes. |
| Ids | UUID strings, exactly as the list routes return them. Never construct one. |
| Versioning | The version is in the path. This version only gains fields and routes; a breaking change would be a new path. |
| Rate limits | The same per-credential ceilings as the MCP server, with `Retry-After` on `429`. |
| Lead privacy | Lead fields are masked exactly as on the MCP server when the workspace keeps masking on. |

```bash
curl "https://app.streamagent.io/api/v1/leads?status=new&limit=20" \
  -H "Authorization: Bearer sa_live_…"
```
_The newest twenty leads still marked new._

```bash
curl -X POST https://app.streamagent.io/api/v1/routes \
  -H "Authorization: Bearer sa_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: onboarding-route-2026-09" \
  -d '{"entry_video":"Welcome tour","name":"Onboarding"}'
```
_Create a route from a video title, safely retryable._

## Pagination

List routes take `limit` (default 50, at most 100) and `cursor`. The response carries a `pagination` block with `total`, `limit`, `has_more` and `next_cursor`; pass `next_cursor` back as `cursor` until it is `null`. Cursors are opaque and only valid for the same route and filters.

```json
{
  "leads": [ … ],
  "pagination": { "total": 143, "limit": 50, "has_more": true, "next_cursor": "eyJvIjo1MH0" },
  "pii_redacted": true
}
```
_A first page of leads._

## Idempotent writes

Send an `Idempotency-Key` header (any string up to 255 characters, unique per operation) on a write and the first response is stored for 24 hours. A retry with the same key and the same request gets that response back with `Idempotency-Replayed: true`; the same key with a different request is a `409 conflict`. Keys are private to the credential that sent them.

> Use it on every create from a job that can run twice: a booking, a route, a tag rename.

## Errors

Every non-success response is `{ "error": { "code", "message", "details"? } }`. The message is written to be shown to a developer and lists every problem it found, so one fix reaches a correct call.

| Code | HTTP | Meaning |
| --- | --- | --- |
| `unauthorized` | 401 | No credential, or one that is malformed, revoked or expired. The response carries a `WWW-Authenticate` header. |
| `forbidden` | 403 | The credential is valid but lacks the scope the route needs; `details.required_scope` names it. |
| `plan_limit` | 403 | The workspace plan does not include this capability; `details.entitlement` names it. |
| `not_found` | 404 | No route at that path, or the object id is not in this workspace. |
| `method_not_allowed` | 405 | The path exists but not for that verb. The `Allow` header lists the verbs that work. |
| `conflict` | 409 | An `Idempotency-Key` was reused with a different request, or the first request with it is still running. |
| `invalid_request` | 400 | A parameter or body field is missing, unknown or the wrong shape; the message lists every problem at once. |
| `rate_limited` | 429 | Over the per-credential ceiling. Retry after the number of seconds in `Retry-After`. |
| `internal` | 500 | Something failed on our side. The details are logged; retry once, then contact support. |

- Retry `429` after the header value and `500` once; never retry `400`, `401`, `403` or `409` unchanged.
- A `404` on an id means the object is not in this workspace, which is also what a wrong workspace key looks like.

## Machine-readable contract

The full OpenAPI document, generated from the same source as this page, is at [/api/v1/openapi.json](/api/v1/openapi.json). Point a client generator or an assistant at it.

## Routes

Grouped as on the [MCP server](/docs/mcp) page. Required parameters are marked with *. Path segments in braces are ids from the list routes; `GET` routes take parameters in the query string, other verbs in a JSON body.

## Videos

| Method and path | What it does | Scope | Query or body fields |
| --- | --- | --- | --- |
| `GET /videos` | Browse your library with status and view counts. | `videos:read` | `status`, `folder_id`, `tags`, `search`, `sort`, `limit`, `cursor` |
| `GET /videos/{video_id}` | Full detail and stats for one video. | `videos:read` | — |
| `POST /videos/compare` | Rank videos on real performance, not raw views. | `videos:read` | `video_ids`* |
| `GET /videos/{video_id}/key-moments` | The notable moments detected in a video. | `videos:read` | — |
| `PATCH /videos/{video_id}` | Rename, describe, retag, or archive a video (write). | `videos:write` | `title`, `description`, `tags`, `archived` |
| `POST /videos` | Pull a video file in from a public link; it processes in the background (write). | `videos:write` | `source_url`*, `title`, `folder_id`, `tags` |
| `POST /videos/uploads` | Create the video and get a one-time URL to send the file to (write). | `videos:write` | `file_name`*, `file_size`*, `title`, `folder_id`, `tags` |

## Analytics

| Method and path | What it does | Scope | Query parameters |
| --- | --- | --- | --- |
| `GET /videos/{video_id}/analytics` | Views, engagement, and conversion for a video. | `analytics:read` | `from_date`, `to_date` |
| `GET /videos/{video_id}/retention` | Where viewers stay and where they drop off. | `analytics:read` | — |
| `GET /changes` | What changed across your workspace lately. | `analytics:read` | `days` |
| `GET /analytics/summary` | The workspace numbers for a range, each with its prior-period change. | `analytics:read` | `range` |
| `GET /analytics/daily` | A per-day series between two dates. | `analytics:read` | `from`, `to` |

## Outcomes

| Method and path | What it does | Scope | Query parameters |
| --- | --- | --- | --- |
| `GET /outcomes` | The revenue ledger: purchases, booked calls and refunds with value. | `outcomes:read` | `from_date`, `to_date`, `kind`, `status`, `lead_id`, `video_id`, `limit`, `cursor` |

## Provisioning

| Method and path | What it does | Scope | Query or body fields |
| --- | --- | --- | --- |
| `GET /webhook-endpoints` | Every endpoint with its events and whether it is active. | `workspace:manage` | — |
| `POST /webhook-endpoints` | Register a URL and get its signing secret once (write). | `workspace:manage` | `name`*, `url`*, `events` |
| `PATCH /webhook-endpoints/{endpoint_id}` | Rename, re-point, change events, pause or rotate the secret (write). | `workspace:manage` | `name`, `url`, `events`, `active`, `rotate_secret` |
| `DELETE /webhook-endpoints/{endpoint_id}` | Remove an endpoint and its history (write). | `workspace:manage` | — |
| `POST /webhook-endpoints/{endpoint_id}/test` | Deliver the synthetic test event and report the result (write). | `workspace:manage` | — |
| `GET /webhook-endpoints/{endpoint_id}/deliveries` | What was sent to an endpoint and how it went. | `workspace:manage` | `status`, `limit`, `cursor` |
| `GET /api-keys` | Active keys by name, prefix and tier. | `workspace:manage` | — |
| `POST /api-keys` | Mint a key of equal or lower access and receive it once (write). | `workspace:manage` | `name`*, `scope` |
| `DELETE /api-keys/{key_id}` | Stop a key immediately (write). | `workspace:manage` | — |

## Leads

| Method and path | What it does | Scope | Query or body fields |
| --- | --- | --- | --- |
| `GET /leads` | Your captured leads, most engaged first. | `leads:read` | `video_id`, `status`, `source`, `from_date`, `to_date`, `search`, `sort`, `limit`, `cursor` |
| `GET /leads/{lead_id}` | One lead with full activity history. | `leads:read` | — |
| `GET /leads/{lead_id}/journey` | A lead’s complete timeline across your videos. | `leads:read` | — |
| `PATCH /leads/{lead_id}` | Set status, tags, or append a note (write). | `leads:write` | `status`, `tags`, `notes`, `email`, `name`, `phone` |
| `PUT /leads` | Add a lead by email, or enrich the one that already exists (write). | `leads:write` | `email`*, `first_name`, `last_name`, `name`, `phone`, `status`, `tags`, `notes`, `custom_fields`, `source` |

## Transcripts

| Method and path | What it does | Scope | Query parameters |
| --- | --- | --- | --- |
| `GET /transcripts/search` | Find where a topic is spoken across your library. | `transcripts:read` | `q`*, `video_id`, `limit` |
| `GET /videos/{video_id}/transcript` | The full transcript for a video. | `transcripts:read` | `offset`, `max_chars` |

## Routes

| Method and path | What it does | Scope | Query or body fields |
| --- | --- | --- | --- |
| `GET /routes` | Your interactive routes. | `routes:read` | `video_id`, `status`, `limit`, `cursor` |
| `GET /routes/{route_id}` | The structure of one route. | `routes:read` | — |
| `PATCH /routes/{route_id}` | Rename, pause, or archive a route (write). | `routes:write` | `name`, `status` |
| `POST /routes` | Start a new interactive route (write). | `routes:write` | `entry_video`*, `name` |
| `POST /routes/{route_id}/steps` | Add a video step to a route (write). | `routes:write` | `video`* |
| `POST /routes/{route_id}/branches` | Wire the choices between steps (write). | `routes:write` | `question`, `branches`* |

## Scheduling

| Method and path | What it does | Scope | Query or body fields |
| --- | --- | --- | --- |
| `GET /bookings` | Upcoming and past calls booked through your videos. | `leads:read` | `scope`, `limit`, `cursor` |
| `GET /scheduling/overview` | Your calendar shape at a glance. | `workspace:read` | — |
| `GET /scheduling/availability` | Why a slot is (or isn’t) offered. | `workspace:read` | `event_type` |
| `POST /bookings` | Book a call and capture the lead (write). | `leads:write` | `event_type_id`*, `start`*, `invitee_email`*, `invitee_name`, `invitee_phone`, `invitee_timezone` |

## Channel

| Method and path | What it does | Scope | Body fields |
| --- | --- | --- | --- |
| `GET /channel` | What’s live on your public channel. | `workspace:read` | — |
| `PUT /channel/items/{id}` | Put a video or route on the channel, or take it off (write). | `channel:write` | `kind`*, `visible`* |
| `PUT /channel/featured` | Choose what leads the channel page (write). | `channel:write` | `video_id`, `route_id` |
| `PATCH /channel` | Update the channel headline and intro line (write). | `channel:write` | `headline`, `dek` |

## Workspace

| Method and path | What it does | Scope | Query or body fields |
| --- | --- | --- | --- |
| `GET /workspace` | Plan, usage, and the guidance you wrote for assistants. | `workspace:read` | — |
| `GET /tags` | The tags in use across your content. | `workspace:read` | — |
| `POST /tags/rename` | Rename or merge a tag everywhere it’s used (write). | `videos:write` | `tag`*, `new_tag`*, `applies_to` |
| `GET /search` | One search across videos, transcripts, routes, and leads. | `workspace:read` | `q`* |

## Not in this version

Deleting a video or a lead stays a signed-in action; archive is the reversible path and is on `PATCH /videos/{video_id}`.

---
Source: https://app.streamagent.io/docs/api
