Reference
REST API
Authentication, errors, pagination and every endpoint of the Comity API, grouped by area.
On this page
Base URL: https://api.comity.so. All REST endpoints are under /v1, take and return JSON, and use the same objects as the MCP tools.
Authentication
curl https://api.comity.so/v1/me -H "Authorization: Bearer cmt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"user": { "id": "0199…", "email": "diego@acme.com", "name": "Diego" },
"org": { "id": "0199…", "slug": "acme", "name": "Acme Inc", "data_mode": "full" },
"role": "member",
"token": { "id": "0199…", "name": "cli · diego-laptop", "kind": "pat", "scopes": ["hooks:evaluate", "mcp:tools", "…"] }
}- A token belongs to one org; there is no org parameter.
- Personal tokens (
pat) act as their user with that user's role.comity logincreates one per machine. Owners and admins can mint more, including for another member (user_email), withPOST /v1/tokens. - Service tokens (
service) have no user. Endpoints marked "personal token" below reject them with 403. They can't holdhooks:evaluateormcp:tools. - Each endpoint requires a scope. Some also require a role: "admin" below means owner or admin.
Invalid, expired or revoked tokens get 401 with WWW-Authenticate: Bearer realm="comity". A personal token stops working when its user leaves the org.
The dashboard uses a session cookie with an X-Comity-Org: <slug> header and X-Comity-CSRF: 1 on writes; cookie sessions can't call hooks or MCP. Use tokens for integrations.
Errors
Errors are RFC 9457 application/problem+json:
{
"type": "urn:comity:problem:validation",
"title": "Invalid request",
"status": 400,
"detail": "request failed validation",
"errors": [{ "path": "title", "message": "String must contain at least 1 character(s)" }]
}type suffix |
Status | When |
|---|---|---|
bad-request |
400 | Malformed request |
validation |
400 | Body or query failed validation; see errors[] |
unauthorized |
401 | Missing, invalid, expired or revoked token |
plan_limit |
402 | Plan limit reached (repos; developers in MCP) |
forbidden |
403 | Missing scope or role |
not-found |
404 | Unknown resource or route |
conflict |
409 | State conflict (already exists, already closed, …) |
rate-limited |
429 | Message or question rate limit; see Retry-After |
unsupported-protocol |
400 | Hooks: X-Comity-Protocol is not 1 |
internal |
500 | Server error |
Billing and sign-in endpoints add their own types (billing_unavailable, already_subscribed, invalid_credentials, …).
Pagination
List endpoints take limit (default 50, max 200) and cursor, and return:
{ "items": [], "next_cursor": "MTI" }Pass next_cursor as cursor for the next page. next_cursor is null on the last page. Lists described as "unpaged" always return next_cursor: null. Comma-separated values work for multi-value filters (status=open,acknowledged).
Rate limits
- Messages: 20 per minute and 200 per day per user.
- Questions: 10 per minute and 100 per day per user.
Exceeding a limit returns 429 with Retry-After in seconds.
Selectors and references
- Repo:
owner/nameor a remote fingerprint such asgithub.com/acme/api. - Intent:
INT-12or12. Conflict:CF-9or9.
Hooks
| Method | Path | Scope | Notes |
|---|---|---|---|
| POST | /v1/hooks/evaluate |
hooks:evaluate |
What comity hook calls on each agent event. Optional header X-Comity-Protocol: 1. See Hook events. |
Account
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/me |
any | User, org (incl. data_mode), role, token |
| PATCH | /v1/org |
policy:write, owner, personal token |
{data_mode: "full"|"metadata_only"}. Returns {org, previous_data_mode, cleared_signatures}. Switching to metadata_only clears stored signature text. See Data modes. |
| GET | /v1/usage |
members:read |
Plan, limits, active developers and repos this month; per-developer list for admins |
Repositories and projects
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/repos |
repos:read |
Unpaged |
| POST | /v1/repos |
repos:write, admin |
{full_name, provider?: "github"|"gitlab"|"bitbucket"|"generic", default_branch?: "main", remotes?: []}. Repos are also registered automatically on the first hook event. 409 if it exists, 402 over the plan's repo limit. |
| GET | /v1/projects |
repos:read |
Unpaged |
| GET | /v1/projects/:id |
repos:read |
|
| POST | /v1/projects |
repos:write, admin |
{name, repos: [selector]} |
| POST | /v1/projects/:id/repos |
repos:write, admin |
{repos: [selector]} |
| DELETE | /v1/projects/:id/repos/:repo |
repos:write, admin |
:repo = repo id or URL-encoded owner/name |
| DELETE | /v1/projects/:id |
repos:write, admin |
204 |
Intents
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/intents |
intents:read |
?repo=&owner=me|<email>&status=live|<statuses>&limit=&cursor= |
| POST | /v1/intents |
intents:write |
Same body as intent_declare. 201 {intent, conflicts} |
| GET | /v1/intents/:ref |
intents:read |
|
| PATCH | /v1/intents/:ref |
intents:write |
Owner or admin. Same fields as intent_update's patch plus note. Returns {intent, conflicts} |
| POST | /v1/intents/:ref/close |
intents:write |
{outcome: "merged"|"abandoned"|"handoff", note?, handoff_to?} |
status=live means draft, active, blocked, in_review.
curl -X POST https://api.comity.so/v1/intents \
-H "Authorization: Bearer $COMITY_TOKEN" -H "Content-Type: application/json" \
-d '{"repo": "acme/api", "title": "Add invoice PDF export", "scope": {"paths": ["apps/api/src/invoices/**"]}}'Conflicts
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/conflicts |
conflicts:read |
?status=live|<statuses>&mine=true&repo=&intent=&limit=&cursor=. repo also matches cross-repo conflicts where the repo is the dependent side. |
| GET | /v1/conflicts/:ref |
conflicts:read |
|
| POST | /v1/conflicts/:ref/actions |
conflicts:write |
Participant or admin. {action, note?}; actions as in conflict_respond. Header X-Comity-Session: <uuid> acts as one of your agent sessions. |
| POST | /v1/conflicts/:ref/feedback |
conflicts:write |
{verdict: "useful"|"false_positive"|"too_late"}. 204 |
| POST | /v1/conflicts/:ref/override |
conflicts:write |
{reason}: 3–2,000 characters, trimmed; optional when the policy sets override.require_reason: false. Role must be in the policy's override.allowed_roles. |
status=live means open, acknowledged, resolving.
Messages and conversations
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/messages |
messages:read, personal token |
Your inbox. ?unread_only=true&limit=&cursor=. Returned messages are marked read. |
| POST | /v1/messages |
messages:write |
{to, body, subject?, requires_ack?, session_id?}; targets as in message_send. 201 |
| POST | /v1/messages/:id/ack |
messages:write |
{id, acked_at} |
| GET | /v1/conversations |
messages:read |
?kind=channel|direct&limit=(≤100)&cursor= |
| GET | /v1/conversations/:id |
messages:read |
|
| GET | /v1/conversations/:id/messages |
messages:read |
?limit=&before=<message id>; oldest → newest; next_cursor is the before for the older page |
| POST | /v1/conversations/:id/messages |
messages:write |
{body, requires_ack?}. In a direct thread only participants may post. |
Channels are conversations with kind: "channel": #team and one per repo. Reading conversations in the dashboard marks messages read without hiding them from your agents.
Coordination context
| Method | Path | Scope | Notes |
|---|---|---|---|
| POST | /v1/team-context |
intents:read |
Body as team_context |
| POST | /v1/scope-check |
intents:read |
Body as scope_check. Writes nothing. |
| GET | /v1/sessions |
intents:read |
?active=true&repo=&limit=&cursor=. {id, repo, user, tool, model, branch, status, intent, started_at, last_heartbeat_at} |
| GET | /v1/presence |
intents:read, personal token |
?repo=&session_id=&limit=(≤100). Teammates' live agents, never your own. |
Team memory
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/search |
personal token; read scope per kind | ?q=&kinds=&repo=&limit=(≤50, default 20). Kinds you can't read are skipped. |
| POST | /v1/questions |
messages:write |
Body as ask. 201 |
| POST | /v1/questions/:id/answers |
messages:write |
{answer, session_id?}. 201 |
Policies
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/policies |
policy:read |
?repo=. {repo_name, org, repo, effective} |
| PUT | /v1/policies |
policy:write, admin |
?repo=. {rules: {…}} replaces that scope's stored rules. See Policies. |
Members, invites and tokens
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/members |
members:read |
Paged |
| POST | /v1/members |
members:write, admin |
{email, name?, role?: "member"}. Only owners add owners or admins. |
| DELETE | /v1/members/:userId |
members:write, admin, personal token |
Revokes their tokens. Only owners remove owners or admins. 204 |
| GET | /v1/invites |
members:read, admin |
Pending invites. Unpaged. |
| POST | /v1/invites |
members:write, admin |
{role, email?}. Returns {id, url, role, email, email_sent, expires_at}. Expires after 7 days. |
| DELETE | /v1/invites/:id |
members:write, admin |
The invite's id. 204 |
| GET | /v1/tokens |
personal token | Admins see all, others their own. {id, name, kind, prefix, user_id, scopes, created_at, last_used_at, expires_at, revoked_at} |
| POST | /v1/tokens |
tokens:write, admin, personal token |
{name, kind?: "pat"|"service", user_email?, scopes?, expires_in_days? (≤3650)}. The plaintext token is returned once. |
| DELETE | /v1/tokens/:id |
own token, or admin | 204 |
# A service token for CI that reads conflicts
curl -X POST https://api.comity.so/v1/tokens \
-H "Authorization: Bearer $COMITY_TOKEN" -H "Content-Type: application/json" \
-d '{"name": "ci-conflict-report", "kind": "service", "scopes": ["conflicts:read", "intents:read"]}'Note
Creating tokens needs a token with tokens:write. Tokens from comity login don't have it (their scopes are listed under Security & data), and the dashboard's Team tab lists and revokes tokens but doesn't create them.
Events, stats and streaming
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/events |
events:read |
Audit log. ?type=<types>&subject=<uuid>&limit=&cursor=, newest first |
| GET | /v1/feed |
events:read |
Same as events plus refs with display names |
| GET | /v1/stats |
events:read |
?days=7|30|90&repo=. Totals, time saved, daily series, heatmap, top clients/members/repos |
| GET | /v1/stream |
events:read |
Server-Sent Events. See below. |
Event envelope (same as webhook payloads):
{
"id": "18342",
"type": "intent.declared",
"version": 1,
"occurred_at": "2026-10-19T12:00:00.000Z",
"org_id": "0199…",
"actor": { "kind": "session", "id": "0199…" },
"subject": { "kind": "intent", "id": "0199…" },
"payload": { "…": "…" }
}SSE stream
curl -N "https://api.comity.so/v1/stream?types=conflict.detected,hook.blocked&repos=acme/api" \
-H "Authorization: Bearer $COMITY_TOKEN"- Filters:
typesandrepos(comma-separated). - Resume with the
Last-Event-IDheader (or?last_event_id=): missed events are replayed. - A
: kacomment every 15 seconds keeps the connection alive. - On server restart you get
event: reconnectwithdata: {"after_ms":1000}.
Integrations
Owner or admin with a personal token. See Integrations.
| Method | Path | Notes |
|---|---|---|
| GET | /v1/webhooks |
Unpaged |
| POST | /v1/webhooks |
{url, event_types?: ["*"], description?}. 201 with secret (once) |
| DELETE | /v1/webhooks/:id |
204 |
| POST | /v1/webhooks/:id/test |
202 {delivery_id}; re-enables a disabled endpoint |
| GET | /v1/webhooks/:id/deliveries |
?limit= |
| GET | /v1/integrations/slack |
Connection status and last post |
| PUT | /v1/integrations/slack |
{webhook_url}; null disconnects |
| POST | /v1/integrations/slack/test |
202 {notification_id} |
| GET | /v1/github/app |
Apps, installations, install links |
| GET | /v1/github/app/manifest |
?owner=&format=json|html. Starts the GitHub App manifest flow |
Billing
| Method | Path | Who | Notes |
|---|---|---|---|
| GET | /v1/billing |
owner, admin, billing | Plan and subscription |
| POST | /v1/billing/checkout |
owner, billing | {plan: "team"|"business"} → {url} |
| POST | /v1/billing/portal |
owner, billing | → {url} |
Health
| Method | Path | Notes |
|---|---|---|
| GET | /healthz |
{"status":"ok"}. No auth. |
| GET | /readyz |
{"status":"ready"|"unavailable","checks":{…}}; 503 when not ready. No auth. |
MCP
GET|POST|DELETE /mcp: the MCP Streamable HTTP endpoint, bearer token with mcp:tools. See MCP tools.