Restivity API
Quick Start
This page is the reference. The Redoc viewer below is generated from the same schemas the API validates with; the rate limits and the plan requirement in sections 5 and 6 are read from the code that enforces them. For the step-by-step connection flow see Connect an agent; for the OAuth details an agent reads on its own, /auth.md and /llms.txt.
1. Authentication: OAuth 2.0 + PKCE
Every /api/v1/* endpoint requires a Bearer token from the standard authorization_code grant with PKCE (RFC 7636). There is no API-key flow. Discover the OAuth server at /.well-known/oauth-authorization-server; register a client at POST /api/oauth/register, or use the URL of your client's Client ID Metadata Document as client_id (what the server fetches and requires is in /auth.md); exchange the code at POST /api/oauth/token. A document that lists none is a public client; one that declares private_key_jwt without none is confidential and signs a client assertion (RFC 7523) at the token and revocation endpoints: aud the issuer or the token endpoint, a jti used once, an exp at most 600 seconds ahead, signed with RS256, PS256 or ES256 by a key of its jwks_uri (the rule in /auth.md). Access tokens are signed JWTs (alg: ES256) verifiable with /api/oauth/jwks and expire in 1 hour. Refresh tokens rotate on each refresh; replaying a used refresh token revokes ALL of the client's tokens (RFC 6749 §10.4). A client_name that imitates Claude, Anthropic, ChatGPT, OpenAI, Cursor or Restivity is refused unless every redirect URI is that vendor's or a native app's loopback callback; a field sent as null counts as omitted (the rule in /auth.md).
An access token stops working when it is revoked: when the user disconnects the app, when you revoke it (or the refresh token it was issued with) at POST /api/oauth/revoke, when its refresh token is replayed, when the authorization code it came from is exchanged a second time, when an administrator suspends the account or disables your client. The API then answers 401 invalid_token within 60 seconds of the revocation, never later. On that 401, refresh; if the refresh is refused with invalid_grant, run the authorization flow again.
2. Scope model
Scopes are least-privilege: resource:action where resource ∈ {tasks, projects, pomodoro, comments, tags} and action ∈ {read, write, delete}, plus profile:read, profile:write and earnings:read. Account deletion is not an agent scope (there is no profile:delete).
| Scope | Allows |
|---|---|
tasks:read | Read tasks: list, get, full-text search and the today list. |
tasks:write | Create, update and restore tasks, batch-update them, and change or skip the rule of a recurring series. |
tasks:delete | Soft-delete tasks (30-day retention). |
projects:read | Read projects and the status lists of projects, teams and workspaces. |
projects:write | Create, update and restore projects, use the agent inbox, change the status lists of projects, teams and workspaces, and reopen an archived team. |
projects:delete | Soft-delete projects. |
pomodoro:read | Read pomodoro / work-session history, and preview where a session can be moved. |
pomodoro:write | Start, update and restore pomodoro sessions, and move a session, decide a move request or undo a move. |
pomodoro:delete | Soft-delete pomodoro sessions. |
comments:read | Read task comments. |
comments:write | Create, edit and restore task comments. |
comments:delete | Soft-delete comments. |
tags:read | Read tags. |
tags:write | Create, rename and restore tags. |
tags:delete | Soft-delete tags. |
profile:read | Read the user profile. |
profile:write | Update whitelisted profile fields. |
earnings:read | Read earnings summaries (worked hours and amounts by project, client, and period). |
The effective scope on a token is the intersection of (a) what the user consented to and (b) what the registered client requested. A call outside it: REST answers 403 insufficient_scope (problem+json with scope_required, and a WWW-Authenticate: Bearer error="insufficient_scope" challenge that names the scope); an MCP tool answers a tool error (isError) whose code is insufficient_scope, with required_scope.
3. Pagination convention
List endpoints paginate via opaque cursor. Request: ?limit=<1-100, default 50>&cursor=<opaque>. Response wraps rows in { data: […], next_cursor: string | null }. next_cursor is null when the current page is the last one. The nullability IS the "has-more" signal, no separate boolean. Filter via documented query parameters; multiple filters AND together.
4. Error shape
Every error response is RFC 9457 problem-details (Content-Type: application/problem+json). The body has { type, title, status, detail, instance }, plus scope_required on 403 and retry_after on 429. type is the issuer origin + /errors/<slug>: 400 invalid_request, 401 invalid_token, 403 insufficient_scope, 404 not_found (also for soft-deleted), 422 unprocessable, 429 rate_limited (with Retry-After), 500 internal, 503 service_unavailable.
5. Rate limits
Per user, per UTC hour, three tiers by HTTP verb. The values below are the ones this deployment enforces.
| Tier | Requests / hour | Counts |
|---|---|---|
read | 1000 | GET, HEAD |
write | 200 | POST, PATCH, PUT |
delete | 50 | DELETE |
The token and revocation endpoints (POST /api/oauth/token, POST /api/oauth/revoke) share one gate: 600 requests per source IP per minute, both counted together. Over budget it answers 429 with the OAuth error slow_down and Retry-After: wait, then retry the same request. Each authenticated client also has a budget across both endpoints: 6000 requests per client per hour. A confidential client past it is refused the same way, with Retry-After to the end of the hour; the refused request consumes nothing, so the same refresh token or code still works on the retry. A public client (PKCE, no secret) is never refused on that count; each person it acts for has 300 requests per user per hour on the token endpoint, counted only for a request that carries a valid code or refresh token, and refused the same way past it. Revocation never counts toward it.
Every agent-authenticated response carries X-RateLimit-Tier as <tier>:<count>/<limit>. Over budget, the API answers 429 problem-details (type …/errors/rate_limited) with Retry-After (seconds to the top of the hour) and retry_after in the body. Back off until then; the counter resets every hour.
6. Plan requirement
Connecting an external agent (REST /api/v1/* and MCP /api/mcp) is a paid feature. A token is minted for any account; the entitlement is checked on every call.
| Plan | Agent API |
|---|---|
| Free | Not included |
| Pro | Included |
| Team | Included |
The connection needs a Restivity Pro or Team plan; a paid Team seat also unlocks it on a free personal plan. A free account can sign in, and the tools then answer that its plan does not include agent access. Without it, the two surfaces answer differently:
- REST /api/v1 answers HTTP 403
upgrade_required(application/problem+json withupgrade_url), withtype …/errors/upgrade_required. - MCP /api/mcp accepts
initialize,pingand notifications, then answers every other request with HTTP 200 and a JSON-RPC error whosedata.typeisupgrade_required(code-32003, withdata.upgrade_url), never an HTTP 403.
Show the upgrade link; do not restart the OAuth flow. The upgrade_url is the /pricing page; neither answer is a 401.
7. Beyond CRUD
Every resource exposes list / create / get / update / delete / restore. These operations do more; each has an MCP tool sibling with the same input and output.
POST /focus/finish: Finish working on a task in one call: close the timer, comment, set the status. (tasks:write)POST /focus/start: Start working on a task: in progress + one work session, in one call. (tasks:write)GET /profile: Fetch the authenticated user profile. (profile:read)PATCH /profile: Update writable profile fields. (profile:write)DELETE /project-statuses/{id}: Delete a custom status (hard delete). (projects:write)PATCH /project-statuses/{id}: Rename, recolor or recategorize a custom status. (projects:write)GET /projects/{id}/statuses: List a project's custom statuses. (projects:read)POST /projects/{id}/statuses: Add a custom status to a project. (projects:write)POST /projects/{id}/statuses/customize: Customise: the project receives a COPY of the palette it follows. (projects:write)POST /projects/{id}/statuses/follow: Follow again: the project drops its own statuses. (projects:write)POST /projects/{id}/statuses/reorder: Reorder a project's custom statuses. (projects:write)POST /projects/{id}/statuses/restore-base: Restore the base columns of a project: not available, Follow again is the way back. (projects:write)POST /projects/agent-inbox: The workspace's agent inbox: returned, chosen, or created on the person's request. (projects:write)PATCH /tasks/{id}/recurrence/rule: Edit a recurring master's rule (materialized model). (tasks:write)POST /tasks/{id}/recurrence/skip: Skip the occurrence a reminder is on (reminder_only model). (tasks:write)POST /tasks/batch-update: Apply one patch to up to 50 tasks atomically. (tasks:write)POST /tasks/search: Full-text search tasks. (tasks:read)GET /tasks/today: Today's focus list. (tasks:read)DELETE /team-statuses/{id}: Delete one team status (hard delete). (projects:write)PATCH /team-statuses/{id}: Rename, recolor or recategorize one team status. (projects:write)GET /teams: List the archived teams the caller can see. (projects:read)POST /teams/{id}/archive: Archive a team. (projects:write)GET /teams/{id}/statuses: List the team's custom statuses. (projects:read)POST /teams/{id}/statuses: Add a custom status to the team palette. (projects:write)POST /teams/{id}/statuses/customize: Customise: the team receives a COPY of the palette it follows. (projects:write)POST /teams/{id}/statuses/follow: Follow again: the team drops its own statuses. (projects:write)POST /teams/{id}/statuses/reorder: Reorder the team's custom statuses. (projects:write)POST /teams/{id}/statuses/restore-base: Restore the base columns: the team's statuses are removed (two calls, the second confirmed). (projects:write)POST /teams/{id}/unarchive: Reopen an archived team. (projects:write)POST /work-sessions/move: Move concluded work sessions (hours) to another project, across workspaces. (pomodoro:write)GET /work-sessions/move-destinations: Where hours can be moved: the projects you can track time in, in every workspace. (pomodoro:read)POST /work-sessions/move/preview: What moving these sessions would do: the price before and after, and who decides. (pomodoro:read)POST /work-sessions/move/requests/{id}/decide: Approve or decline a pending move of someone’s hours (origin owner/admin). (pomodoro:write)GET /work-sessions/move/undo: The move of hours an undo would put back, and whether it can. (pomodoro:read)POST /work-sessions/move/undo: Undo a move of hours: your own, or one you approved. (pomodoro:write)DELETE /workspace-statuses/{id}: Delete one workspace status (hard delete). (projects:write)PATCH /workspace-statuses/{id}: Rename, recolor or recategorize one workspace status. (projects:write)GET /workspaces/{id}/statuses: List the workspace's custom statuses. (projects:read)POST /workspaces/{id}/statuses: Add a custom status to the workspace palette. (projects:write)POST /workspaces/{id}/statuses/reorder: Reorder the workspace's custom statuses. (projects:write)POST /workspaces/{id}/statuses/restore-base: Restore the base columns: the workspace's statuses are removed (two calls, the second confirmed). (projects:write)
Same handlers serve REST, MCP (/api/mcp), and the in-browser WebMCP read-only tools: see /.well-known/mcp/server-card.json for the MCP transport details.
Interactive OpenAPI 3.1 reference rendered with Redoc.