OAuth 2.0 authentication
Linear supports OAuth2 authentication, which is recommended if you’re building applications to integrate with Linear.
It is highly recommended you create a workspace for the purpose of managing the OAuth2 Application, as each admin user will have access.
All OAuth2 applications were migrated to the new refresh token system on April 1, 2026.
Create an OAuth2 application
Create a new OAuth2 Application and configure the redirect callback URLs to your application.
Redirect user access requests to Linear
When authorizing a user to the Linear API, redirect to an authorization URL with correct parameters and scopes:
GET https://linear.app/oauth/authorize HTTP/1.1Name | Description |
|---|---|
| (required) Client ID provided when you create the OAuth2 Application |
| (required) Redirect URI |
| (required) Expected response type |
| (required) Comma separated list of scopes:
See App authentication for agent-specific scopes such as |
| (optional, recommended) Prevents CSRF attacks and should always be supplied. Read more about it here. |
| (optional) The consent screen is displayed every time, even if all scopes were previously granted. This can be useful if you want to give users the opportunity to connect multiple workspaces. |
| Define how the OAuth application should create issues, comments and other changes:
|
PKCE
Linear supports the PKCE flow. To use this flow, you'll need to include two additional parameters as part of your /authorize request:
Name | Description |
|---|---|
| (required) Code challenge you generated |
| (required) Either |
Example
GET https://linear.app/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URL&state=SECURE_RANDOM&scope=read HTTP/1.1
GET https://linear.app/oauth/authorize?client_id=client1&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Fcallback&response_type=code&scope=read,write HTTP/1.1
GET https://linear.app/oauth/authorize?client_id=client1&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Fcallback&response_type=code&scope=read,write&code_challenge=challenge&code_challenge_method=S256 HTTP/1.1Handle the redirect URLs you specified in the OAuth2 Application
Once the user approves your application they will be redirected back to your application, with the OAuth authorization code in the URL params.
Any state parameter you specified in step 2 will also be returned in the URL params and must match the value specified in step 2. If the values do not match, the request should not be trusted.
Example
GET https://example.com/oauth/callback?code=9a5190f637d8b1ad0ca92ab3ec4c0d033ad6c862&state=b1ad0ca92 HTTP/1.1Exchange code for an access token
After receiving the code, you can exchange it for a Linear API access token:
POST https://api.linear.app/oauth/token HTTP/1.1Pass parameters in body as URL-encoded form submission, where the Content-Type header must be application/x-www-form-urlencoded.
Parameter | Description |
|---|---|
| (required) Authorization code from the previous step |
| (required) Same redirect URI which you used in the previous step |
| (required) Application's client ID |
| (required) Application's client secret |
| (required) |
PKCE
If you are using the PKCE flow, the parameters required by the /token endpoint are different:
Name | Description |
|---|---|
| (required) Authorization code from the previous step |
| (required) Same redirect URI which you used in the previous step |
|
|
| (optional) Application's client secret |
| (required) The code verifier for the PKCE request that you originally generated before the authorization request |
| (required) |
Response
After a successful request, a valid access token and refresh token will be returned in the response. The access token is valid for 24 hours and will need to be refreshed when it expires. Example response:
{
"access_token": "00a21d8b0c4e2375114e49c067dfb81eb0d2076f48354714cd5df984d87b67cc",
"token_type": "Bearer",
"expires_in": 86399,
"scope": "read write",
"refresh_token": "sz0c8ffy95zj2ff6bh1hiausauw3dbfsu4gly1z4p49b5odqv8l7owunb654vg1f",
}Note: OAuth apps created prior to Dec 1, 2023 will instead return scope as an array of strings in the token response.
Refresh an access token
You'll need to use the refresh token you receive alongside your access token to retrieve a new access token when the previous one expires.
For authorization, you have two options:
- Use HTTP basic authentication by passing a Base64-encoded
client_id:client_secretstring as an authorization header:Authorization: Basic <base64(client_id:client_secret)> - Pass
client_idandclient_secretas parameters
If your access token was generated using PKCE, you can simply pass client_id as a parameter.
Requests to consume a refresh token and obtain a new one have a 30-minute grace period to allow for network errors. If you make a request with a valid refresh token but do not receive the new one in the response, you can replay the original request to retrieve the new refresh token up to 30 minutes after the original request.
POST https://api.linear.app/oauth/token HTTP/1.1Pass parameters in body as URL-encoded form submission, where the Content-Type header must be application/x-www-form-urlencoded.
Parameter | Description |
|---|---|
| (required) Refresh token from the previous step |
| (required) |
| (optional if you're using HTTP basic authentication) Application's client ID |
| (optional if you're using HTTP basic authentication or refreshing a token generated using PKCE) Application's client secret |
Response
After a successful request, a new valid access token and a new refresh token will be returned in the response:
{
"access_token": "fxra4u0msw3bagb9rdn2i641bs52m9zo8ksoxljouygcu31nh8s2jf8fygbepy16",
"token_type": "Bearer",
"expires_in": 86399,
"scope": "read write",
"refresh_token": "qjmj51q8f8fnwe188702jarfqxwhdy6r5ivqy4yjuhw2crubm5e7nyu84un3marx",
}Make an API request
Once you have obtained a valid access token, you can make a request to Linear's GraphQL API. You can initialize the Linear Client with the access token:
const client = new LinearClient({ accessToken: response.access_token })
const me = await client.viewerOr pass the token as an authorization header: Authorization: Bearer <ACCESS_TOKEN>
curl https://api.linear.app/graphql \
-X POST \
-H "Content-Type: application/json" \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
--data '{ "query": "{ viewer { id name } }" }' \Revoke an access token
To revoke a user’s access, call the revocation endpoint and include the token to be revoked in the token form field of the request body.
If available, include token_type_hint (access_token or refresh_token) to help the server identify the token.
POST https://api.linear.app/oauth/revoke HTTP/1.1Note: For backwards compatibility, we still accept revocation via the Authorization header or the legacy access_token / refresh_token form fields. New integrations should use the token field. The token field must not be combined with legacy fields.
Response
Expected HTTP status:
200- token was revoked400- unable to revoke token (e.g. token was already revoked)401- unable to authenticate with the token
Migrate to using refresh tokens
To ease the transition to refresh tokens for OAuth2 applications that weren’t always using them, we've added a temporary endpoint to migrate any old, long-lived access token to a new, short-lived access token with a refresh token.
POST https://api.linear.app/oauth/migrate_old_token HTTP/1.1Pass parameters in body as URL-encoded form submission, where the Content-Type header must be application/x-www-form-urlencoded.
Parameter | Description |
|---|---|
| (required) Existing long-lived access token |
| (required) Application's client ID |
| (required) Application's client secret |
Client credentials tokens
We support the client_credentials grant type for OAuth2 apps that use tokens for server-to-server communication and cannot support a user-initiated OAuth flow involving refresh tokens.
You must first toggle on client credentials tokens for your OAuth2 app when creating or editing the app in Linear
The token generated using this grant type will be an app actor token that has access to all public teams in the workspace and is valid for 30 days. You can learn more about app actor tokens here.
The app user's team access can be modified through the app details page for your app at any point after the token is generated. Since there is no refresh token paired with this access token, your server is expected to fetch a new token if it receives a 401 error when making a request with the previous token.
Every OAuth2 app can only have one active client credentials token at a time since it is an app token. If you request a new client credentials token while you still have an active one, we will invalidate the currently active token and return a new token with a 30-day validity.
For increased security, we will also invalidate your app's client credentials token if its client secret is rotated.
Request
For authorization, you have two options:
- Use HTTP basic authentication by passing a Base64-encoded
client_id:client_secretstring as an authorization header:Authorization: Basic <base64(client_id:client_secret)> - Pass
client_idandclient_secretas parameters
POST https://api.linear.app/oauth/token HTTP/1.1Name | Description |
|---|---|
| (required) |
| (required) Comma-separated list of scopes |
| (optional if you're using HTTP basic authentication) Application's client ID |
| (optional if you're using HTTP basic authentication) Application's client secret |
Response
After a successful request, a new valid access token will be returned in the response:
{
"access_token": "fxra4u0msw3bagb9rdn2i621bs52m9zo8ksoxljouygcu31nh8s2jf8fygbepy16",
"token_type": "Bearer",
"expires_in": 2591999,
"scope": "read write",
}If your OAuth2 app does not have client credentials tokens enabled, you will receive an error response:
{
"error":"Error",
"error_description":"Client does not support the client_credentials grant type"
}