# Table of Contents - [Pagination | Linear API](#pagination-linear-api) - [Working with the GraphQL API | Linear API](#working-with-the-graphql-api-linear-api) - [Deprecations | Linear API](#deprecations-linear-api) - [Rate limiting | Linear API](#rate-limiting-linear-api) - [Webhooks | Linear API](#webhooks-linear-api) - [Filtering | Linear API](#filtering-linear-api) - [Attachments | Linear API](#attachments-linear-api) - [OAuth 2.0 Authentication | Linear API](#oauth-2-0-authentication-linear-api) - [OAuth Actor Authorization | Linear API](#oauth-actor-authorization-linear-api) - [File Storage Authentication | Linear API](#file-storage-authentication-linear-api) - [Getting Started | Linear API](#getting-started-linear-api) - [Errors | Linear API](#errors-linear-api) - [Fetching & Modifying Data | Linear API](#fetching-modifying-data-linear-api) - [How to upload a file to Linear | Linear API](#how-to-upload-a-file-to-linear-linear-api) - [Webhooks | Linear API](#webhooks-linear-api) - [Brand Guidelines | Linear API](#brand-guidelines-linear-api) - [Introduction | Linear API](#introduction-linear-api) - [Getting Started | Linear API](#getting-started-linear-api) - [Advanced Usage | Linear API](#advanced-usage-linear-api) - [Working with the GraphQL API | Linear API](#working-with-the-graphql-api-linear-api) - [Migrating from 1.x to 2.x | Linear API](#migrating-from-1-x-to-2-x-linear-api) - [How to upload a file to Linear | Linear API](#how-to-upload-a-file-to-linear-linear-api) - [OAuth 2.0 Authentication | Linear API](#oauth-2-0-authentication-linear-api) - [Managing Customers | Linear API](#managing-customers-linear-api) - [How to create new issues using linear.new | Linear API](#how-to-create-new-issues-using-linear-new-linear-api) - [Brand Guidelines | Linear API](#brand-guidelines-linear-api) --- # Pagination | Linear API * * * All list responses from queries return paginated results. We implement Relay style cursor-based pagination model with `first`/`after` and `last`/`before` pagination arguments. To simply query get first 10 issues for your organization: Copy query Issues { issues(first: 10) { edges { node { id title } cursor } pageInfo { hasNextPage endCursor } } } To query the next 10, simply pass the value of `pageInfo.endCursor` as `after` parameter for the next request. You can do this as long as `pageInfo.hasNextPage` return true and you'll paginate through all the values in the collection. The first 50 results are returned by default without query arguments. Pagination also supports simpler syntax where instead of edges you can directly get all the nodes similar to GitHub's GraphQL API: Copy query Teams { teams { nodes { id name } } } By default results are ordered by `createdAt` field. To get most recently updated resources, you can alternatively order by `updatedAt` field: Copy query Issues { issues(orderBy: updatedAt) { nodes { id identifier title createdAt updatedAt } } } [PreviousWorking with the GraphQL API](/docs/graphql/working-with-the-graphql-api) [NextFiltering](/docs/graphql/working-with-the-graphql-api/filtering) Last updated 1 year ago Was this helpful? --- # Working with the GraphQL API | Linear API * * * Linear's public API is built using GraphQL. It's the same API we use internally for developing our applications. If you're new to GraphQL, Apollo has [resources for beginners](https://blog.apollographql.com/the-basics-of-graphql-in-5-links-9e1dc4cac055) . [The official documentation](https://graphql.org/) is another good starting point. ### [](#endpoint) Endpoint Linear's GraphQL endpoint is: Copy https://api.linear.app/graphql It supports introspection so you can query the whole schema. ### [](#authentication) Authentication Right now we support personal API keys and OAuth2 authentication. #### [](#oauth2) OAuth2 If you're building an application for others to use, we recommend you use [OAuth2 authentication](/docs/oauth/authentication) . Once you completed the authentication flow and acquired an access token, you can pass it with header: `Authorization: Bearer ` Copy curl \ -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ --data '{ "query": "{ issues { nodes { id title } } }" }' \ https://api.linear.app/graphql #### [](#personal-api-keys) Personal API keys For personal scripts API keys are the easiest way to access the API. Visit [Security & access](https://linear.app/settings/account/security) settings to create and manage them. To authenticate your requests, you need to pass the API key with header: `Authorization: ` Copy curl \ -X POST \ -H "Content-Type: application/json" \ -H "Authorization: " \ --data '{ "query": "{ issues { nodes { id title } } }" }' \ https://api.linear.app/graphql ### [](#linear-sdk) Linear SDK The [Linear SDK](/docs/sdk/getting-started) exposes the Linear GraphQL schema, and makes it easy to access models, or perform mutations. We recommend using it to interact with the GraphQL API. It is written in TypeScript, allowing all operations to be strongly typed. ### [](#getting-started) Getting Started #### [](#we-recommend-using-a-graphql-client-to-introspect-and-explore-the-schema-if-you-are-not-using-the-li) We recommend using a GraphQL client to introspect and explore the schema if you are not using the Linear Client (SDK). Our GraphQL API is explorable and queryable via [Apollo Studio](https://studio.apollographql.com/public/Linear-API/variant/current/home) , no download or log in required. Click the Schema tab to browse the schema, and click the Explorer tab to run queries. Once you have your client installed, you can start making queries (read) and mutations (write) to the API. ### [](#queries-and-mutations) Queries & Mutations To get information about the authenticated user, you can use the `viewer` query: Copy query Me { viewer { id name email } } As issues (and most other objects) are team based, you first need to get the ID of the team you want to interact with: Copy query Teams { teams { nodes { id name } } } Once you have found the correct team, you can get the issues for that team. Lets make a request with also some other issue metadata: Copy query Team { team(id: "9cfb482a-81e3-4154-b5b9-2c805e70a02d") { id name issues { nodes { id title description assignee { id name } createdAt archivedAt } } } } We can also get an issue by id: Copy query Issue { issue(id: "BLA-123") { id title description } } To get the full list of available queries and mutations, introspect the API schema using your favorite GraphQL client. Protip: Locate the IDs of teams, issues and other entities directly within Linear itself from the command menu: `⌘/CTRL+K` "Copy model UUID". This will show results based on the page you're currently viewing within Linear. ### [](#creating-and-editing-issues) Creating & Editing Issues To create a new issue, we'll need to create a mutation: Copy mutation IssueCreate { issueCreate( input: { title: "New exception" description: "More detailed error report in markdown" teamId: "9cfb482a-81e3-4154-b5b9-2c805e70a02d" } ) { success issue { id title } } } This mutation will create a new issue and return its `id` and `title` if the call was successful (`success: true`). If an issue is created without a specified `stateId`(the status field for the issue), the issue will be assigned to the team's first state in the Backlog workflow state category. If the "Triage" feature is turned on for the team, then the issue will be assigned to the Triage workflow state. A common use case after creating an issue is updating the issue. To do this we can use the `issueUpdate` mutation, using the input field to include whatever it is we want to change. The `id` provided can be either be the uuid returned by the creation query, or the shorthand id like `BLA-123` below. Copy mutation IssueUpdate { issueUpdate( id: "BLA-123", input: { title: "New Issue Title" stateId: "NEW-STATE-ID", } ) { success issue { id title state { id name } } } } ### [](#accessing-images) Accessing images Linear hosts images and other assets uploaded into Linear behind authentication. Only authenticated users can view their assets. This also applies to the API and all images will require authentication to be displayed outside Linear's application. Regular [API authentication](/docs/graphql/working-with-the-graphql-api#authentication) (OAuth or API keys) is accepted for displaying images. If you're displaying images outside Linear's applications, you should download and self-host them in your application's environment. ### [](#adding-mentions-in-markdown) Adding mentions in Markdown In the Linear application, you can add mentions to users, issues, projects, and other resources by typing @ and then selecting a resource to mention. In the GraphQL API, mentions can be created in Markdown by using the plain URL of the resource. For example: Copy https://linear.app/linear/profiles/someuser what do you think about https://linear.app/linear/issue/LIN-123/some-issue here? Will convert into: > **@user** what do you think about **@LIN-123 some issue** here? Where the bolded segments are mentions. ### [](#fetching-updates) Fetching updates If you're working on building an application which display Linear data and you want the information to update (near) realtime, you have few options. To prevent excessive usage of our API, we recommend that you be mindful about your implementation. Lets say you're displaying a big number of issues in your application and want to update them: **Do's:** * Register [a programmatic webhook](/docs/graphql/webhooks) and get updates for all issues for the team. When you detect changes, update the issue information. You can also automatically register webhooks for OAuth applications. * If you have to poll recent changes, order results by returning recently updated issue first. See [Pagination](/docs/graphql/working-with-the-graphql-api/pagination) section above how to implement this * [Filter issues](/docs/graphql/working-with-the-graphql-api/filtering) in your GraphQL request instead of fetching all issues and filtering in code. **Dont's:** * Poll updates for each issue in the application. There should never be a reason to do this and your application might get rate limited. See above tactics to implement this better If you have any questions, visit **#api** channel on our [customer Slack](https://linear.app/join-slack) . ### [](#other-examples) Other Examples #### [](#queries) Queries There are many ways to fetch issues. One common use case is to get all the issues assigned to a user. First let's find our user's id: Copy query { users { nodes { name id } } } Now we can use the `assignedIssues` field on User: Copy query { user(id: "USERID") { id name assignedIssues { nodes { id title } } } } We can do the same thing with `workflowStates` which represent status fields for teams: Copy query { workflowStates { nodes { id name } } } Copy query { workflowState(id: "WORKFLOW_ID") { issues { nodes { title } } } } #### [](#archived-resources) Archived resources Archived resources are hidden by default from the paginated responses. They can be included by passing optional `includeArchived: true` as a query parameter for pagination. ### [](#support) Support If you run into problems or have questions or suggestions, you can join our customer Slack or send us a note ([hello@linear.app](mailto:hello@linear.app) ). Both options are available through the user menu in the Linear application. [PreviousIntroduction](/docs) [NextPagination](/docs/graphql/working-with-the-graphql-api/pagination) Last updated 0 minutes ago Was this helpful? --- # Deprecations | Linear API Linear's GraphQL API doesn't have versioning like many REST APIs due to the more constantly evolving nature of GraphQL. As the product evolves, we need to evolve the API as well, but we take breaking changes and deprecations seriously as we know developers build applications and integrations for the API. If there's a noticeable breaking change in the API, we'll proactively reach out to developers that use that part of the API and give you plenty of time to make changes. In other cases some functionality will be removed and a non-functioning stub will be left in the API to prevent breakage in queries and mutations. We utilize the `@deprecated` directive to annotate deprecation warnings in the schema. In addition, changes to the API are listed with `[API]` prefix in the [Linear changelog](https://linear.app/changelog) . Deprecated fields are also listed in API responses under `extensions.deprecations` , which you can setup alerting for: Copy { "data": { "issues": { "nodes": [\ {\ "identifier": "M-415",\ "title": "Runtime error",\ "description": "App crashes",\ "state": {\ "type": "triage",\ "name": "Triage"\ },\ "fakeField": "22H02",\ }\ ] } }, "extensions": { "deprecations": [\ {\ "field": "fakeField",\ "reason": "This field has been deprecated, use `newFakeField` instead."\ }\ ] } } [PreviousRate limiting](/docs/graphql/working-with-the-graphql-api/rate-limiting) [NextWebhooks](/docs/graphql/webhooks) Last updated 1 year ago Was this helpful? * * * --- # Rate limiting | Linear API Calls to our GraphQL API are rate limited to provide equitable access to the API for everyone and to prevent abuse. We are going to be evolving these limits as we gather more information, and encourage your feedback. Any changes to limits will be announced in our Slack community's [API announcements channel](https://linearcustomers.slack.com/archives/CN61HRZ9T) . We use the [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm for our rate limiters, which means that your tokens are refilled with a constant rate of `LIMIT_AMOUNT / LIMIT_PERIOD`. If you temporarily require higher limits, you can request them by contacting Linear support where we'll review them on a case by case basis. ### [](#avoiding-hitting-limits) Avoiding hitting limits There are ways of using our APIs that will in most cases avoid hitting our rate limits. #### [](#polling) Polling One thing that we especially discourage is polling the API to fetch updates. If you need to know when data updates in Linear, you should use our [Webhook](/docs/graphql/webhooks) functionality. #### [](#fetching-unneeded-data) Fetching unneeded data Avoid fetching data you don't need by using our [filtering](/docs/graphql/working-with-the-graphql-api/filtering) functionality. This way you can drill down on specific records only and avoid pagination in some cases. Keep in mind that by default our [pagination](/docs/graphql/working-with-the-graphql-api/pagination) returns up to 50 records. When querying for children this can quickly multiply the requested complexity. Consider specifying the amount of records you want returned. #### [](#ordering-data) Ordering data In certain cases where you do need to fetch all data, we suggest sorting it by the updated timestamp instead of when it was created. This way you can get the most recently changed data first, and avoid paginating through the entire dataset. #### [](#write-custom-specific-queries) Write custom, specific queries This applies especially if you're using our SDK. If you're fetching lots of different entities or dependencies, or have specific data needs, it's always recommended to write your own custom GraphQL queries and use filters to narrow down the data as much as possible. ### [](#api-request-limits) API request limits We limit the amount of requests you make to our GraphQL API. To make it easier to keep track and avoid going over the limits, there are 3 HTTP response headers we send back on each request. **Header name** **Description** `X-RateLimit-Requests-Limit` The maximum number of API requests you're permitted to make per hour. `X-RateLimit-Requests-Remaining` The number of API requests remaining in the current rate limit window. `X-RateLimit-Requests-Reset` The time at which the current rate limit window resets in [UTC epoch milliseconds](http://en.wikipedia.org/wiki/Unix_time) . #### [](#limits) Limits **Auth method** **Amount** **Per** **Period** API key 1,500 User 1 hour OAuth App 500 User/App 1 hour Unauthenticated 60 IP 1 hour When authenticated using an API key you can make up to **1,500 requests per hour**. Requests are associated with the authenticated user, which means all requests by the same user share the same quota even when using different API keys. When making unauthenticated requests, you are limited to **60 requests per hour**. These requests are associated with the originating IP address instead of the user making the request. #### [](#query-and-mutation-specific-request-limits) Query- and mutation-specific request limits Some queries and mutations have individual request rate limits that are lower than the global request limit. When one of these limits is hit, the Linear API will send the same response as described in [Handling rate limited errors](/docs/graphql/working-with-the-graphql-api/rate-limiting#handling-rate-limited-errors) . The window for each endpoint can be different, and is described in the response body. We will also send these extra headers: Header name Description `X-RateLimit-Endpoint-Requests-Limit` The maximum number of API requests you're permitted to make to this endpoint in a rate limit window. `X-RateLimit-Endpoint-Requests-Remaining` The number of API requests remaining in the current rate limit window. `X-RateLimit-Endpoint-Requests-Reset` The time at which the current rate limit window resets in [UTC epoch milliseconds](http://en.wikipedia.org/wiki/Unix_time) . `X-RateLimit-Endpoint-Name` The name of the endpoint that was rate limited. ### [](#api-request-complexity-limits) API request complexity limits In order to protect our system from queries that are too complex and resource intensive, we calculate the complexity of each query, based on the amount of requested data. To make it easier to keep track and avoid going over the limits, there are 4 HTTP response headers we send back on each request. **Header name** **Description** `X-Complexity` The complexity of the query. `X-RateLimit-Complexity-Limit` The maximum number of API complexity points you're permitted to request per hour. `X-RateLimit-Complexity-Remaining` The number of points of API request complexity remaining in the current rate limit window. `X-RateLimit-Complexity-Reset` The time at which the current rate limit window resets in [UTC epoch milliseconds](http://en.wikipedia.org/wiki/Unix_time) . #### [](#limits-1) Limits **Auth** **Amount** **Per** **Period** API key 250,000 User 1 hour OAuth app 200,000 User/App 1 hour Unauthenticated 10,000 IP 1 hour Requests authenticated using an API key can request up to **250,000 points per hour**. Requests are associated with the authenticated user, which means all requests by the same user share the same quota even when using different API keys. Unauthenticated requests are limited to **10,000 points per hour**. These requests are associated with the originating IP address instead of the user making the request. #### [](#maximum-complexity-of-a-single-query) Maximum complexity of a single query We also enforce a maximum complexity of a single query at any time to **10,000 points**. Your query will always get rejected if it exceeds that. #### [](#understanding-query-complexity) Understanding query complexity In order to protect our systems from too complex and resource intensive queries, we calculate the complexity of each query. Each property is 0.1 point, each object is 1 point and any connection multiplies its children's points based on the given pagination argument, or the default 50. The score is then rounded up to the nearest integer. Examples: Let's fetch an object that returns only one user and request only one property. The calculation is `1 + 0.1 = 1.1`, which equals a **complexity of 2** when rounded up. Copy query WhoAmI { user(id: "me") { name } } Let's now fetch all of our created issue's ID, title and when they were created. This has a **complexity of 66**. Here's why: `1 point` Getting the user `50 points = 50 × 1` Getting the issues (assume 50, the default pagination) `15 points = 50 × 3 × 0.1` Getting 3 attributes for 50 issues Copy query MyCreatedIssues { user(id: "me") { createdIssues { nodes { id title createdAt } } } } You can use pagination parameters to specify a different limit than the default 50 to let the complexity calculator know how much data you're trying to fetch. This query with an explicit limit of the first 10 nodes then has a **complexity of 14**. `1 + (10 × 1) + (10 × 3 × 0.1) = 14` Copy query MyCreatedIssues { user(id: "me") { createdIssues(first: 10) { nodes { id title createdAt } } } } ### [](#handling-rate-limited-errors) Handling rate limited errors Once you actually exceed rate limits, Linear API will start returning rate limit error responses. You can catch these by checking the `errors`in the response body containing the `RATELIMITED` error code. Copy { "errors": [\ {\ "message": "...",\ "extensions": {\ "code": "RATELIMITED",\ ...\ }\ }\ ] } [PreviousFiltering](/docs/graphql/working-with-the-graphql-api/filtering) [NextDeprecations](/docs/graphql/working-with-the-graphql-api/deprecations) Last updated 1 month ago Was this helpful? * * * --- # Webhooks | Linear API * * * Linear provides **webhooks** which allow you to receive HTTP push notifications whenever data is created or updated. This allows you to build integrations on top of Linear. You could trigger CI builds, perform calculations on issue data, or send messages on specific conditions – you name it. Webhooks are specific to an `Organization`, but you can configure webhooks to provide updates from all public teams, or a single team to satisfy the needs of each team in your organization. Please visit [your application's settings](https://linear.app/settings/api) to configure webhooks. Additionally, [OAuth applications](/docs/oauth/authentication#1-create-an-oauth2-application-in-linear) can configure webhook settings. Once those settings are configured, each time a new organization authorizes the given application, a webhook will be created for that organization that posts to the provided webhook URL, as described below. If your application is de-authorized from an organization the [OAuthApp revoked](/docs/graphql/webhooks#oauthapp-revoked-fields) event will be sent. Only workspace admins, or OAuth applications with the `admin` scope, can create or read webhooks. What we call "data change webhooks" are currently supported for the following models: * `Issues` * `Issue attachments` - [Documentation](/docs/graphql/attachments) * `Issue comments` * `Issue labels` * `Comment reactions` * `Projects` * `Project updates` * `Cycles` Other webhooks are provided for convenience: * `Issue SLA` - [Documentation](/docs/graphql/webhooks#issue-sla-fields) * `OAuthApp revoked` - [Documentation](/docs/graphql/webhooks#oauthapp-revoked-fields) ### [](#how-does-a-webhook-work) How does a Webhook work? A Webhook push is simply a `HTTP POST` request, sent to the URL of your choosing. The push is automatically triggered by Linear when data updates. For an example of what data a payload contains, see [The Webhook Payload](/docs/graphql/webhooks#the-webhook-payload) . Your webhook consumer is a simple HTTP endpoint. It must satisfy the following conditions: * It's available in a publicly accessible HTTPS, non-localhost URL * It will respond to the Linear Webhook push (HTTP POST request) with a `HTTP 200` ("OK") response If a delivery fails (i.e. server unavailable, takes longer than 5s to respond, or responds with a non-200 HTTP status code), the push will be retried a maximum of 3 times. A backoff delay is used: the attempt will be retried after 1 minute, 1 hour, and finally after 6 hours. If the webhook URL continues to be unresponsive the webhook might be disabled by Linear, and must be re-enabled again manually. To make sure a Webhook POST is truly created by Linear, you can check the request to originates from one of the following IPs: **35.231.147.226**, **35.243.134.228**, **34.140.253.14**, or **34.38.87.206**. For additional information on Webhooks, there are a number of good resources: * [RequestBin: Webhooks – The Definitive Guide](https://requestbin.com/blog/working-with-webhooks/) * [requestbin.com](https://requestbin.com/) is a great tool for testing webhooks * [GitHub Developer Guide: Webhooks](https://developer.github.com/webhooks/) ### [](#getting-started-with-linear-webhooks) Getting started with Linear Webhooks You will first need to create a Webhook endpoint (_"consumer"_) to be called by the Linear Webhook agent. This can be a simple HTTP server you deploy yourself, or a URL endpoint configured by a service such as [Zapier](https://zapier.com/) (or for testing purposes, [RequestBin](https://requestbin.com/) ). Once your consumer is ready to receive updates, you can enable it for your Linear team. Webhooks can be enabled in Linear both via the Team Settings UI. #### [](#creating-a-simple-webhook-consumer) Creating a simple Webhook consumer You might consider using something like [Netlify Functions](https://docs.netlify.com/functions/get-started/?fn-language=ts) , [Vercel Functions](https://vercel.com/docs/functions) , or [Cloudflare Workers](https://developers.cloudflare.com/workers/) , which provide a straightforward way of deploying simple HTTP(S) endpoints. Netlify Deploying a simple webhook consumer on Netlify might look something like this: Copy const { createHmac } = require('node:crypto'); export default async (request) => { const payload = await request.text(); const { action, data, type, createdAt } = JSON.parse(payload); // Verify signature const signature = createHmac("sha256", Netlify.env.get('WEBHOOK_SECRET')).update(payload).digest("hex"); if (signature !== request.headers.get('linear-signature')) { return new Response(null, { status: 400 }) } // Do something neat with the data received! // Finally, respond with a HTTP 200 to signal all good return new Response(null, { status: 200 }) } export const config = { path: "/my-linear-webhook" }; Netlify has also created a template to deploy a webhook in a single click: [https://github.com/netlify/linear-webhook-template](https://github.com/netlify/linear-webhook-template) #### [](#configuring-with-the-settings-ui) Configuring with the Settings UI The easiest way to configure a Webhook is via API Settings. Open Settings and find "API". Click on "New webhook", and specify the URL in which you have an endpoint ready to receive HTTP POST requests. Label is used to identify webhooks and describe their purpose. Your newly created webhook will be listed and is ready to be used. Your defined URL of `http://example.com/webhooks/linear-updates` will now get notified of any updates for your chosen models. **Creating a new Webhook** To create a new Webhook via the API, you can create a new Webhook with by calling a `webhookCreate` mutation with the `teamId` (or `allPublicTeams: true`) and `url` of your webhook, and the preferred `resourceTypes` (`[Comment, Issue, IssueLabel, Project, Cycle, Reaction]`): Copy mutation { webhookCreate( input: { url: "http://example.com/webhooks/linear-consumer" teamId: "72b2a2dc-6f4f-4423-9d34-24b5bd10634a" resourceTypes: ["Issue"] } ) { success webhook { id enabled } } } The server should respond with a `success` flag, along the `id` of your newly created webhook: Copy { "data": { "webhookCreate": { "success": true, "webhook": { "id": "790ce3f6-ea44-473d-bbd9-f3c73dc745a9", "enabled": true } } } } That's it! Your webhook is now ready to use and enabled by default. You can try it out e.g. by commenting on an Issue on your team, or maybe creating a new Issue. **Querying existing webhooks** Your webhooks belong to an Organization. You can either query all webhooks in your organization, or find them via their respective teams. Querying all webhooks in your organization (the results are paginated, so you will need to include the `nodes` property.): Copy query { webhooks { nodes { id url enabled team { id name } } } } Querying webhooks via their associated teams: Copy query { teams { nodes { webhooks { nodes { id url enabled creator { name } } } } } } **Deleting a webhook** Deleting a webhook is done with the `webhookDelete` mutation, by supplying the `id` of the webhook in question: Copy mutation { webhookDelete( id: "1087f03a-180a-4c31-b7dc-03dbe761ff59" ) { success } } ### [](#the-webhook-payload) The Webhook Payload The webhook HTTP payload will include information both in its HTTP headers and its request body. The format of the payload body reflects that of the corresponding GraphQL entity. To get a hang of the data contained in the various objects, feel free to play around with GraphQL queries against Linear's API. The payload will be sent with the following HTTP headers: Copy Accept-Charset: utf-8 Content-Type: application/json; charset=utf-8 Linear-Delivery: 234d1a4e-b617-4388-90fe-adc3633d6b72 Linear-Event: Issue Linear-Signature: 766e1d90a96e2f5ecec342a99c5552999dd95d49250171b902d703fd674f5086 User-Agent: Linear-Webhook Where the custom headers include: Name Description `Linear-Delivery` An UUID (v4) uniquely identifying this payload. `Linear-Event` The Entity type which triggered this event: `Issue`, `Comment` etc `Linear-Signature` HMAC signature of the webhook payload #### [](#data-change-events-payload) Data change events payload These fields are present on all data change events. Field Description `action` The type of the action that took place: `create`, `update` or `remove`. `type` The type of entity that was targeted by the action. `createdAt` The date and time that the action took place. `data` The serialized value of the subject entity. `url` URL where you can open up the subject entity. `updatedFrom` For `update` actions, an object containing the previous values of all updated properties. `webhookTimestamp` UNIX timestamp when the webhook was sent. `webhookId` ID uniquely identifying this webhook. For example: Copy { "action": "create", "data": { "id": "2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9", "createdAt": "2020-01-23T12:53:18.084Z", "updatedAt": "2020-01-23T12:53:18.084Z", "archivedAt": null, "body": "Indeed, I think this is definitely an improvement over the previous version.", "edited": false, "issueId": "539068e2-ae88-4d09-bd75-22eb4a59612f", "userId": "aacdca22-6266-4c0a-ab3c-8fa70a26765c" }, "type": "Comment", "url": "https://linear.app/issue/LIN-1778/foo-bar#comment-77217de3-fb52-4dad-bb9a-b356beb93de8", "createdAt": "2020-01-23T12:53:18.084Z", "webhookTimestamp": 1676056940508, "webhookId": "000042e3-d123-4980-b49f-8e140eef9329" } #### [](#other-events-payload) Other events payload These fields will be present on all other events as well. Field Description `action` The type of the action that took place. Specific to the event stream. For Issue SLA this is for example one of `set`, `highRisk` and `breached`. `type` The type of entity that was targeted by the action. `createdAt` The date and time that the action took place. `url` URL where you can open up the subject entity. `updatedFrom` For `update` actions, an object containing the previous values of all updated properties. Properties that were previously not set, will have a value of `null`. `webhookTimestamp` UNIX timestamp when the webhook was sent. `webhookId` ID uniquely identifying this webhook. #### [](#issue-sla-fields) Issue SLA fields These fields are present on Issue SLA events. Field Description `issueData` The serialized value of the issue. #### [](#oauthapp-revoked-fields) OAuthApp revoked fields This webhook is specific to OAuth applications and is called when your app is revoked from an organization. The will be `type` set to `OAuthApp` and `action` to `revoked`. Field Description `oauthClientId` Id of OAuth App that was revoked. `organizationId` Organization from which OAuth App was revoked. ### [](#securing-webhooks) Securing webhooks We support securing webhooks through content hashing with a signature. SHA256 HMAC signature is calculated of the content and delivered in `Linear-Signature` header which can used for comparison. Content body also includes a field `webhookTimestamp` with UNIX timestamp of the time when webhook was sent. It's recommended you verify that it's within a minute of the time your system sees it to prevent replay attacks. To verify the webhook, calculate the signature from request body using the webhook secret available in developer settings. It's recommended to use raw request body content for the hashing as using JSON parsing might change it. Copy const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex"); if (signature !== request.headers['linear-signature']) { throw "Invalid signature" } In addition to verifying webhook, it's recommended to validate the sender IP addresses. [See section above](/docs/graphql/webhooks#getting-started-with-linear-webhooks) for the list. [PreviousDeprecations](/docs/graphql/working-with-the-graphql-api/deprecations) [NextAttachments](/docs/graphql/attachments) Last updated 3 months ago Was this helpful? Webhooks configuration in API Settings UI ![](https://developers.linear.app/~gitbook/image?url=https%3A%2F%2F680911021-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-MUPMfJduSTtxypLII9W%252Fuploads%252FJ3Ty355WBnfrQXmVFFkF%252FCleanShot%25202023-03-17%2520at%252010.34.13%25402x.png%3Falt%3Dmedia%26token%3D07bc212e-981b-40d4-a015-95b7c31dbbd1&width=768&dpr=4&quality=100&sign=c01c9510&sv=2) ![](https://developers.linear.app/~gitbook/image?url=https%3A%2F%2F680911021-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-legacy-files%2Fo%2Fassets%252F-MUPMfJduSTtxypLII9W%252F-MjWaSB05cchb00fCwJF%252F-MjWapahW5eaWz6MhPj4%252FScreen%2520Shot%25202021-09-13%2520at%25205.00.55%2520PM.png%3Falt%3Dmedia%26token%3D5a4567ea-db4c-449f-a0d0-fe738293e5df&width=768&dpr=4&quality=100&sign=f1a9a07&sv=2) --- # Filtering | Linear API * * * Most results that are paginated can also be filtered. This makes it easy to retrieve specific information, like any issues assigned to a particular user, but much more complex queries are also possible. For example, you could fetch all issues associated with a project that is supposed to be completed next week and have not yet been started. Filtering is currently in Alpha. While we don't anticipate the filtering format to change, it might. Follow our [API slack channel](https://linearcustomers.slack.com/archives/CN61HRZ9T) to get a heads up on breaking changes. For example, to return all urgent and high priority issues in the workspace, you can use the following query: Copy query HighPriorityIssues { issues(filter: { priority: { lte: 2 } }) { nodes { id, title, priority } } } The above query will also return any issues that haven't been given any priority (their priority is 0). To exclude them, you can add another **not equals** comparator: Copy query HighPriorityIssues { issues(filter: { priority: { lte: 2, neq: 0 } }) { nodes { id, title, priority } } } ### [](#comparators) Comparators You can use the following comparators on **string**, **numeric**, and **date** fields: Comparator Description `eq` Equals the given value `neq` Doesn't equal the given value `in` Value is in the given collection of values `nin` Value is not in the given collection of values Numeric and date fields additionally have the following comparators: Comparator Description `lt` Less than the given value `lte` Less than or equal to the given value `gt` Greater than then given value `gte` Greater than or equal to the given value String fields additionally have the following comparators: Comparator Description `eqIgnoreCase` Case insensitive `eq` `neqIgnoreCase` Case insensitive `neq` `startsWith` Starts with the given value `notStartsWith` Doesn't start with the given value `endsWith` Ends with the given value `notEndsWith` Doesn't end with the given value `contains` Contains the given value `notContains` Doesn't contain the given value `containsIgnoreCase` Case insensitive `contains` `notContainsIgnoreCase` Case insensitive `notContains` Optional values additionally support the `null` comparator, which can be used to return entities depending on whether the field has a value or not. The following query will return all issues that don't have a description: Copy query Issues { issues(filter: { description: { null: true } }) { nodes { id, title, description } } } ### [](#logical-operators) Logical operators By default, all fields described in the filter need to be matched. The filter merges all the conditions together using a logical **and** operator. For example, The below example will find all urgent issues that are due in the year 2021. Copy query Issues { issues(filter: { priority: { eq: 1 } dueDate: { lte: "2021" } }) { nodes { id, title, priority, dueDate } } } To change the logical operator, all filters support the `**or**` keyword that lets you switch to a logical **or** operator. For example, to filter for low-priority or un-prioritized issues that need to be completed in the year 2021, you can execute the following query: Copy query Issues { issues(filter: { or: [\ { priority: { eq: 4 } },\ { priority: { eq: 0 } }\ ] dueDate: { lte: "2021" } }) { nodes { id, title, priority, dueDate } } } ### [](#filtering-by-relationship) Filtering by relationship Data can also be filtered based on their relations. For example, you can filter issues based on the properties of their assignees. To query all issues assigned to a user with a particular email address, you can execute the following query: Copy query AssignedIssues { issues(filter: { assignee: { email: { eq: "john@linear.app" } } }) { nodes { id title assignee { name } } } } Many-to-many relationships can be filtered similarly. The following query will find issues that have the **Bug** label associated. Copy query Issues { issues(filter: { labels: { name: { eq: "Bug" } } }) { nodes { id, title } } } The above query returns all issues that have at least one label that matches the name **Bug**. To create a query where all labels on an issue are matched to the filter criteria, you can use the `**every**` keyword: Copy query Issues { issues(filter: { labels: { every: { name: { eq: "Bug" } } } }) { nodes { id, title } } } The above would also filter out issues that have multiple labels, regardless of what they are. ### [](#relative-time) Relative time All date fields support relative time, defined as [ISO 8601 durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) relative to the current date. This lets you create a filter that always returns all issues that are due in the next 2 weeks, regardless of when you run it: Copy query IssuesDue { issues(filter: { dueDate: { lt: "P2W" } }) { nodes { id, title } } } ### [](#examples) Examples Find all **bugs** and **defects** from projects that are lead by any user named "**John**": Copy query Projects { projects(filter: { lead: { name: { startsWith: "John" } } }) { nodes { issues(filter: { labels: { name: { in: ["Bug", "Defect"] } } }) { nodes { id title } } } } } Find all issues assigned to me that have a comment containing a thumbs-up emoji: Copy query Issues { viewer { assignedIssues(filter: { comments: { body: { contains: "👍" } } }) { nodes { id title } } } } Find all issues that have been created by me and have been closed in the past two weeks: Copy query ClosedIssues { viewer { createdIssues(filter: { completedAt: { gt: "-P2W" } }) { nodes { id, title } } } } Find all started issues in ongoing projects that don't have an estimate: Copy query Issues { issues( filter: { estimate: { eq: 0 } state: { type: { eq: "started" } } project: { state: { eq: "started" } } } ) { nodes { title estimate project { name } } } } [PreviousPagination](/docs/graphql/working-with-the-graphql-api/pagination) [NextRate limiting](/docs/graphql/working-with-the-graphql-api/rate-limiting) Last updated 3 years ago Was this helpful? --- # Attachments | Linear API * * * Issue attachments allow you to link external resources to issues and display them inside Linear similarly to GitHub Pull Requests. They are designed with API developers in mind and we also use them for upcoming integrations inside Linear. Example use cases: * Customer support software where an agent can create a Linear issue * Release bot that attached release version to an issue Attachments functionality is still in alpha and subject to change. Unique URLs are a core concept with attachments. They enable building stateless applications and integrations which interact with Linear's API. _Attachment URL is used as an idempotent value if used in conjunction with the same issue id_ so if you try to re-create an attachment with the same URL on the same issue, the original attachment is updated instead. This enables simple scripts which update the attachment content without storing the attachment ID. _You can also query an attachment, and the associated issue, by its URL_. This makes creating links to Linear issues from external application easy and again you don't need to track the attachment ID. It's recommended to create attachments through Linear's OAuth authentication. Then the application icon is used for the attachment by default, but an icon image URL can be specified when creating the attachment that overrides the application icon. For API key auth, you can also provide an icon URL when creating an attachment. The image provided by URL must be of png or jpg format. Attachments also support key-value metadata. Values can be any string or number and you can store information there related to your integration. Right now metadata is only exposed through the API but we're also considering exposing it in the UI. Linear's webhooks also support attachments so you can subscribe to get updates for new and updated attachments. **Create an attachment:** Copy mutation{ attachmentCreate(input:{ issueId: "590a1127-f98b-49fc-ba74-2df8751c089e" title: "Exception" subtitle: "Open" url: "http://exception.com/123" iconUrl: "https://exception.com/assets/icon.png" metadata: {exceptionId: "exc-123"} }){ success attachment{ id } } } **Update an attachment with id:** Copy mutation{ attachmentUpdate(id: "47e14163-404c-4a34-b775-5c536d67760a", input: { title: "Exception" subtitle: "Resolved" metadata: {exceptionId: "exc-123"} }){ success attachment{ id } } } **Query for attachments with a URL or for a specific attachment with an id:** Copy query { attachment(id: "47e14163-404c-4a34-b775-5c536d67760a") { id issue { id identifier title } } } query { attachmentsForURL(url: "http://exception.com/123") { nodes { id issue { id identifier title } } } } ### [](#rich-metadata) Rich metadata In addition to generic key-value pairs, `metadata` field can include fields which will be rendered as a rich attachment modal inside Linear. This makes it easier to include data that you would otherwise have to fetch/read by following the attachment link. Key Type Description `title` `string` Title for the modal `messages` `{ subject?: string, body?: string, timestamp?: string }[]` Messages included in the attachment. Subject, body, and timestamp are all optional, but we suggest always populating body. Keep under 10k characters. `attributes` `{ name: string, value: string }[]` Additional attributes which will be rendered in a list. ### [](#formatting) Formatting Format Variable Type Output example `{variableName__since}` Date as ISO string "2 days ago", "23 hours ago" `{variableName__relativeTimestamp}` Date as ISO string **If +/- 6 days from current:** "today at 9:30 AM", "Friday at 9:30 AM" **If > 6 days from current:** "Oct 20, 9:30 AM" In order to use the date formatting, when creating an attachment provide a date variable (in ISO string format) in the attachment's metadata. You may then add that date with the format `{variableName__since}` into the attachment subtitle. When the attachment is rendered, we will format the time since that date, or format that date and time relative to current time, depending on which format is being used. Copy mutation{ attachmentCreate(input:{ issueId: "590a1127-f98b-49fc-ba74-2df8751c089e" title: "Exception" subtitle: "Detected {detectedAt__since}" url: "http://exception.com/123" iconUrl: "https://exception.com/assets/icon.png" metadata: {detectedAt: "2021-07-06T17:10:32.090Z"} }){ success attachment{ id } } } The above query would yield output like the following: [PreviousWebhooks](/docs/graphql/webhooks) [NextOAuth 2.0 Authentication](/docs/oauth/authentication) Last updated 1 year ago Was this helpful? Attachment using since date formatting ![](https://developers.linear.app/~gitbook/image?url=https%3A%2F%2F680911021-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-legacy-files%2Fo%2Fassets%252F-MUPMfJduSTtxypLII9W%252F-Me0Y26RwtpQ4yJlusIL%252F-Me0br5S5JtqnfDvEmqE%252FScreen%2520Shot%25202021-07-07%2520at%25208.38.24%2520AM.png%3Falt%3Dmedia%26token%3D9231bfc7-a230-40e3-b5c6-eb71c5c98708&width=768&dpr=4&quality=100&sign=78e21ae0&sv=2) --- # OAuth 2.0 Authentication | Linear API * * * 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. ### [](#id-1.-create-an-oauth2-application-in-linear) **1\. Create an OAuth2 application in Linear** Create a new [OAuth2 Application](https://linear.app/settings/api/applications/new) Configure the redirect callback URLs to your application ### [](#id-2.-redirect-user-access-requests-to-linear) **2\. Redirect user access requests to Linear** When authorizing a user to the Linear API, redirect to an authorization URL with correct parameters and scopes: Copy GET https://linear.app/oauth/authorize **Parameters** Name Description `client_id` (required) Client ID provided when you create the OAuth2 Application `redirect_uri` (required) Redirect URI `response_type=code` (required) Expected response type `scope` (required) Comma separated list of scopes: * `read` - (Default) Read access for the user's account. This scope will always be present. * `write` - Write access for the user's account. If your application only needs to create comments, use a more targeted scope * `issues:create` - Allows creating new issues and their attachments * `comments:create` - Allows creating new issue comments * `timeSchedule:write` - Allows creating and modifying time schedules * `admin` - Full access to admin level endpoints. You should never ask for this permission unless it's absolutely needed `state` `prompt=consent` (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. `actor` Define how the OAuth application should create issues, comments and other changes: * `user` - (Default) Resources are created as the user who authorized the application. This option should be used if you want each user to do their own authentication * `application` - Resources are created as the application. This option should be used if you have have only one user (e.g. admin) authorizing the application. Can be used together with `createAsUser` property when creating issues and comments. **Example** Copy GET https://linear.app/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URL&state=SECURE_RANDOM&scope=read GET https://linear.app/oauth/authorize?client_id=client1&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Fcallback&response_type=code&scope=read,write ### [](#id-3.-handle-the-redirect-urls-you-specified-in-the-oauth2-application) **3\. Handle 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** Copy GET https://example.com/oauth/callback?code=9a5190f637d8b1ad0ca92ab3ec4c0d033ad6c862&state=b1ad0ca92 ### [](#id-4.-exchange-code-for-an-access-token) **4\. Exchange** `**code**` **for an access token** After receiving the `code`, you can exchange it for a Linear API access token: Copy POST https://api.linear.app/oauth/token `Content-Type` header should be `application/x-www-form-urlencoded` **Parameters** Name Description `code` (required) Authorization code from the previous step `redirect_uri` (required) Same redirect URI which you used in the previous step `client_id` (required) Application's client ID `client_secret` (required) Application's client secret `grant_type=authorization_code` (required) Pass parameters in body as [URL-encoded form submission](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#url-encoded_form_submission) **Response** After a successful request, a valid access token will be returned in the response: Copy { "access_token": "00a21d8b0c4e2375114e49c067dfb81eb0d2076f48354714cd5df984d87b67cc", "token_type": "Bearer", "expires_in": 315705599, "scope": "read write" } Note: OAuth apps created prior to Dec 1, 2023 will instead return `scope` as an array of strings in the token response. ### [](#id-5.-make-an-api-request) **5\. 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](/docs/sdk/getting-started) with the access token: Copy const client = new LinearClient({ accessToken: response.access_token }) const me = await client.viewer Or pass the token as an authorization header: `Authorization: Bearer ` Copy curl https://api.linear.app/graphql \ -X POST \ -H "Content-Type: application/json" \ -H 'Authorization: Bearer 00a21d8b0c4e2375114e49c067dfb81eb0d2076f48354714cd5df984d87b67cc' \ --data '{ "query": "{ viewer { id name } }" }' \ ### [](#id-6.-revoke-an-access-token) **6\. Revoke an access token** To revoke a user's access to your application pass the access token as Bearer token in the authorization header (`Authorization: Bearer `) or as the `access_token` form field: Copy POST https://api.linear.app/oauth/revoke **Response** Expected HTTP status: * `200` - token was revoked * `400` - unable to revoke token (e.g. token was already revoked) * `401` - unable to authenticate with the token [PreviousAttachments](/docs/graphql/attachments) [NextOAuth Actor Authorization](/docs/oauth/oauth-actor-authorization) Last updated 3 months ago Was this helpful? (optional) Prevents CSRF attacks and should always be supplied. Read more about it [here](https://auth0.com/docs/protocols/state-parameters) --- # OAuth Actor Authorization | Linear API * * * By default all Linear's API authentication methods treat the authenticating user as the API actor. Most of the time this is fine and each user has to authorize their own access. Linear also supports **OAuth Actor Authorization** which allows performing certain API actions as the application instead of the user authorizing the application. To enable the actor authorization, add `actor=application` parameter to your [OAuth authorization URL](/docs/oauth/authentication#2.-redirect-user-access-requests-to-linear) . The setting is tied to the authorization and its access token. In this mode, all created issues, comments and [linked attachments](/docs/graphql/attachments) will be created as the application. In addition to creating resources as applications, you can also add an optional user name and avatar to go with the application to have it rendered in _User (via Application)_ format. This will help identify the user that performed the action in the 3rd party system. To set the custom user name, set the `createAsUser` attribute with the user name and set `displayIconUrl` with the URL of the avatar in `issueCreate` or `commentCreate` mutations: Copy mutation IssueCreate { issueCreate( input: { title: "New exception" description: "More detailed error report in markdown" teamId: "9cfb482a-81e3-4154-b5b9-2c805e70a02d" createAsUser: "Mark" displayIconUrl: "http://path.to/image.png" } ) { success issue { id title } } } [PreviousOAuth 2.0 Authentication](/docs/oauth/authentication) [NextFile Storage Authentication](/docs/oauth/file-storage-authentication) Last updated 1 year ago Was this helpful? --- # File Storage Authentication | Linear API * * * Files uploaded to Linear, such as images and attachments, are stored in Linear's private cloud storage. You must authenticate to access these files. These files are accessible from the `https://uploads.linear.app` hostname. ### [](#authorization-header) Authorization Header You can pass the same access token and `authorization` header as you would when making requests to our GraphQL API when requesting files from storage. This is usually the best option when downloading files in a server environment. An example request might look something like: Copy curl https://uploads.linear.app/6db02bb9-fba2-473b-8f9d-f38188e84813/d20adbea-186d-4643-ad07-004bda7d099d \ -X GET \ -H 'Authorization: Bearer 00a21d8b0c4e2375114e49c067dfb81eb0d2076f48354714cd5df984d87b67cc' ### [](#request-signed-urls) Request Signed URLs When using the GraphQL API, you can request that all URLs in responses pointing to file storage include a signature which allows temporary access to the file. This is achieved by passing the `public-file-urls-expire-in` header with an integer value representing the signature expiration in seconds. With the [TypeScript SDK](/docs/sdk/getting-started) this header can be set directly on the client. Here is an example to receive signatures that are valid for 1 minute: Copy const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY, headers: { "public-file-urls-expire-in": 60, } }); [PreviousOAuth Actor Authorization](/docs/oauth/oauth-actor-authorization) [NextGetting Started](/docs/sdk/getting-started) Last updated 1 year ago Was this helpful? --- # Getting Started | Linear API The Linear Typescript SDK exposes the [Linear GraphQL schema](https://github.com/linear/linear/blob/master/packages/sdk/src/schema.graphql) through strongly typed [models and operations](https://github.com/linear/linear/blob/master/packages/sdk/src/_generated_sdk.ts) . It's written in Typescript but can also be used in any Javascript environment. All operations return models, which can be used to perform operations for other models and all types are accessible through the Linear SDK package. Copy import { LinearClient, LinearFetch, User } from "@linear/sdk"; const linearClient = new LinearClient({ apiKey }); async function getCurrentUser(): LinearFetch { return linearClient.viewer; } You can view the Linear SDK source code on [GitHub](https://github.com/linear/linear/tree/master/packages/sdk) . [](#connect-to-the-linear-api-and-interact-with-your-data-in-a-few-steps) Connect to the Linear API and interact with your data in a few steps: ---------------------------------------------------------------------------------------------------------------------------------------------------- ### [](#id-1.-install-the-linear-client) **1\. Install the Linear Client** Using npm: Copy npm install @linear/sdk Or yarn: Copy yarn add @linear/sdk ### [](#id-2.-create-a-linear-client) **2\. Create a Linear client** SDK supports both authentication methods, personal API keys and OAuth 2. See [authentication](https://developers.linear.app/docs/graphql/working-with-the-graphql-api#authentication) for more details. You can create a client after creating authentication keys: Copy import { LinearClient } from '@linear/sdk' // Api key authentication const client1 = new LinearClient({ apiKey: YOUR_PERSONAL_API_KEY }) // OAuth2 authentication const client2 = new LinearClient({ accessToken: YOUR_OAUTH_ACCESS_TOKEN }) ### [](#id-3.-query-for-your-issues) **3\. Query for your issues** Using async await syntax: Copy async function getMyIssues() { const me = await linearClient.viewer; const myIssues = await me.assignedIssues(); if (myIssues.nodes.length) { myIssues.nodes.map(issue => console.log(`${me.displayName} has issue: ${issue.title}`)); } else { console.log(`${me.displayName} has no issues`); } } getMyIssues(); Or promises: Copy linearClient.viewer.then(me => { return me.assignedIssues().then(myIssues => { if (myIssues.nodes.length) { myIssues.nodes.map(issue => console.log(`${me.displayName} has issue: ${issue.title}`)); } else { console.log(`${me.displayName} has no issues`); } }); }); [PreviousFile Storage Authentication](/docs/oauth/file-storage-authentication) [NextFetching & Modifying Data](/docs/sdk/fetching-and-modifying-data) Last updated 2 years ago Was this helpful? * * * --- # Errors | Linear API * * * Errors can be caught and interrogated by wrapping the operation in a try catch block: Copy async function createComment(input: LinearDocument.CommentCreateInput): LinearFetch { try { /** Try to create a comment */ const commentPayload = await linearClient.createComment(input); /** Return it if available */ return commentPayload.comment; } catch (error) { /** The error has been parsed by Linear Client */ throw error; } } Or by catching the error thrown from a calling function: Copy async function archiveFirstIssue(): LinearFetch { const me = await linearClient.viewer; const issues = await me.assignedIssues(); const firstIssue = issues.nodes[0]; if (firstIssue?.id) { const payload = await linearClient.archiveIssue(firstIssue.id); return payload; } else { return undefined; } } archiveFirstIssue().catch(error => { throw error; }); The parsed error type can be compared to determine the course of action: Copy import { InvalidInputLinearError, LinearError, LinearErrorType } from '@linear/sdk' import { UserError } from './custom-errors' const input = { name: "Happy Team" }; createTeam(input).catch(error => { if (error instanceof InvalidInputLinearError) { /** If the mutation has failed due to an invalid user input return a custom user error */ return new UserError(input, error); } else { /** Otherwise throw the error and handle in the calling function */ throw error; } }); Information about the `request` resulting in the error is attached if available: Copy run().catch(error => { if (error instanceof LinearError) { console.error("Failed query:", error.query); console.error("With variables:", error.variables); } throw error; }); Information about the `response` is attached if available: Copy run().catch(error => { if (error instanceof LinearError) { console.error("Failed HTTP status:", error.status); console.error("Failed response data:", error.data); } throw error; }); Any GraphQL `errors` are parsed and added to an array: Copy run().catch(error => { if (error instanceof LinearError) { error.errors?.map(graphqlError => { console.log("Error message", graphqlError.message); console.log("LinearErrorType of this GraphQL error", graphqlError.type); console.log("Error due to user input", graphqlError.userError); console.log("Path through the GraphQL schema", graphqlError.path); }); } throw error; }); The `raw` error returned by the `LinearGraphQLClient` is still available: Copy run().catch(error => { if (error instanceof LinearError) { console.log("The original error", error.raw); } throw error; }); [PreviousFetching & Modifying Data](/docs/sdk/fetching-and-modifying-data) [NextAdvanced Usage](/docs/sdk/advanced) Last updated 2 years ago Was this helpful? --- # Fetching & Modifying Data | Linear API * * * ### [](#queries) Queries Some models can be fetched from the Linear Client without any arguments: Copy const me = await linearClient.viewer; const org = await linearClient.organization; Other models are exposed as connections, and return a list of nodes: Copy const issues = await linearClient.issues(); const firstIssue = issues.nodes[0]; All required variables are passed as the first arguments: Copy const user = await linearClient.user("user-id"); const team = await linearClient.team("team-id"); Any optional variables are passed into the last argument as an object: Copy const fiftyProjects = await linearClient.projects({ first: 50 }); const allComments = await linearClient.comments({ includeArchived: true }); Most models expose operations to fetch other models: Copy const me = await linearClient.viewer; const myIssues = await me.assignedIssues(); const myFirstIssue = myIssues.nodes[0]; const myFirstIssueComments = await myFirstIssue.comments(); const myFirstIssueFirstComment = myFirstIssueComments.nodes[0]; const myFirstIssueFirstCommentUser = await myFirstIssueFirstComment.user; **NOTE:** Parenthesis is required only if the operation takes an optional variables object. **TIP:** You can find ID's for any entity within the Linear app by searching for for "Copy model UUID" in the command menu. ### [](#undefined) ### [](#mutations) Mutations To create a model, call the Linear Client mutation and pass in the input object: Copy const teams = await linearClient.teams(); const team = teams.nodes[0]; if (team.id) { await linearClient.createIssue({ teamId: team.id, title: "My Created Issue" }); } To update a model, call the Linear Client mutation and pass in the required variables and input object: Copy const me = await linearClient.viewer; if (me.id) { await linearClient.updateUser(me.id, { displayName: "Alice" }); } Or call the mutation from the model: Copy const me = await linearClient.viewer; await me.update({ displayName: "Alice" }); All mutations are exposed in the same way: Copy const projects = await linearClient.projects(); const project = projects.nodes[0]; if (project.id) { await linearClient.archiveProject(project.id); await project.archive(); } Mutations will often return a success boolean and the mutated entity: Copy const commentPayload = await linearClient.createComment({ issueId: "some-issue-id" }); if (commentPayload.success) { return commentPayload.comment; } else { return new Error("Failed to create comment"); } ### [](#pagination) Pagination Connection models have helpers to fetch the next and previous pages of results: Copy const issues = await linearClient.issues({ after: "some-issue-cursor", first: 10 }); const nextIssues = await issues.fetchNext(); const prevIssues = await issues.fetchPrevious(); Pagination info is exposed and can be passed to the query operations. This uses the [Relay Connection spec](https://relay.dev/graphql/connections.htm) : Copy const issues = await linearClient.issues(); const hasMoreIssues = issues.pageInfo.hasNextPage; const issuesEndCursor = issues.pageInfo.endCursor; const moreIssues = await linearClient.issues({ after: issuesEndCursor, first: 10 }); Results can be ordered using the `orderBy` optional variable: Copy import { LinearDocument } from "@linear/sdk"; const issues = await linearClient.issues({ orderBy: LinearDocument.PaginationOrderBy.UpdatedAt }); [PreviousGetting Started](/docs/sdk/getting-started) [NextErrors](/docs/sdk/errors) Last updated 1 year ago Was this helpful? --- # How to upload a file to Linear | Linear API * * * Files uploaded to Linear are stored in Linear's private cloud storage. They are only intended to be used within Linear, and you must authenticate to access these files elsewhere. Read more in [File Storage Authentication](/docs/oauth/file-storage-authentication) [](#include-an-image-within-markdown-content) Include an image within markdown content ------------------------------------------------------------------------------------------- The easiest way to upload an image to Linear's private cloud storage is to include a URL reference to it within markdown content that you provide while creating issues, comments, or documents. For example, while using the `IssueCreate` mutation in the GraphQL API, include an image in the markdown content provided in the `description` field: Copy mutation IssueCreate { issueCreate( input: { title: "Issue title" description: "Markdown image here: \n ![alt text](https://example.com/image.png)" teamId: "9cfb482a-81e3-4154-b5b9-2c805e70a02d" } ) { success } } The image file at `https://example.com/image.png` will be automatically uploaded to Linear's private cloud storage. You can also embed a base64 encoded image instead of a URL: Copy ![alt text](data:image/jpeg;base64,...) [](#upload-files-manually) Upload files manually ----------------------------------------------------- To upload directly to storage and for files other than images, use the `fileUpload` mutation to request a pre-signed upload URL, then send a `PUT` request to that URL with the file content. Attempting to upload a file from the client-side will be blocked by Linear's [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) . You may request the signed upload URL from the client, but the `PUT` request must be executed on the server. Here is an example using the TypeScript SDK to upload a file on the server: Copy /** Uploads a file to Linear, returning the uploaded URL. @throws */ async function uploadFileToLinear(file: File): Promise { const uploadPayload = await linearClient.fileUpload(file.type, file.name, file.size); if (!uploadPayload.success || !uploadPayload.uploadFile) { throw new Error("Failed to request upload URL"); } const uploadUrl = uploadPayload.uploadFile.uploadUrl; const assetUrl = uploadPayload.uploadFile.assetUrl; // Make sure to copy the response headers for the PUT request const headers = new Headers(); headers.set("Content-Type", file.type); headers.set("Cache-Control", "public, max-age=31536000"); uploadPayload.uploadFile.headers.forEach(({ key, value }) => headers.set(key, value)); try { await fetch(uploadUrl, { method: "PUT", headers, body: file }); return assetUrl; } catch (e) { console.error(e); throw new Error("Failed to upload file to Linear"); } } The resulting file URL now points to Linear's private cloud storage and can be used in API mutations, like creating an issue or a comment. ### [](#proxy-the-file-upload-on-the-server) Proxy the file upload on the server If you're handling file uploads from a website, for example with the `` element, you must forward the file to a server before attempting to upload it to Linear. **Next.js Example** Browse or run a full example that proxies a file upload through Next.js API Routes here: [https://github.com/linear/linear/tree/master/examples/nextjs-file-upload](https://github.com/linear/linear/tree/master/examples/nextjs-file-upload) [](#errors) Errors ----------------------- Common errors when uploading files to Linear and how to fix them. CORS error when sending the `PUT` request[](#cors-error-when-sending-the-put-request) You are trying to upload a file from the client-side rather than a server, which is not allowed. You must proxy the file upload request via a server. See [Proxy the file upload on the server](/docs/guides/how-to-upload-a-file-to-linear#proxy-the-file-upload-on-the-server) for an example of how to do this with Next.js. 403 Forbidden response from PUT request[](#id-403-forbidden-response-from-put-request) You likely forgot to copy the headers returned by the `fileUpload` mutation onto the `PUT` request. Note that the headers are returned in array format and must be transformed into an object or a `Headers` instance before including them in a `fetch` request. [PreviousMigrating from 1.x to 2.x](/docs/sdk/migrating-from-1.x-to-2.x) [NextHow to create new issues using linear.new](/docs/guides/how-to-create-new-issues-using-linear.new) Last updated 2 months ago Was this helpful? --- # Webhooks | Linear API * * * The SDK provides a helper class to verify [webhook](/docs/graphql/webhooks) signatures. #### [](#usage) Usage Copy import { LinearWebhooks, LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD } from '@linear/sdk' const webhook = new LinearWebhooks("WEBHOOK_SECRET"); ... // Use it in the webhook handler. Example with Express: app.use('/hooks/linear', bodyParser.json({ verify: (req, res, buf) => { webhook.verify(buf, req.headers[LINEAR_WEBHOOK_SIGNATURE_HEADER] as string, JSON.parse(buf.toString())[LINEAR_WEBHOOK_TS_FIELD]); } }), (req, res, next) => { //Handle the webhook event next() }) [PreviousAdvanced Usage](/docs/sdk/advanced) [NextMigrating from 1.x to 2.x](/docs/sdk/migrating-from-1.x-to-2.x) Last updated 2 years ago Was this helpful? --- # Brand Guidelines | Linear API The following guidelines help developers of 3rd-party Linear apps and integrations to use Linear’s brand and assets correctly and consistently. ### [](#naming-apps-and-integrations) Naming Apps and Integrations When naming apps and integrations that work with Linear, please follow these guidelines: * Use a unique name for your application or integration * Do not use “Linear” in the name of your application or integration * Clearly state that the integration is built by you — and not Linear * Do not use a name that is confusingly similar to Linear or infringes on any other trademarks ### [](#logo-usage) Logo Usage The logo and workmark are the most prominent elements of the Linear brand and it’s important to use them correctly. Similar to the Linear brandname, please refrain from using it as the main identifier for your app or integration. Detailed instructions for usage of Linear’s brand assets are provided on the [Linear Brand Guidelines](https://linear.app/brand) page. ### [](#submission-process-for-the-integration-directory) Submission Process for the Integration Directory Developers can submit their Linear apps and integrations to the [Linear Integrations directory](https://linear.app/integrations) . Please note that a listing in the directory does not constitute a “partnership” with Linear and should not be communicated as such. Please also refrain from using terms such as “official Linear integration” or “verified Linear app”. To submit your application to the integration directory, follow these steps: 1. Fill out the [integration submission form](https://docs.google.com/forms/d/e/1FAIpQLSdYKtml_JsVv-sXuP4_eiT-l-8K6IaoEQrxdAP_7w-mb1-hSQ/viewform?usp=sf_link) . It includes a sample page that helps to get a sense of the required copy style and length. 2. Submit assets to integrations@linear.app or include a link in the form. Use [this Figma template](https://www.figma.com/community/file/1111754695287344546) for asset guidelines. 3. Send any questions to integrations@linear.app [PreviousManaging Customers](/docs/guides/managing-customers) Last updated 1 year ago Was this helpful? * * * --- # Introduction | Linear API * * * [NextWorking with the GraphQL API](/docs/graphql/working-with-the-graphql-api) Last updated 9 months ago Was this helpful? Welcome to [Linear](https://linear.app) 's developer docs, where you can learn more about Linear's API and developer tooling. You can use Linear's API to build everything from light scripts to complete integrations for the tool you're working on. The easiest way to get started is to install our [Javascript/Typescript SDK](/docs/sdk/getting-started) . You can also learn more about our [GraphQL API](/docs/graphql/working-with-the-graphql-api) to create issues or access your data, and how to get realtime updates via [Webhooks](/docs/graphql/webhooks) . ![](https://developers.linear.app/~gitbook/image?url=https%3A%2F%2F680911021-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-legacy-files%2Fo%2Fassets%252F-MUPMfJduSTtxypLII9W%252F-MUyzZMeD4phCOLP2NPL%252F-MUyzctqQN5MVzPEFbNE%252FGroup%252037.png%3Falt%3Dmedia%26token%3D769c5162-ccd7-42a6-aefc-4bf92e29592c&width=768&dpr=4&quality=100&sign=e02153b5&sv=2) --- # Getting Started | Linear API * * * --- # Advanced Usage | Linear API * * * The Linear Client wraps the [Linear SDK](https://github.com/linear/linear/tree/master/packages/sdk/src/_generated_sdk.ts) , provides a [LinearGraphQLClient](https://github.com/linear/linear/tree/master/packages/sdk/src/graphql-client.ts) , and [parses errors](https://github.com/linear/linear/tree/master/packages/sdk/src/error.ts) . #### [](#request-configuration) Request Configuration The `LinearGraphQLClient` can be configured by passing the `RequestInit` object to the Linear Client constructor: Copy const linearClient = new LinearClient({ apiKey, headers: { "my-header": "value" } }); #### [](#raw-graphql-client) Raw GraphQL Client The `LinearGraphQLClient` is accessible through the Linear Client: Copy const graphQLClient = linearClient.client; graphQLClient.setHeader("my-header", "value"); #### [](#raw-graphql-queries) Raw GraphQL Queries The Linear GraphQL API can be queried directly by passing a raw GraphQL query to the `LinearGraphQLClient`: Copy const graphQLClient = linearClient.client; const cycle = await graphQLClient.rawRequest(` query cycle($id: String!) { cycle(id: $id) { id name completedAt } }`, { id: "cycle-id" } ); #### [](#custom-graphql-client) Custom GraphQL Client In order to use a custom GraphQL Client, the Linear SDK must be extended and provided with a request function: Copy import { LinearError, LinearFetch, LinearRequest, LinearSdk, parseLinearError, UserConnection } from "@linear/sdk"; import { DocumentNode, GraphQLClient, print } from "graphql"; import { CustomGraphqlClient } from "./graphql-client"; /** Create a custom client configured with the Linear API base url and API key */ const customGraphqlClient = new CustomGraphqlClient("https://api.linear.app/graphql", { headers: { Authorization: apiKey }, }); /** Create the custom request function */ const customLinearRequest: LinearRequest = ( document: DocumentNode, variables?: Variables ) => { /** The request must take a GraphQL document and variables, then return a promise for the result */ return customGraphqlClient.request(print(document), variables).catch(error => { /** Optionally catch and parse errors from the Linear API */ throw parseLinearError(error); }); }; /** Extend the Linear SDK to provide a request function using the custom client */ class CustomLinearClient extends LinearSdk { public constructor() { super(customLinearRequest); } } /** Create an instance of the custom client */ const customLinearClient = new CustomLinearClient(); /** Use the custom client as if it were the Linear Client */ async function getUsers(): LinearFetch { const users = await customLinearClient.users(); return users; } [PreviousErrors](/docs/sdk/errors) [NextWebhooks](/docs/sdk/webhooks) Last updated 3 years ago Was this helpful? --- # Working with the GraphQL API | Linear API * * * --- # Migrating from 1.x to 2.x | Linear API * * * Version 2 of the SDK introduces a new naming pattern for mutations. The action verb (create, update, delete, archive) should now precede the name of the model being mutated. In order to migrate from version 1.x of the SDK, all mutations usages need to be renamed. Here is an example with the mutation to update a `User`. Mutation in version 1.x Copy const me = await linearClient.viewer; if (me.id) { await linearClient.userUpdate(me.id, { displayName: "Alice" }); } should become: Mutation in version 2.x Copy const me = await linearClient.viewer; if (me.id) { await linearClient.updateUser(me.id, { displayName: "Alice" }); } [PreviousWebhooks](/docs/sdk/webhooks) [NextHow to upload a file to Linear](/docs/guides/how-to-upload-a-file-to-linear) Last updated 9 months ago Was this helpful? --- # How to upload a file to Linear | Linear API * * * --- # OAuth 2.0 Authentication | Linear API * * * --- # Managing Customers | Linear API * * * [](#data-models) Data Models --------------------------------- The Customer requests relies on two objects: `Customer` and `CustomerNeed`. ### [](#customer) `Customer` The `Customer` object represents an external company. #### [](#fields) Fields Field Type Description `id` string The id of the Customer in Linear. `name` string The name of the Customer. `logoUrl` string `domains` string\[\] The list of domains associated with the Customer. Only apex domains are supported. Domains associated with public email providers are not allowed. Only contains distinct values. `externalIds` string\[\] A list of external ids associated with the Customer in various external systems. Only contains distinct values. `slackChannelId` string The id of the Slack channel this customer has been associated with, if any. Requests coming from this channel will be automatically associated with this Customer. `revenue` number The annual revenue of the Customer, if available. `size` number The size of the Customer. This could be used to either represent the number of employees, or the number of seats or subscriptions this Customer represents. Note that some integrations like Intercom will default to using this field to represent a number of employees. `tier` uuid Id of the CustomerTier associated with the Customer. `status` uuid Id of the CustomerStatus associated with the Customer. ### [](#customerneed) `CustomerNeed` The `CustomerNeed` object represents a customer request. It is attached to an `Issue` and optionally, to a specific `Customer`. #### [](#fields-1) Fields Field Type Description `customerId` uuid The id of the Customer in Linear this request is associated with. Can be `undefined` if the request is not attached to a Customer. `issueId` uuid The id of the `Issue` this request is associated with. `attachmentId` uuid The id of the `Attachment` this request is associated with. All requests with a source URL are backed by an `Attachment`. `priority` number Whether the customer request is important or not. 0 = Not important, 1 = Important. `body` string An optional content for the request, in markdown format. `creatorId` uuid The id of the Linear user who created the request. [](#create-a-customer) Create a Customer --------------------------------------------- The [`customerCreate`](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Mutation#customerCreate) mutation allows to create a customer through the API: **Basic customer:** Request Copy mutation CustomerCreate($input: CustomerCreateInput!) { customerCreate(input: $input) { success customer { id } } } { "input": { "name": "ACME" } } Response Copy { "data": { "customerCreate": { "success": true, "customer": { "id": "bc993fb1-bf7e-48ab-aff9-2d014cfc5842" } } } } **Customer with metadata** The following request creates a customer with additional metadata: * `tierId` is the id of Linear tier this customer is associated with. Tiers must be created with mutation `customerTierCreate` before they can be used with a Customer * `externalIds` contains a list of all unique external identifiers for this Customer. It can be the uuid of the Customer in your own database or any other external system you are getting customer information from. Setting this value allows to refer to this customer by this specific external id when creating customer requests rather than the Linear defined Customer id. * `domains` must contain unique values and cannot contain any public email provider. Request Copy mutation CustomerCreate($input: CustomerCreateInput!) { customerCreate(input: $input) { success customer { id } } } { "input": { "name": "ACME", "domains": ["acme.com"], "externalIds": ["cus-acme-12345"] "tierId": "e7cfd601-5582-41aa-8f52-77685191e221", "revenue": 1250, "size": 12, } } [](#create-a-customer-request) Create a Customer Request ------------------------------------------------------------- Once a Customer has been created, it is possible to use it to create a Customer Request on a specific issue. Request Copy mutation CustomerNeedCreate($customerNeedCreateInput: CustomerNeedCreateInput!) { customerNeedCreate(input: $customerNeedCreateInput) { success } } { "input": { "issueId": "be65eaec-314c-412c-baf8-4787f2b85bdd", "body": "Content of the request", "customerId": "021d4b25-b0a9-4f02-85ad-bd840ac9c3ee" } } Alternatively, it is possible to use one of the external id of the Customer to attach the request to the Customer: Copy mutation CustomerNeedCreate($customerNeedCreateInput: CustomerNeedCreateInput!) { customerNeedCreate(input: $customerNeedCreateInput) { success } } { "input": { "issueId": "be65eaec-314c-412c-baf8-4787f2b85bdd", "body": "Content of the request", "customerExternalId": "cus-acme-12345" } } When passing an `url` to the input, an `Attachment` will be created and attached to the request: Request Copy mutation CustomerNeedCreate($customerNeedCreateInput: CustomerNeedCreateInput!) { customerNeedCreate(input: $customerNeedCreateInput) { success } } { "input": { "issueId": "be65eaec-314c-412c-baf8-4787f2b85bdd", "body": "Content of the request", "customerExternalId": "cus-acme-12345", "attachmentUrl": "https://conversations.support.com/conversations/12345" } } Response Copy { "data": { "customerNeedCreate": { "success": true, "need": { "id": "26c9dbf5-44b5-4e00-9978-c779467d87f0", "body": "Content of the request", "attachment": { "url": "https://conversations.support.com/conversations/12345" } } } } } [](#update-a-customer) Update a Customer --------------------------------------------- The [`customerUpdate`](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Mutation?query=customerUpdate#customerUpdate) mutation allows to update a Customer. Linear will automatically attempt to match created Customers against customers in your integrations (Intercom, Zendesk or Front). When a match happens, the Customer object in Linear becomes managed by the integration and only certain fields can be updated: **Customers managed by Intercom:** Copy "ownerId", "statusId", "logoUrl" **Customers managed by Zendesk or Front:** Copy "ownerId", "statusId", "logoUrl", "revenue", "size", "tierId" If you are building an integration with Linear and need to manage or operate with Customers that could have been created by other integrations, please refer to the [Upsert Customers](/docs/guides/managing-customers#upsert-customers) section [](#upsert-customers) Upsert Customers ------------------------------------------- Customers get created automatically when an issue is created from one of the supported sources: Intercom, Zendesk, Front, Email intake or Slack. If you are building an integration that will create Customer Requests and associate them with Customers, it is likely that you will encounter the case where the request should be attached to a Customer that's been already created by another integration. As there is a uniqueness constraint on `domains`, you cannot create a duplicate Customer in Linear. Instead, you must re-use this existing Customer. The `customerUpsert` mutation allows you to append domains and external identifiers to an existing Customer and contribute to its definition. Starting with the following Customer, assuming it has been created by the Intercom integration Existing customer Copy { "name": "ACME", "domains": ["acme.com"], "externalIds": ["intercom-654536452625"] "revenue": 1250, "size": 12, } It is possible to upsert this existing Customer with your integration's own definition: Request Copy mutation CustomerUpsert($customerUpsertInput: CustomerUpsertInput!) { customerUpsert(input: $customerUpsertInput) { success customer { domains name externalIds } } } { "customerUpsertInput": { "domains": ["acme.com", "acme.dev"], "externalId": "own-customer-id", } } Response Copy { "data": { "customerUpsert": { "success": true, "customer": { "domains": [\ "acme.com",\ "acme.dev"\ ], "name": "ACME", "externalIds": [\ "cus-acme-12345",\ "own-customer-id"\ ] } } } } Your integration's Customer id has been appended to the list of `externalIds` of the Customer, after it matched by domain. If there had been no match, a new Customer would have been created. This allows your integration to push Customers into Linear without having to check ahead of time if there is already an existing Customer for the same domains. Either the Customer does not exist and will be created, either it exists already and will be reused. The `domains` and `externalIds` will be merged. Your integration can then attach Customer Requests using your integration's customer id: Copy mutation CustomerNeedCreate($customerNeedCreateInput: CustomerNeedCreateInput!) { customerNeedCreate(input: $customerNeedCreateInput) { success } } { "input": { "issueId": "be65eaec-314c-412c-baf8-4787f2b85bdd", "body": "Content of the request", "customerExternalId": "own-customer-id", "attachmentUrl": "https://conversations.support.com/conversations/12345" } } [PreviousHow to create new issues using linear.new](/docs/guides/how-to-create-new-issues-using-linear.new) [NextBrand Guidelines](/docs/brand/brand-guidelines) Last updated 2 months ago Was this helpful? The URL of the Customer logo. A logo will be downloaded automatically if `domains` are provided for the Customer. See for instructions on how to upload a file. [How to upload a file to Linear](/docs/guides/how-to-upload-a-file-to-linear) --- # How to create new issues using linear.new | Linear API * * * The following links trigger the creation of a new Linear issue in any browser and you can add query parameters after any of them to pre-fill issue fields: * [http://linear.app/new](https://linear.app/new) * [http://linear.app/team//new](https://linear.app/team/%3Cteam%20ID%3E/new) * [http://linear.new](https://linear.new/) For example, you can assign new issues to a specific person, set an estimate, add labels, or combine multiple parameters with instructions in the description to create a template for a user to fill out. ### [](#generate-a-pre-filled-link) Generate a pre-filled link You can open any issue page in Linear, open command menu using `Cmd/Ctrl + K` and then select `_Copy pre-filled create issue URL to clipboard_`. This will copy the URL to the clipboard, allowing you to quickly create a URL with parameters that will pre-fill the new issue creation state with the same properties set on the issue page. ### [](#supported-parameters) Supported parameters We support the following query parameters: #### [](#title-and-description) `title` and `description` Use to `+` indicate an empty space in the keyword. For example, `?title=My+Title` meaning "My Title". Examples: * `https://linear.new?title=My+issue+title&description=This+is+my+issue+description` * `https://linear.app/team/LIN/new?title=Issues+with+scrolling+the+modal+window` #### [](#status) `status` Indicates the initially selected status of the issue. Can be set by `UUID` or name of the workflow status. When using `UUID` you also need to indicate a corresponding team key. Examples: * `https://linear.new?status=Todo` * `https://linear.app/team/MOB/new?status=` #### [](#priority) `priority` Indicates the initially selected priority of the issue. Possible values are `high`, `urgent`, `medium` and `low` Examples: * `https://linear.new?priority=urgent` * `https://linear.app/team/LIN/new?title=Important+Bug&priority=high` #### [](#assignee) `assignee` Indicates the initially selected assignee of the issue. Possible values: `UUID` of the specific user, display name (shortname) or a full name of a user Examples: * `https://linear.new?assignee=john` * `https://linear.new?assignee=Erin+Baker` * `https://linear.app/team/LIN/new?assignee=` #### [](#estimate) `estimate` Indicates the initially selected estimate of the issue. Applicable only when the targeted team has estimates feature enabled. Can be set by their point number e.g. `estimate=4` T-shirt sizes have the following point values: `No priority (0)`, `XS (1)`, `S(2)`, `M (3)`, `L (5)`, `XL (8)`, `XXL (13)`, `XXXL (21)` Examples: * `https://linear.app/team/LIN/new?estimate=2` #### [](#cycle) `cycle` Indicates the initially selected cycle of the issue. Applicable only when the targeted team has cycles feature enabled. Can be set by `UUID`, cycle number or a name of a cycle. Examples: * `https://linear.app/team/MOB?cycle=36` * `https://linear.app/team/EU/new?cycle=focus+on+bugs` * `https://linear.app/team/EU/new?cycle=` #### [](#label-or-labels) `label` or `labels` Indicates the initially selected labels on the issue. If the label doesn't exist in the workspace, it will be ignored. Examples: * `https://linear.app/team/LIN/new?label=bug` * `https://linear.new?labels=bug,android,comments` #### [](#project) `project` Indicates the initially selected project in the issue. Requires `team` to be specified in the URL. Can be set by `UUID` or the name of the project. Examples: * `https://linear.app/team/LIN/new?project=Project+page+improvements` * `https://linear.app/team/MOB/new?project=` #### [](#project-milestone) `project milestone` Indicates the initially selected project milestones in the issue. Requires `team` and `project` to be specified in the URL. Can be set by `UUID` or the name of the project milestone. Examples: * `https://linear.app/team/LIN/new?project=Project+page+improvements&projectMilestone=Beta` * `https://linear.app/team/MOB/new?project=&projectMilestone=` #### [](#template) template Indicates a template that will be used for the issue creation. Issue templates are a powerful tool to set multiple issue properties at once. Also, it's possible to specify sub-issues when using an issue template. Can be set by `UUID` of the issue template. Examples: * `https://linear.app/team/LIN/new?template=` You can easily generate an issue template URL in the app. Go to your team's templates (under team settings), hover over the template you want to use, and then click the "Copy URL" action in the menu. [PreviousHow to upload a file to Linear](/docs/guides/how-to-upload-a-file-to-linear) [NextManaging Customers](/docs/guides/managing-customers) Last updated 9 months ago Was this helpful? --- # Brand Guidelines | Linear API * * * ---