# State-of-the-art research and polished deliverables, all in one API.

Canonical URL: https://www.getspine.ai/api

Spine orchestrates parallel agents across 300+ models, runs deep research, and returns client-ready slides, docs, spreadsheets, images, and reports.

- **Try the Playground**: https://platform.getspine.ai ($10 in free credits on us)
- **Read the docs**: https://docs.getspine.ai

## #1 on DeepSearchQA — by 8 points.

Spine Deep Research scores 87.6% on DeepSearchQA, ahead of Perplexity (79.5%), Claude (76.1%), ChatGPT (71.3%), and Gemini Deep Research (66.1%) on the same public eval.

[Read the benchmark write-up](https://www.getspine.aihttps://blog.getspine.ai/spine-swarm-hits-1-on-gaia-level-3-and-google-deepmind-deepsearchqa)

## How it works

1. **Start a run via REST** — POST a prompt (and an optional template — slides, deep_research, report, excel, memo, app, landing_page) to /v1/run. You get a run_id back immediately — no long-lived connection required.
2. **Async by design** — One POST kicks off the agent swarm; webhook callbacks notify your app the moment results are ready.
3. **Receive polished deliverables** — Download .pptx decks, .docx briefs, Excel models with formulas, images, JSON research reports with citations, or interactive dashboards — whatever the task called for.

## REST endpoints

Small surface. Full reference at https://docs.getspine.ai.

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/v1/run` | Start an async run (multipart form). Returns a run_id immediately. |
| `GET` | `/v1/run/{run_id}` | Poll status, retrieve final output and downloadable artifacts. |
| `GET` | `/v1/canvas/{id}/dag` | Block graph (nodes and edges) for the canvas this run produced. |
| `GET` | `/v1/canvas/{id}/tasks` | Task tree — every agent action, sub-task, and intermediary output. |

## Submit a task

### curl

```bash
# 1. Start a run
curl -X POST https://api.getspine.ai/v1/run \
  -H "X-API-KEY: $SPINE_API_KEY" \
  -F 'prompt=Build an investor-ready competitive analysis for the AI coding assistants space.' \
  -F 'template=slides'
# → { "data": { "run_id": "...", "status": "running" } }

# 2. Poll until done
curl https://api.getspine.ai/v1/run/<run_id> \
  -H "X-API-KEY: $SPINE_API_KEY"
# → { "data": { "status": "completed",
#       "artifacts": [{ "name": "deck.pptx", "download_url": "..." }] } }
```

### JavaScript (fetch)

```javascript
import { Spine } from "spine-sdk";

const spine = new Spine({ apiKey: process.env.SPINE_API_KEY });

const run = await spine.runs.create({
  prompt: "Build an investor-ready competitive analysis for the AI coding assistants space.",
  template: "slides",
});
const result = await spine.runs.waitForCompletion(run.run_id);

for (const a of result.result.artifacts) {
  console.log(a.name, a.download_url);
}
```

### Python (httpx)

```python
from spine import SpineClient, Template

with SpineClient() as client:  # picks up SPINE_API_KEY from env
    run = client.runs.create(
        prompt="Build an investor-ready competitive analysis for the AI coding assistants space.",
        template=Template.SLIDES,
    )
    result = run.wait()

    for a in result.artifacts:
        print(a.name, a.download_url)
```

## Pay as you go. No subscriptions.

- $1 = 1,000 credits
- $10 free to try

Sign up at platform.getspine.ai and get $10 in free credits on us — enough to run several real tasks. No subscriptions, no seats, no monthly minimums.

Using Spine on the web instead? See Canvas pricing: https://www.getspine.ai/pricing

## Docs

Full request/response schemas, deliverable formats, and error codes: https://docs.getspine.ai

---

# API Reference

Base URL: `https://api.getspine.ai`

## Works inside every agentic surface

Spine is a REST API — call it from any coding agent, IDE, or chat. Paste this page into the agent and it has everything it needs.

- **Claude** — Turn any Claude turn into a 50-page investment memo or a 30-slide deck — not just a message.
- **Cursor** — Cursor agents ship research memos, decks, and spreadsheets from a rule file — not just code.
- **Replit** — Replit apps pull real research and client-ready artifacts inline — no more placeholder content.
- **Codex** — Give the Codex CLI long-running research agents and polished deliverables behind one REST call.
- **Lovable** — Lovable apps produce real investor decks, market memos, and research reports for end users.
- **OpenClaw** — OpenClaw agents ship real research briefs, decks, and spreadsheets — not just local-system actions.
- **Hermes** — Hermes agents send polished client deliverables as fast as they send messages.

Agent integration guide: https://docs.getspine.ai/api-reference/for-agents

## Authentication

All endpoints require an `X-API-KEY` header. Get a key from the Spine dashboard → Settings → Developer Keys. Keys can be rotated or revoked at any time.

```
X-API-KEY: sk_spine_...
```

## Endpoints

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/v1/run` | Start an async run (multipart form). Returns a run_id immediately. |
| `GET` | `/v1/run/{run_id}` | Poll status, retrieve final output and downloadable artifacts. |
| `GET` | `/v1/canvas/{id}/dag` | Block graph (nodes and edges) for the canvas this run produced. |
| `GET` | `/v1/canvas/{id}/tasks` | Task tree — every agent action, sub-task, and intermediary output. |

## Start a run — request parameters

`POST /v1/run` accepts a `multipart/form-data` body with the following fields:

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| `prompt` | yes | string | Natural-language description of what to produce. |
| `template` | no | string | One of: auto, deep_research, report, slides, memo, excel, app, landing_page. Defaults to 'auto'. |
| `blocks` | no | JSON array | Advanced: pass an explicit array of block definitions to override the template. See valid block types above. |
| `agent_instructions` | no | string | Natural-language directive injected into every agent layer to shape tone, depth, scope, and citation requirements. Additive to template/blocks. |
| `files` | no | file[] | Attach images (PNG/JPEG/GIF/WEBP/SVG) or documents (PDF/DOCX/XLSX/CSV/TXT/MD/YAML/JSON/ZIP) as agent context. Repeat the field for multiple files. |

## Templates

Pass one of the following `template` values, or pass `blocks` (a JSON array of block definitions) for finer control. `blocks` overrides the template.

| id | what it does | output |
| --- | --- | --- |
| `auto` | Let Spine pick blocks based on the prompt | Varies |
| `deep_research` | Multi-source research with citations | Memo (.docx) |
| `report` | Structured report with sections | Report (.docx) |
| `slides` | Presentation deck | Slides (.pptx) |
| `memo` | Short executive memo | Memo (.docx) |
| `excel` | Spreadsheet analysis | Workbook (.xlsx) |
| `app` | Single-page web app | App (.html) |
| `landing_page` | Marketing landing page | Page (.html) |

## Block types (for `blocks` override)

`prompt-block`, `list-block`, `memo-block`, `document-block`, `excel-block`, `deep-research-block`, `image-block`, `presentation-block`, `app-block`, `table-block`, `prototype-block`, `landing-page-block`, `text-block`, `web-block`, `yt-block`, `web-research-block`, `file-block`.

## Agent instructions

`agent_instructions` is a natural-language directive injected into system prompts at every agent layer (orchestrator, strategist, persona agents, block operators). Additive to `template` and `blocks` — combine all three in one request.

| Instruction | Effect |
| --- | --- |
| "Be thorough, verify all claims with 2+ sources" | More personas spun up, verification steps inserted |
| "Prioritize speed, minimal verification" | Single persona, cross-checks skipped |
| "Write for a C-suite executive audience" | Executive tone across all outputs |
| "Focus on European markets only" | Research scoped geographically |
| "Include source citations for every claim" | Citation requirements injected into per-block prompts |

```bash
curl -X POST https://api.getspine.ai/v1/run \
  -H "X-API-KEY: sk_spine_..." \
  -F "prompt=Analyze the competitive landscape for AI coding tools" \
  -F "template=deep_research" \
  -F "agent_instructions=Be thorough, verify all claims with 2+ sources. Write for a technical audience."
```

## File uploads

Attach files via the `files` field on `POST /v1/run`. They ride along as `multipart/form-data` and become blocks on the resulting canvas.

| Kind | Block type | Supported types |
| --- | --- | --- |
| Images | `image-block` | PNG, JPEG, GIF, WEBP, SVG |
| Documents | `file-block (indexed for agent access)` | PDF, DOCX, XLSX, CSV, TXT, MD, YAML, JSON, ZIP |

```bash
curl -X POST https://api.getspine.ai/v1/run \
  -H "X-API-KEY: sk_spine_..." \
  -F "prompt=Summarize this sales data and create a chart" \
  -F "template=report" \
  -F "files=@sales-data.csv;type=text/csv" \
  -F "files=@revenue-chart.png;type=image/png"
```

- Multiple files can be attached in a single request — repeat `-F "files=@..."` per file.
- Image files become `image-block`s and are available to downstream agents by visual reference.
- Document files are indexed at ingest time so every agent in the run can query them.
- Maximum file size and total attachment limits apply per plan.

## Run statuses

| Status | Meaning | Terminal? |
| --- | --- | --- |
| `running` | Agents still executing | No |
| `completed` | All agents finished, results available | Yes |
| `partial` | Some blocks failed but usable results exist | Yes |
| `failed` | Run failed — see `errors` array | Yes |

Runs average 10–20 minutes; complex research can take up to 2 hours. Poll every 15–30 seconds, or use a backoff (start at 15 s, grow to 60 s) to reduce traffic on longer runs. Webhooks (below) are the preferred integration path.

## Response shapes

Run just started:

```json
{
  "data": {
    "run_id": "01HXZJ4P...K3Q9",
    "status": "running"
  }
}
```

While running:

```json
{
  "success": true,
  "data": {
    "run_id": "uuid",
    "status": "running",
    "canvas_id": "uuid",
    "progress": {
      "tasks_completed": 3,
      "tasks_total": 8,
      "elapsed_ms": 480000
    }
  }
}
```

Run completed:

```json
{
  "data": {
    "status": "completed",
    "result": {
      "final_output": "<summary text>",
      "artifacts": [
        { "name": "deck.pptx", "download_url": "https://..." }
      ]
    }
  }
}
```

Run failed:

```json
{
  "success": true,
  "data": {
    "run_id": "uuid",
    "status": "failed",
    "errors": [{ "error": "Agent execution failed" }]
  }
}
```

## Full polling loop (Python)

```python
import os, time, httpx

API_KEY = os.environ["SPINE_API_KEY"]
BASE = "https://api.getspine.ai"
H = {"X-API-KEY": API_KEY}

def run(prompt, template="auto", **kwargs):
    r = httpx.post(f"{BASE}/v1/run", headers=H,
                   data={"prompt": prompt, "template": template, **kwargs})
    run_id = r.json()["data"]["run_id"]

    while True:
        p = httpx.get(f"{BASE}/v1/run/{run_id}", headers=H).json()["data"]
        if p["status"] in ("completed", "partial", "failed"):
            return p
        time.sleep(5)

result = run("Build a landing page for an AI sales tool", template="landing_page")
print(result["result"]["final_output"])
for a in result["result"]["artifacts"]:
    print(a["name"], "→", a["download_url"])
```

## Use as a tool inside Claude / Anthropic SDK

```typescript
// Use Spine as a tool inside the Anthropic SDK
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const tool = {
  name: "spine_run",
  description: "Spawn a multi-agent Spine run that produces research, memos, decks, excel, or apps.",
  input_schema: {
    type: "object",
    properties: {
      prompt: { type: "string" },
      template: {
        type: "string",
        enum: ["auto", "deep_research", "report", "slides", "memo", "excel", "app", "landing_page"],
      },
    },
    required: ["prompt"],
  },
};

const resp = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 2048,
  tools: [tool],
  messages: [{ role: "user", content: "Make a market report on AI agents" }],
});
// On stop_reason="tool_use" → POST /v1/run, poll GET /v1/run/{run_id}, feed result back.
```

The `spine_run` tool kicks off a run; your agent should hand the user the `run_id` and stop, or poll `GET /v1/run/{run_id}` on its own.

## Auditable trace

Spine returns a step-by-step trace of every agent action, sub-task dispatched, and intermediary output produced. Debug runs, cite sources, or show your work to clients — nothing is a black box.

Fetch the full task tree (every agent dispatch, tool call, and intermediary output) with `GET /v1/canvas/{id}/tasks`. Fetch the block graph (nodes and edges) with `GET /v1/canvas/{id}/dag`.

### Example task tree

- **Morning Brief: Axios + Mercor + Claude leak coverage** _[task]_
  - **Axios Attacks Researcher** _[persona]_
    - **Create Plan Note** _[agent]_
    - **Research: Supply-chain Attack Vectors** _[agent]_
      - `axios-research-block` _[block]_
  - **Morning Brief Synthesizer** _[persona]_
    - **Compose Exec Memo** _[agent]_
      - `synthesis-memo-block` _[block]_
    - **Render Dashboard** _[agent]_
      - `deck.pptx` _[block]_

The same run renders as a canvas DAG at `GET /v1/canvas/{id}/dag` — 7 nodes, 6 dependency edges.

## Webhooks

Webhooks let your app react to run lifecycle events without polling. Spine POSTs a signed JSON envelope to a URL you configured (per workspace in the developer portal); you verify it with a shared secret.

### Event types

| Event | Fires when |
| --- | --- |
| `run.started` | Immediately after `POST /v1/run` creates the run. |
| `run.completed` | The run reaches terminal status `completed`. Includes `final_output` and artifacts. |
| `run.failed` | The run reaches terminal status `failed`. Includes the error message. |
| `webhook.ping` | Fired by **Send test event** in the portal — use it to exercise your verifier. |

### Event envelope

```json
{
  "id": "evt_78ffe7c625d94d2192b2f5a8d63933a6",
  "type": "run.completed",
  "created": 1745251200,
  "livemode": true,
  "api_version": "2026-04-01",
  "data": {
    "object": {
      "run_id": "06bb6cee-7982-48af-acd4-05d21ce39ed5",
      "canvas_id": "3c86545a-2e06-4c29-aac5-557f897c8ada",
      "status": "completed",
      "duration_ms": 10736,
      "artifacts": [],
      "final_output": "2 + 2 equals 4."
    }
  }
}
```

### Request headers

```
Content-Type: application/json
User-Agent: Spine-Webhooks/1.0
Spine-Signature: t=1745251200,v1=5fe3f78dbf86c53084d1bd1222005d3c064567937e5ceafb904e30551fdb89ad
Spine-Event-Id: 37070895-029a-4802-a5da-4052e7964cc3
Spine-Event-Type: run.completed
```

`Spine-Signature` is `t={unix_ts},v1={hex}` — timestamp and HMAC-SHA256 over `{t}.{raw_body}`. `Spine-Event-Id` equals `event.id`; persist it to dedupe.

### Verify the signature

Always verify before processing — anyone who learns your URL can send unsigned POSTs.

1. Parse the header — split on `,` to extract `t` (unix seconds) and every `v1=` value.
2. Check freshness — reject if `abs(now - t) > 300` to block replayed deliveries.
3. Rebuild the signed payload — literally `{t}.{raw_body}` using the **raw request body bytes**, not the parsed JSON.
4. Recompute the HMAC — `HMAC-SHA256(secret, signed_payload)` as a lowercase hex digest.
5. Constant-time compare against any `v1` value. Use `hmac.compare_digest` (Python) or `crypto.timingSafeEqual` (Node) — never `==`.

```python
import hmac, hashlib, time

TOLERANCE_SECONDS = 300

def verify_spine_webhook(secret: str, raw_body: bytes, header: str) -> bool:
    pairs = [kv.split("=", 1) for kv in header.split(",") if "=" in kv]
    t = next((v for k, v in pairs if k == "t"), None)
    v1s = [v for k, v in pairs if k == "v1"]
    if not t or not v1s:
        return False
    if abs(int(time.time()) - int(t)) > TOLERANCE_SECONDS:
        return False
    expected = hmac.new(
        secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return any(hmac.compare_digest(expected, v) for v in v1s)
```

```typescript
import crypto from "node:crypto";

const TOLERANCE_SECONDS = 300;

export function verifySpineWebhook(
  secret: string,
  rawBody: Buffer | string,
  header: string,
): boolean {
  const pairs = header.split(",").map((s) => s.trim().split("="));
  const t = pairs.find(([k]) => k === "t")?.[1];
  const v1s = pairs.filter(([k]) => k === "v1").map(([, v]) => v);
  if (!t || v1s.length === 0) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(t, 10)) > TOLERANCE_SECONDS) {
    return false;
  }
  const body = typeof rawBody === "string" ? Buffer.from(rawBody, "utf8") : rawBody;
  const signed = Buffer.concat([Buffer.from(`${t}.`), body]);
  const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
  const e = Buffer.from(expected);
  return v1s.some((s) => {
    const b = Buffer.from(s);
    return b.length === e.length && crypto.timingSafeEqual(e, b);
  });
}
```

### Operational notes

- Return any `2xx` status within 10 seconds — anything else is recorded as `failed`. Acknowledge fast, process async.
- Persist `Spine-Event-Id` alongside downstream effects to dedupe resends and repeat triggers.
- The `whsec_...` secret is shown exactly once when you create or rotate an endpoint. Store it in a secret manager.
- Use `*` in the events list to subscribe an endpoint to every event type, including ones added later.
- HTTPS is enforced in production.

## Errors

Errors return `{ "success": false, "error": "..." }` with one of these HTTP codes:

| Code | Reason |
| --- | --- |
| `400` | Bad template, malformed blocks, or invalid UUID. |
| `401` | Missing or invalid X-API-KEY. |
| `404` | Run or canvas not found (or not owned by the authenticated key). |
| `500` | Server error — retry with exponential backoff. |

