Skip to main content
Linear access tokens last 24 hours, and Linear’s refresh tokens rotate, with a 30-minute grace period in which a used one is still accepted. It documents scopes as a comma-separated list. The OAuth application is registered at linear.app/settings/api: Register a Linear OAuth application.

The server

linear_server.py
scope_separator is the comma Linear documents. dead_grant_errors is how Linear says a grant is gone: Refresh behaviour. revocation is how a user is disconnected: Disconnecting.

Scopes

Each tool declares what an OAuth grant needs for it, from Linear’s list: read is on every grant (“this scope will always be present”), so an agent that only files issues asks to change nothing else:
linear_scopes.py
Reading webhooks takes admin, though creating and deleting one takes only write; Linear’s own refusal says so, and scripts/live_linear_scopes_check.py asks it for every tool. A personal API key carries the account’s full access and takes no scopes. A call the grant does not cover raises InsufficientScope with the scope Linear names, the narrowest the tool lists first: “Invalid scope: write or comments:create required” gives needed == ['comments:create']. Linear sends it with 400 to a mutation and 200 to a query.

Connecting a user

linear_users.py
The client reads the stored grant before every refresh rather than holding a copy of the refresh token, because Linear rotates it: a copy goes stale the moment another process refreshes. More than one process has the rest.

Refresh behaviour

Linear refuses a refresh token that is spent, past its 30-minute grace, or unknown with invalid_request (“Invalid refresh token”) in a 400, where RFC 6749 says invalid_grant. dead_grant_errors declares it, so a disconnected user is asked about once a minute and the error’s reauthorize is true. Linear uses the same code for a missing parameter, which a refresh from Charter never has.

A pasted personal key

A user who pastes a personal API key instead gets a StaticTokenProvider(key) from the same linear_for. Linear wants a personal key with no scheme and an OAuth token as Bearer, and refuses each in the other’s shape. The pack tells them apart by Linear’s lin_api_ prefix, so the two kinds of user can share one configuration.

Disconnecting

The SubjectProvider the pack is configured with builds the user’s client from your store and revokes the grant, in one call:
linear_disconnect.py
The declaration sends the refresh token to oauth/revoke, with no client credentials and with token_type_hint=refresh_token. Linear’s docs offer the hint “to help the server identify the token”; measured live, it is needed: without it, a live refresh token gets 401 Token not found and the grant stays. With it, Linear answers 200 and the access token is refused at once. A token already revoked gets 401 (“Token has already been revoked.”), where Linear’s docs give “token was already revoked” as their example of a 400, so both count as gone and revoke() returns False. A pasted personal key was made by the user, and only they can delete it, in Linear’s settings. Delete your row.

Where the rest is