API & tokens
Marca's API lives under /api/v1. It's what the review widget, the dashboard, the CLI and the MCP server use, and you can build on it too. The full list of operations is in the API reference, generated from the OpenAPI document; the SDK wraps it with types.
Credentials
Send Authorization: Bearer <credential>. Every credential belongs to one project; anything it can't see — including a revoked or expired credential — answers 404.
| Credential | Looks like | Where it comes from | Acts as |
|---|---|---|---|
| API key | sk_live_… | Project → Access → API keys | No one, unless it names a member with X-Marca-On-Behalf-Of. Limited by its scopes. |
| Agent token | rev_… | marca login (Agents & MCP) | The person who logged in, with their role. |
| Widget token | rev_… | The review widget's sign-in, or sign-in from your app | The reviewer, limited to commenting and creating tasks — even for owners, because it lives in a web page. |
| Dashboard session | cookie | Signing in at marca.dev | The signed-in person, with their role. |
The project token (proj_…) identifies a project in URLs and in the widget's data-project. It isn't a secret on its own.
API key scopes
Choose scopes when you create a key. Keys created before scopes existed have manifests:write and tasks:read, exactly what they could always do.
| Scope | Allows |
|---|---|
tasks:read | Reading tasks, comments, the review feed and the member list |
tasks:write | Creating, editing, moving and commenting on tasks — on behalf of a member |
comments:write | Creating and editing page comments — on behalf of a member |
manifests:write | Crawler writes: publishing manifests, moving pins after a crawl, and reporting QA results (the CLI and crawler) |
webhooks:manage | Creating and managing webhooks |
A request without the scope it needs gets 403 insufficient_scope with the missing scope in required.
Acting on behalf of a member
Review items are always authored by a person. To write with an API key, name the workspace member the request is for:
POST /api/v1/projects/proj_…/tasks
Authorization: Bearer sk_live_…
X-Marca-On-Behalf-Of: [email protected]
Content-Type: application/json
{ "route": "/pricing", "instruction": "Raise the CTA contrast" }
Use the member's email or their stable id, user_<id>. The request then behaves exactly as if that member made it: it can't do more than their role allows, and anything it writes shows the key's label ("via CI bot") next to their name. Naming someone who isn't a member of the project's workspace returns 403 on_behalf_of_not_member.
Calling from a server or a browser
API keys and agent tokens are server credentials: send them from your backend, never from browser code. Server requests don't need an Origin header. If a request does carry one, it must be on the project's allowed origins, like the widget's requests.
Common endpoints
All under /api/v1/projects/{projectToken}. See the API reference for request and response bodies.
| Method | Path | Purpose |
|---|---|---|
GET | /whoami | Describe the calling credential: user, role, scopes, expiry. |
GET POST | /tasks | List tasks (?include=subtasks for all); create one. |
GET PATCH | /tasks/{id} | Task detail with comments and subtasks; change status, instruction, priority, assignee. |
POST | /tasks/bulk-status | Move several items to one status. |
POST | /tasks/{id}/comments | Comment on a task. |
GET | /items | The review feed: open tasks plus page comments. |
GET POST | /comments | List or pin page comments. |
GET POST | /webhooks | Manage webhook endpoints. |
POST | /exports | Promote selected items to tasks and return a brief (markdown, slack, cursor, claude). |
POST | /send | File selected items as GitHub or Linear issues. |
Rate limits & privacy
Unauthenticated routes (signup, widget bootstrap, sign-in exchanges) are rate-limited per IP. Source IPs are truncated before storage (last IPv4 octet zeroed, IPv6 cut to the first 48 bits) — full IPs are never written to the database or logs.