The API, MCP and connected apps
Three ways software reaches a Projectri workspace — the REST API, the MCP server for coding agents, and third-party apps a member authorises.

What it is
Three surfaces, one access model. The REST API at /api/v1 is for your own code. The MCP server at /api/mcp is for coding agents — Claude Code, Cursor, Codex and anything else that speaks the protocol. OAuth apps are for software somebody else wrote, which a member of your workspace authorises.
All three authenticate with a bearer token, and every one of those tokens has the same ceiling: the permissions of the person it belongs to, recomputed on every request.
How to get there
Everything starts at Admin → Security:
- Connect IDE picks your editor, mints a scoped token and prints the exact configuration with the token already in it.
- API tokens is the same mint with the full permission picker.
- Connected apps lists every third-party application authorised in this workspace, who approved it and when it was last used.
Minting or revoking a token needs integration.api_token, which ships granted to ADMIN and can be delegated without promoting anyone. Merely seeing which applications can read your workspace needs only integration.view — an auditor asking "what has access to our data" should not need the key that grants it.
The REST API needs the platform.public_api entitlement, which starts at the Team plan.
How to use it
Mint a token
- Admin → Security → API tokens, or Connect IDE for one of three presets: read-only, read and write, or everything including financials.
- The plaintext starts
pri_and is shown once. Only its hash is stored. - A preset arrives already narrowed to your own access, with anything dropped named on the button. You cannot grant a scope you do not hold yourself.
- Revoking is a timestamp, not a delete — an audit trail that loses the row it refers to cannot say what the token did before it was killed.
Call the REST API
Authorization: Bearer pri_…. Header only; a query parameter would leak the secret into access logs andRefererheaders.GET /api/v1lists the resources: **projects, tasks, time-entries, expenses, clients, milestones, comments, users**. Each names the scope it reads and writes with.GET /api/v1/openapi.jsonis the full specification, public and unauthenticated — evaluating whether to integrate should not need a credential.
The conventions are identical on every endpoint, by construction:
- Pagination is cursor-based, never offset.
limitdefaults to 50 and caps at 200; an over-large limit is clamped, not rejected. There is no total — an exact count on a filtered table is a second full scan on every page. - **
?fields=id,name,status** prunes the response.idis always kept. - **Money is always
{ amount, currency }** with the amount in integer minor units. Never a float, never a bare number. - Dates are ISO 8601. An unparseable one is an error rather than a silently ignored filter.
- Errors are RFC 7807 problem documents with
application/problem+jsonand a stablecode, so you can tell one of our errors from a proxy's HTML 502. - **Every response carries
X-Request-Id.** Send your own and it is echoed back, so one id traces across both systems.
Connect a coding agent over MCP
- Admin → Security → Connect IDE, pick the editor, copy what it prints.
- The endpoint is
POST https://<your-host>/api/mcp, streamable HTTP and stateless. - Prefer the environment-variable form. A
.mcp.jsonat a repo root gets committed, and a token in it is a credential that acts as a real member of your workspace, in version control. - The tool list is filtered per request to what the token may actually call, so an agent is never shown a tool it can only fail at.
The server exposes 49 tools — 39 reads and 10 writes — curated around whole questions rather than mirrored from the roughly 120 HTTP routes, because every tool definition costs the model context on every turn.
Reads cover projects, tasks, sprints, notes, documents, the knowledge base, this documentation, clients, estimates, contracts and contract burn, the sales pipeline and its forecast, people, skills, availability, staffing scenarios, demand forecasting, bookings, dashboards and reports, and four finance reads — utilisation, project profitability, work in progress and finance exceptions.
Writes: create a task, update a task, comment on a task, move a task to a sprint, log a time entry, create a note, create a project, create a milestone, create a booking, create an estimate. Nothing deletes. To retire a task, set its status to Cancelled.
Four resource types (projectri://project/{id} and the same for tasks, notes and documents) and three prompts — standup_summary, sprint_review, project_health — are available for hosts that support them.
Authorise a third-party app
- The application sends you to a consent screen naming itself and exactly what it is asking for.
- **The scopes recorded are the request intersected with your own live permissions**, and the screen shows which keys that drops. An app asking for financial reporting because some of its users are finance admins is still installable by everybody else — it just gets less.
- A narrowing to nothing is refused. A token that authenticates and can do nothing is an install that looks connected and fails every call.
- Review and revoke at Admin → Security → Connected apps. The list is workspace-wide, not per person, with the approver's name against each row and a live count of outstanding credentials — how many are actually out there is what tells you whether a disconnect is urgent.
What it affects
- A token is not an identity. It acts as the person who created it, and its access is recomputed on every request as *the owner's live permissions intersected with the token's stored scopes*. Demote the owner, suspend them or remove them from the workspace and the token narrows or dies on its next call. Stored scopes are a ceiling, never a grant.
- A token with no scopes authenticates and can do nothing. That is not a bug; it is the empty intersection.
- Writes fire everything the screen fires — the notification, the timeline entry, the watcher subscription, the automation rule. A task created by an agent is indistinguishable in effect from one created by hand, which is the point, and the reason the timeline marks it.
- The activity feed keeps the person in bold and adds a byline reading *via API token "…"*. Work should not appear in a timeline with nothing behind it.
- Every API call is metered against
platform.api_callsand appears in the workspace's request log, including the ones that fail — a 404 costs what a 200 costs, and an integrator asking why their calls stopped is answered from the same log as every other question about them. - Rate limit: 300 requests per minute per token, published in
X-RateLimit-Limiton every response. MCP and REST share one bucket, because they are the same credential hitting the same database.
Reading a refusal
| Status | code | Means |
|---|---|---|
| 401 | missing_token, invalid_token, expired_token, revoked_token | the credential |
| 403 | no_membership, insufficient_scope | mint a broader token, or ask an admin |
| 403 | revoked_grant | the workspace disconnected the app; send the user back through consent |
| 402 | plan_required | nothing a token can fix — the workspace has not bought it |
| 429 | quota_exceeded | the month's API calls are spent |
| 429 | rate_limited | slow down |
insufficient_scope and plan_required are deliberately different codes. Both would otherwise arrive as "access denied" and send an integrator on a long hunt through scope settings for a commercial problem.
On mobile
There is no token management in the phone app. Everything here is one-time configuration done at a desk, usually with a vendor console or an editor open beside it.
Limits and gotchas
- **Assigning work to somebody else needs
task.assignon top.** Assigning to yourself does not — picking up your own work through an agent should not need a permission that picking it up in the interface does not. - A read-scoped token cannot write anything. This is marginally stricter than the app in one place: the comment route has no role guard, so a VIEWER can comment in the interface but a VIEWER's token cannot.
- **Use
idempotency_keyon anything that creates a row.** A retry with the same key returns the original result and does nothing else; reusing a key with different arguments is an error, not a replay. Keys are scoped per token and live 24 hours. - Ids are UUIDs, not the
APO-42identifier — that is a display name. - Private notes stay private. Workspace scope alone is not enough for documents: a private note is readable only by its author, explicit share targets, and admins, exactly as in the interface.
- The workspace IP allowlist is not enforced on the API. It is not enforced on the browser path either; wiring it for tokens alone would be a silent inconsistency.
- The rate limit is honest but shared. It is enforced by a shared counter, and if that counter is unreachable the request is allowed rather than refused — a limiter that starts refusing everybody when the database hiccups is an outage with a security label on it.
- **
.well-knowndiscovery is served for OAuth clients** at/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource. PKCE with S256 is mandatory;plainis not offered.
Related
- Security Center — where tokens are minted and apps are reviewed.
- MCP servers — the other direction, Projectri calling out.
- How permissions work — the ceiling every token is under.
- Plans and entitlements —
platform.public_apiand the call quota. - Integrations — connectors we wrote, rather than software you drive.