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).

ScopeAllows
tasks:readRead tasks: list, get, full-text search and the today list.
tasks:writeCreate, update and restore tasks, batch-update them, and change or skip the rule of a recurring series.
tasks:deleteSoft-delete tasks (30-day retention).
projects:readRead projects and the status lists of projects, teams and workspaces.
projects:writeCreate, update and restore projects, use the agent inbox, change the status lists of projects, teams and workspaces, and reopen an archived team.
projects:deleteSoft-delete projects.
pomodoro:readRead pomodoro / work-session history, and preview where a session can be moved.
pomodoro:writeStart, update and restore pomodoro sessions, and move a session, decide a move request or undo a move.
pomodoro:deleteSoft-delete pomodoro sessions.
comments:readRead task comments.
comments:writeCreate, edit and restore task comments.
comments:deleteSoft-delete comments.
tags:readRead tags.
tags:writeCreate, rename and restore tags.
tags:deleteSoft-delete tags.
profile:readRead the user profile.
profile:writeUpdate whitelisted profile fields.
earnings:readRead 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.

TierRequests / hourCounts
read1000GET, HEAD
write200POST, PATCH, PUT
delete50DELETE

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.

PlanAgent API
FreeNot included
ProIncluded
TeamIncluded

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 with upgrade_url), with type …/errors/upgrade_required.
  • MCP /api/mcp accepts initialize, ping and notifications, then answers every other request with HTTP 200 and a JSON-RPC error whose data.type is upgrade_required (code -32003, with data.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.