# SeedViral API v1, MCP connector and CLI

Version 1.0.0 of the connector; API version `v1`. Base URL `https://seedviral.com`.

## Files

| File | What |
| --- | --- |
| `seedviral-agent-1.0.0.mjs` | Connector + CLI in one file (Node 20+). Includes the official MCP TypeScript SDK 1.32.1 and zod 3.25.76. |
| `seedviral-agent-1.0.0-source.tar.gz` | Its source (`src/`, `package.json`, `package-lock.json`, this README, `SKILL.md`). |
| `SKILL.md` | Agent instructions (Claude skills format). |
| `SHA256SUMS` | Checksums. Verify with `shasum -a 256 -c SHA256SUMS`. |

Nothing is installed globally and no agent configuration is changed for you.

```sh
mkdir seedviral && cd seedviral
curl -fsSLO https://seedviral.com/agent/1.0.0/seedviral-agent-1.0.0.mjs
curl -fsSLO https://seedviral.com/agent/1.0.0/SHA256SUMS
shasum -a 256 -c SHA256SUMS --ignore-missing
export SEEDVIRAL_API_KEY=svk_…            # from SeedViral → Developers; never pass it as an argument
node seedviral-agent-1.0.0.mjs whoami
```

MCP client configuration (stdio). Put the key in the client's `env`, not in `args`:

```json
{
  "mcpServers": {
    "seedviral": {
      "command": "node",
      "args": ["/absolute/path/seedviral-agent-1.0.0.mjs", "mcp"],
      "env": { "SEEDVIRAL_API_KEY": "svk_…", "SEEDVIRAL_DOWNLOAD_DIR": "/absolute/path/videos" }
    }
  }
}
```

The connector speaks MCP over stdio (protocol 2025-11-25, older versions negotiated by the SDK). It is a local process; there is
no hosted SeedViral MCP server or OAuth connection yet.

Environment: `SEEDVIRAL_API_KEY` (required), `SEEDVIRAL_API_URL` (default `https://seedviral.com`; must be https),
`SEEDVIRAL_DOWNLOAD_DIR` (default the current folder), `SEEDVIRAL_ALLOW_INSECURE_LOCALHOST=1` (only for http://localhost testing).

The client never follows a redirect with your key, never puts the key in a URL, and fetches download links without it.

## Authentication and permissions

`Authorization: Bearer svk_<id>_<secret>`. A key belongs to one workspace; the workspace is taken from the key.

| Permission | Allows |
| --- | --- |
| `read` (every key) | catalog, balance, series and topics (read), videos, versions, shapes list, download links |
| `create` | create/update series drafts, topic source changes and previews, quotes, making videos (spends credits), shape requests |

No key can change billing or plans, connect or post to social accounts, or manage the account. A revoked or expired key, or a
key whose creator left the workspace, stops on its next request. Revoking does not cancel a video already accepted.

## Requests and responses

JSON in and out (`Content-Type: application/json`; bodies up to 64 KB, 1.2 MB for topic CSV uploads). Every response has
`Cache-Control: no-store` and `SeedViral-Api-Version: v1`. Errors:

```json
{ "error": { "code": "SCOPE_REQUIRED", "message": "This key is read only. …" } }
```

| Status | Codes (examples) |
| --- | --- |
| 400 | `INVALID_REQUEST`, `INVALID_DRAFT`, `INVALID_STYLE`, `INVALID_PAGE`, `IDEMPOTENCY_KEY_REQUIRED` |
| 401 | `API_KEY_REQUIRED`, `INVALID_API_KEY`, `KEY_EXPIRED` |
| 402 | `INSUFFICIENT_CREDITS` |
| 403 | `SCOPE_REQUIRED` |
| 404 | `SERIES_NOT_FOUND`, `VIDEO_NOT_FOUND`, `QUOTE_NOT_FOUND` |
| 409 | `IDEMPOTENCY_CONFLICT`, `IDEMPOTENCY_IN_PROGRESS` (`Retry-After: 2`), `SERIES_CHANGED`, `SERIES_ARCHIVED`, `PLAN_CHANGE_REQUIRED`, `SOURCE_CHANGED`, `PREVIEW_CHANGED` |
| 410 | `QUOTE_EXPIRED`, `WORKSPACE_DELETED` |
| 413 / 415 | `REQUEST_TOO_LARGE` / `JSON_REQUIRED` |
| 422 | source and link read errors (`SOURCE_UNREADABLE`, `LINK_UNREACHABLE`, …) |
| 429 | `RATE_LIMITED` (`Retry-After: 60`) |
| 503 | `UNAVAILABLE` (`Retry-After`) |

**Rate limits** (per workspace, all keys together, per hour): 1,200 reads, 300 writes, plus the studio's own limits (30 one-off
videos, 60 link reads, 60 source checks, 60 shape requests).

**Pagination**: list endpoints take `limit` (1–50, default 20) and `cursor`; responses carry `nextCursor` (null at the end).

**Idempotency**: `POST /api/v1/series`, `POST /api/v1/videos` and topic `use`/`add` require `Idempotency-Key` (8–128 of
`A-Z a-z 0-9 - _ . :`). The key is bound to the complete request body (and, for videos, to the quote's frozen input). The same
key and body returns the stored response (`Idempotent-Replayed: true`) without running again; the same key with another body
is `409 IDEMPOTENCY_CONFLICT`. A failed attempt stores nothing, so the same request may be retried after fixing the cause.
Keys are per API key and kept with the workspace.

## Endpoints

| Method and path | Permission | Body / query | Response |
| --- | --- | --- | --- |
| `GET /api/v1/workspace` | read | | `{workspaceId, key:{id,name,scopes}}` |
| `GET /api/v1/catalog` | read | | `{formats[], niches[], languages[], voices[], artStyles[], captionStyles[], durations[], cadences[], credits}` |
| `GET /api/v1/balance` | read | | `{available, reserved, plan, hasPlan}` |
| `GET /api/v1/series` | read | `limit`, `cursor`, `archived=true` | `{series[], nextCursor}` |
| `POST /api/v1/series` | create | `{settings}` (or the full studio `{draft}`) + Idempotency-Key | `201 {series}` (a draft; no plan, review mode, no destinations) |
| `GET /api/v1/series/{id}` | read | | `{series:{id,name,state,revision,draft,nextSlotAt,nextSlots,blockers,plan,…}}` |
| `PUT /api/v1/series/{id}` | create | `{settings, expectedRevision}` (only what changes) or `{draft, expectedRevision}` | `{series}`; publishing settings are kept; another cadence than the plan is `409 PLAN_CHANGE_REQUIRED` |
| `GET /api/v1/series/{id}/topics` | read | | `{source, queue[], queued, history[], waitingSince, queueLimit}` |
| `POST /api/v1/series/{id}/topics` | create | `{action:"preview", kind:"rss"\|"youtube"\|"csv", input}` | preview (saves nothing); CSV `input` is `{fileName, text, mapping?}` and the preview returns `mapping`, `header`, `errors` and `previewHash` |
| | | `{action:"use", kind, input, expectedSourceId, previewHash?}` + Idempotency-Key | topics view |
| | | `{action:"add", title, notes?, link?}` + Idempotency-Key | topics view |
| | | `{action:"pause"\|"resume"\|"remove", sourceId}`, `{action:"skip"\|"requeue", itemId}`, `{action:"check"}` | topics view |
| `POST /api/v1/videos/quotes` | create | `{mode, text, link?, duration, style, format?, transcript?, remix?}` | `201 {quote:{id, credits, expiresAt, balance, input, settings}}` |
| `POST /api/v1/videos` | create | `{quoteId}` + Idempotency-Key | `201 {video:{id, credits, quoteId, state}}` |
| `GET /api/v1/videos` | read | `seriesId`, `limit`, `cursor` | `{videos[], nextCursor}` |
| `GET /api/v1/videos/{id}` | read | | `{video:{id, stage, title, topic, scheduledFor, readyAt, failure, …}}` |
| `GET /api/v1/videos/{id}/versions` | read | `cursor` | saved versions |
| `GET /api/v1/videos/{id}/download` | read | | `{url, expiresAt, thumbnailUrl}` (fetch the url without the key) |
| `GET /api/v1/videos/{id}/exports` | read | | `{available, exports[{id, aspect, state, downloadUrl}]}` |
| `POST /api/v1/videos/{id}/exports` | create | `{aspect:"1:1"\|"16:9"}` | `202 {export}` (free; same version + shape = same export) |

Series `settings`: only `topic` (your own words, or a niche name such as "History") or `niche` is needed to create; optional
`brief`, `name`, `language`, `voice` (a name such as "Dramatic"), `artStyle`, `captionStyle`, `music` ("none" or a track),
`duration` ("30-40"/"60-70"), `cadence` ("3/week"/"1/day"/"2/day"), `postingTime`, `secondPostingTime` (HH:MM), `timezone` (IANA),
`format` and `reviewScripts`. Missing values get defaults (English, first voice, Cinematic, Clean, no music, 30-40, 3/week, 18:00
Europe/London). Values match without regard to case; an unknown one is `400 INVALID_SETTINGS` whose message lists the valid options.

`style` is `{"from":"series","seriesId":"…"}` or `{"from":"custom","art","voice","caption","language"}` with catalog values.
`mode`: `prompt` (8–1,000 characters), `script` (40–4,000), `link` (read when quoting), `remix` (`remix: like|part2`, a link
and/or a 40+ character transcript). Quotes last 15 minutes; one quote makes at most one video. A quote freezes the effective
settings it shows in `settings` (language, voice, art, caption, music, effect, format, duration, and the series they came from):
the video is made with exactly those, even if the series is edited, archived or removed before you accept. A missing or
archived series, or a format that needs 60–70 seconds, is refused when quoting. One-off videos cost
10 credits (30–40 s) or 15 credits (60–70 s); series videos use the series plan.

## CLI

`node seedviral-agent-1.0.0.mjs help` lists every command. Output is JSON; errors go to stderr with exit code 1.
