API & MCP Reference
The Maneuvr API exposes your workspace's competitors, signals, battlecards, runs, and digests over a small, predictable REST surface — plus webhooks for push and an MCP endpoint for AI assistants like Claude and Cursor.
Quickstart
Three calls to prove the surface works.
# 1. List the competitors in your workspace
curl https://your-workspace.maneuvr.app/api/v1/entities \
-H "Authorization: Bearer mk_live_YOUR_KEY"
# 2. Ingest a signal you heard about
curl -X POST https://your-workspace.maneuvr.app/api/v1/events:ingest \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"entity_id": "00000000-0000-0000-0000-000000000000",
"type": "pricing",
"title": "Acme raised Enterprise tier to $50k",
"url": "https://acme.com/pricing"
}'
# 3. Kick off a refresh run
curl -X POST https://your-workspace.maneuvr.app/api/v1/runs \
-H "Authorization: Bearer mk_live_YOUR_KEY"Authentication
All /v1 endpoints take a Bearer token. Workspace API keys (recommended) or a Supabase session JWT both work.
Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCreate and revoke keys in Settings → API Keys. The plaintext value is shown once at creation.
Response envelope
Every endpoint returns the same shape — even errors. SDKs and webhook consumers can parse one schema.
{
"data": { /* the resource, or an array of resources */ },
"error": null,
"meta": {
"request_id": "req_8f4c…",
"page": { "next_cursor": "eyJjcmVhdGVkX2F0Ij…" }
}
}request_id is also returned as the X-Request-Id response header. Include it when reporting issues.
Pagination
Cursor-based, opaque. Pass the cursor from meta.page.next_cursor back as ?cursor=…
curl "https://your-workspace.maneuvr.app/api/v1/events?limit=50&cursor=eyJjcmVhdGVkX2F0Ij…" \
-H "Authorization: Bearer mk_live_YOUR_KEY"Default limit is 25, max 100. When next_cursor is null, you've reached the end.
Idempotency
Send an Idempotency-Key on POSTs to safely retry. We cache the first response for 24 hours and replay it byte-for-byte.
curl -X POST https://your-workspace.maneuvr.app/api/v1/events:ingest \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Idempotency-Key: 7c2b5f48-…" \
-H "Content-Type: application/json" \
-d '{ "entity_id": "...", "type": "launch", "title": "..." }'If you send a different body with a reused key, the request is rejected with 409 conflict.
Errors & rate limits
Errors share the envelope; status is mapped from error.code.
{
"data": null,
"error": {
"code": "validation",
"message": "Invalid body",
"details": { "fieldErrors": { "title": ["Required"] } }
},
"meta": { "request_id": "req_…" }
}unauthorized → 401
forbidden → 403
not_found → 404
conflict → 409
validation → 422
rate_limited → 429
internal → 500
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (ISO 8601). Back off until the reset timestamp on 429.
Endpoints
Full schemas live in the OpenAPI spec. Required scope shown on the right.
| Method | Path | Summary | Scope |
|---|---|---|---|
GET | /api/v1/entities | List entities | read:entities |
POST | /api/v1/entities | Create an entity | write:entities |
GET | /api/v1/entities/{id} | Get an entity | read:entities |
PATCH | /api/v1/entities/{id} | Update an entity | write:entities |
DELETE | /api/v1/entities/{id} | Archive an entity | write:entities |
POST | /api/v1/entities/{id}/autofill | Auto-discover sources and seed signals | write:entities |
GET | /api/v1/events | List events (newest first) | read:events |
GET | /api/v1/events/{id} | Get an event with impact brief | read:events |
POST | /api/v1/events:ingest | Ingest a competitor signal | write:events |
GET | /api/v1/deal-outcomes | List deal outcomes (newest close first) | read:deal_outcomes |
POST | /api/v1/deal-outcomes | Log a won, lost, or no-decision deal | write:deal_outcomes |
GET | /api/v1/deal-outcomes/{id} | Get a deal outcome | read:deal_outcomes |
GET | /api/v1/battlecards/{entityId} | Get the battlecard for an entity | read:battlecards |
POST | /api/v1/battlecards/{entityId}/regenerate | Regenerate a battlecard from latest signals | write:entities |
GET | /api/v1/runs | List recent research runs | read:runs |
POST | /api/v1/runs | Kick off a new research run | write:runs |
GET | /api/v1/runs/{id} | Get a run by id | read:runs |
GET | /api/v1/digests | List digests | read:digests |
GET | /api/v1/digests/{id} | Get a digest | read:digests |
GET | /api/v1/webhooks | List subscriptions | admin:webhooks |
POST | /api/v1/webhooks | Create a subscription (returns signing secret once) | admin:webhooks |
GET | /api/v1/webhooks/{id} | Get a subscription | admin:webhooks |
PATCH | /api/v1/webhooks/{id} | Pause, resume, or change a subscription | admin:webhooks |
DELETE | /api/v1/webhooks/{id} | Delete a subscription | admin:webhooks |
GET | /api/v1/webhooks/{id}/deliveries | List recent delivery attempts | admin:webhooks |
POST | /api/v1/webhooks/deliveries/{deliveryId}/replay | Replay a failed delivery | admin:webhooks |
POST | /api/v1/mcp | Model Context Protocol — Streamable HTTP transport | mcp:read |
Webhooks
Subscribe a URL to one or more event types. We POST a thin envelope; fetch the full resource via REST if you need it.
Payload
{
"id": "evt_…",
"type": "event.impact_generated",
"workspace_id": "…",
"occurred_at": "2026-06-11T11:30:00Z",
"data": {
"id": "…",
"kind": "event",
"links": { "self": "/v1/events/…" }
}
}Signature
We sign every delivery with X-Maneuvr-Signature: t=<unix-ts>,v1=<hex> where hex = HMAC-SHA256(secret, `$${ts}.${rawBody}`). Reject if the timestamp is more than 5 minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=") as [string, string]),
);
const t = parts.t;
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Retries
5 attempts at 1m, 5m, 30m, 2h, 12h. After the final failure the delivery is marked dead; replay any delivery from Settings → Webhooks.
MCP (Model Context Protocol)
Point Claude Desktop, Cursor, or any MCP client at /api/v1/mcp to give your AI assistant scoped access to this workspace.
Claude Desktop
{
"mcpServers": {
"maneuvr": {
"url": "https://your-workspace.maneuvr.app/api/v1/mcp",
"headers": { "Authorization": "Bearer mk_live_YOUR_KEY" }
}
}
}Available tools
Read: list_competitors, get_competitor, search_signals, get_event, list_recent_changes, get_latest_digest. Write (scope-gated): trigger_run, ingest_signal.
OpenAPI 3.1 spec
Machine-readable spec for code generation, Postman, or import into any OpenAPI tool.