init.Tasks openapi.json inittasks.com

tokens, the approval flow, scopes

Authentication

Every request carries a bearer token:

http
Authorization: Bearer itk_7mQ2xLp9Va4Nk1Zr8Ts6Ye3Wu0Bd5Mg2Hj7Cq4Ln1Xv8

There is no API key, no client-credentials flow, and no way to act without a user. That is a consequence of the encryption design: a token that no user authorised would have no key, and a request with no key can read nothing. See encryption.

Personal access tokens

For your own scripts. Created in the app — Settings → account → encryption & devices → api keys — because only a device that already holds the encryption key can hand it to a new credential.

OAuth 2.1

For an app other people will use. Registration is open — RFC 7591 dynamic client registration, no review process, no waiting.

bash
curl -s https://api.inittasks.com/oauth/register \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "client_name": "Standup Bot",
    "redirect_uris": ["https://standup.example/callback"],
    "token_endpoint_auth_method": "none"
  }'

Then the standard flow, with PKCE (S256) required and resource mandatory:

text
GET https://api.inittasks.com/oauth/authorize
      ?response_type=code
      &client_id=<your id>
      &redirect_uri=https://standup.example/callback
      &scope=todos:read todos:write
      &resource=https://api.inittasks.com
      &code_challenge=<S256 of your verifier>
      &code_challenge_method=S256
      &state=<your state>

resource (RFC 8707) is not optional. A token is minted for one audience and refused at the other, so a token for the REST API cannot be replayed at the MCP server.

Approval happens on the user's device

There is no password form in your browser window. The user sees a nine-digit code, opens init.Tasks on a device they already trust, and approves there.

What they are shown, in this order: the callback address first, then your app's name as a subtitle. Names are self-declared and unverified — the host is the fact. There is no directory, no badge, and no review; identity is the address you registered.

They can also grant less than you asked for. If you request todos:read todos:write and they grant read, your token comes back with scope=todos:read. Read the granted scope from the token response; do not assume you got what you asked for.

Refresh tokens rotate

Every refresh returns a new refresh token and invalidates the old one, with a 30-second grace window for a response you did not receive.

⚠ Reuse of an already-rotated refresh token revokes the whole grant. That is the point: replay means the token leaked. Store the newest one atomically, and never run two refreshes concurrently.

A grant needs re-approval after 365 days.

Scopes

scope grants
todos:read todos:write to-dos
containers:read containers:write areas, projects, subprojects
inbox:read inbox:write capture
tags:read tags:write tags
attachments:read attachments:write attachments, including file bytes
filters:read filters:write saved filters
settings:read settings:write week start
sessions:read sessions:write keys, grants and sessions
webhooks:write webhooks — including reading them
tasks:read tasks:write umbrellas over the content scopes

Two rules that surprise people, both deliberate:

A write scope implies its read scope: todos:write gives you todos:read.

Everything ends at once

Any of these disconnects every token, key, session and grant:

There is no partial state to recover from and no way to detect it in advance. Handle unauthorized and grant_revoked by sending the user through approval again.