Creative MCP

Linear issue visuals

An agent generates on-brand stills with Brandiseer for Linear issues and projects (issue covers, project banners, cycle or roadmap stills, changelog or launch stills); a person then attaches those files in the Linear UI, or an agent with a Linear API key places the public imageUrl into issue or project markdown (or uploads bytes via Linear fileUpload). Brandiseer does not create Linear issues or projects and does not sync.

~25 to 40 min setup
  • MCP
  • REST
  • Cursor
  • Cowork

Brandiseer + Linear (+ Cursor or Claude Cowork)

Cover: an on-brand still landing on a Linear issue

By the end of this guide you will have a repeatable way to make on-brand stills for Linear issues and projects. An agent in Cursor or Claude Cowork calls Brandiseer, gets an imageUrl for an issue cover, a project banner, a cycle or roadmap still, or a changelog or launch still, and that file is placed on the issue or project.

Brandiseer owns the brand memory and the still. Linear owns the issues, the projects, and where the file sits. Brandiseer returns imageUrl only. It does not create Linear issues or projects, and it does not sync. There is no Brandiseer Linear app in this guide, and there is no Linear MCP in this guide. The default handoff is a person: download the file (or open imageUrl) and attach it in the Linear UI. Optionally, an agent that already holds a Linear API key can place that public URL in issue or project markdown, or upload the file bytes with Linear fileUpload and a server-side PUT. A person still decides which issue or project is final.

People who keep campaign records in Airtable should use Airtable record visuals. People who brief stills inside Slack should use Slack creative desk. Recurring captions and Buffer scheduling should use Social media automation. Schedules and webhooks in Zapier, Make, or n8n should use Always-on creative automation. Webflow site stills should use Webflow site visuals. WordPress Media Library stills should use WordPress site visuals. Framer site stills should use Framer site visuals. This guide does not recreate any of those.

Who it's for

  • Product, marketing, and ops teams who already track work in Linear and need an on-brand still on an issue or project without opening a separate design tool for every ticket
  • Agencies that keep one Brandiseer Brand ID per client and one Linear workspace (or a named set of teams) per client
  • Operators who can download an imageUrl and attach it in the Linear UI, or who already give an agent a Linear API key and want that agent to place the public URL in markdown

Ideal customer profile: a team whose issues and projects already live in Linear, whose brand already lives in Brandiseer, and whose missing piece is a brand-safe still plus a clear handoff into the Linear UI or into markdown on an issue or project that already exists.

Why Brandiseer in this stack

Linear is where the issue and the project live. Cursor or Cowork is the agent that turns a brief into a still. Brandiseer stores the logo and visual identity once, then applies that memory on every still. The prompt carries subject, layout, composition, and on-image text. It does not restate colours, fonts, mood, or visual style.

A launch thread drifts when every cover is a one-off export from a general image tool. Brandiseer keeps the still on the same brand as the rest of the work. Linear still decides which issue, which project, and which markdown field hold the file.

This is a different job from the sibling guides:

Example strip: issue cover, project banner, cycle or roadmap still, and changelog or launch still

Stack

LayerToolRole
Brand visualsBrandiseer MCP and/or REST v2Generate on-brand stills. MCP uses Authorization: Bearer. REST uses X-API-Key: bds_...
Agent host (pick one)CursorRead the brief, call Brandiseer, return imageUrl and a placement note. Optional Linear GraphQL write
Agent host (pick one)Claude CoworkSame contract as Cursor for interactive issue and project stills
Work trackerLinearIssues, projects, and file or markdown image placement. A person attaches by default

Stack diagram: Cursor or Cowork, Brandiseer, and Linear issue attachments

You only need one agent host. The Brandiseer contract is the same on both. There is no Brandiseer Linear app that drops a still onto an issue. The handoff is an image URL, a download, or a Linear GraphQL call that you perform with Linear's own API. This guide does not use a Linear MCP. Do not invent one.

Brandiseer docs: Developers · guide/docs · guide/image · Settings → API

Linear docs: GraphQL · How to upload a file · Attachments · SDK · API and webhooks

Endpoint tables and HTTP contracts are in API reference at the end of this guide.

Linear (placement, not generation)

Three practical paths. Use the first one until a GraphQL write is actually needed. A fourth Linear feature, attachmentCreate, is a different thing. It is listed here so an agent does not use it as a file upload.

  1. Linear UI (default). Download the file or open imageUrl. On the issue or project the human named, attach that file in the Linear UI (drag it into the description, or upload it from the editor). Brandiseer stops at the URL. Linear stores its own file.
  2. Markdown public URL (easiest API path). Include ![alt](https://public-imageUrl) in an issue description, a comment body, or project content. Send that markdown with issueUpdate, commentCreate, or projectUpdate to POST https://api.linear.app/graphql. Linear fetches the public image and stores it in Linear's private storage. imageUrl must be publicly fetchable. See How to upload a file. This is a Linear call with the human's key. It is not a Brandiseer push.
  3. fileUpload, then PUT (server-side). Call the fileUpload(contentType, filename, size) mutation. PUT the raw bytes to uploadFile.uploadUrl with Content-Type and every header in uploadFile.headers. Then use uploadFile.assetUrl in markdown on the issue, comment, or project. The PUT must run server-side. Linear's content security policy blocks a browser PUT. See the same upload guide.
  4. attachmentCreate is a link card, and it is out of scope for this still. It links an external resource (title, subtitle, url, iconUrl) the way a GitHub pull request card does. iconUrl is a small png or jpg icon. It is not the way to attach a Brandiseer PNG. Do not call it for these stills. Docs: Attachments.

issueUpdate replaces description. projectUpdate replaces content. Read the current markdown first when the human needs the existing text kept, then send the full markdown plus the new image. commentCreate adds a comment and leaves the description in place. That is the safer write when the issue body must stay as it is.

Project icon on projectUpdate is an icon name. It is not a custom cover upload, and there is no documented field in this guide that sets a project cover image URL for a Brandiseer still. For a project banner or thumbnail, put the markdown image in project content, or have a person upload the file in the Linear UI.

Cycle and roadmap stills, and changelog or launch stills, use the same handoff. Generate the still, then attach it in the UI or place the markdown image on the issue or project the human named. This guide does not document a cycle cover field or a changelog image field.

Files Linear has fetched or accepted live in Linear's private storage. Viewing those Linear-hosted URLs outside Linear requires authentication. Keep the Brandiseer imageUrl as the public URL until Linear has stored the file. See GraphQL (Accessing Images) and the upload guide.

How it works

Architecture, from brief to a still sitting on a Linear issue or project:

  1. You store the brand once in Brandiseer (logo and reference visuals teach the look).
  2. The agent host (Cursor or Cowork) has Brandiseer MCP or REST, the Brand ID (bds_c_...), and the standing instructions in this guide.
  3. You name the slot: issue cover, project banner, cycle or roadmap still, or changelog or launch still. The agent rewrites that brief into subject, layout, and on-image text, then sets aspectRatio, resolution, mode, and useLogo as fields.
  4. Brandiseer returns imageUrl. The agent reports the URL, the crop, the mode, and a one-line placement note (which issue or project, and whether the file is for the UI, a comment, a description, or project content). It does not claim Linear changed unless a Linear call you actually performed returned success.
  5. A person attaches the file in the Linear UI, or an agent with a Linear API key writes the public imageUrl into markdown (or uploads the bytes). A person reviews the issue or project when the creative is ready.

Workflow infographic: brief, Brandiseer still, imageUrl, attach in Linear, human reviews

Connect the workflow

Give the agent access to Brandiseer

Create an API key for the runnable setup, or connect the hosted MCP endpoint when your agent supports MCP.

What you need

  • Company website URL, if the brand is not in Brandiseer yet
  • Brandiseer account with at least one finished brand profile
  • API key (bds_...) from Settings → API or Developers
  • Brand ID (bds_c_...) for every brand the agent may call
  • Credits for the stills you actually generate (see the table in Limits & FAQ)
  • Cursor or Claude Cowork, with Brandiseer MCP or a way to call REST
  • A Linear workspace you can edit, and the issue or project the still belongs on
  • The issue id (UUID or identifier such as BLA-123) or the project UUID for any GraphQL write. Copy a model UUID from Linear with the command menu: Cmd/Ctrl+K, then "Copy model UUID". See GraphQL. Do not guess ids
  • Optional, only for GraphQL writes: a Linear personal API key from Settings → Account → Security & access, or an OAuth access token. Personal keys can be full access or limited to Read, Write, Admin, Create issues, or Create comments, and they can be limited to specific teams. See API and webhooks. A description or project content write needs write access on that team. A key limited to Create comments can add a comment image and cannot rewrite an issue description
  • Store the Linear key in the agent secret store. Never in an issue, a prompt you commit, Slack, or git

Setup

Stage 1: Add your brand on Brandiseer

  1. Go to brandiseer.com. In the homepage input, enter your company website URL and click Try My Brand.
  2. Wait about one minute while Brandiseer analyses the site and picks a logo and visuals.
  3. Review those visuals carefully. They teach Brandiseer your look. Every later Linear still is influenced by what you leave here.
  4. Remove anything off-brand (partner logos, stock, irrelevant screenshots). Add campaign creatives or product photography that match the look you want.
  5. Click Add Brand, sign up if prompted, then wait about 30 seconds for the brand profile to finish.

Agencies repeat this once per client. Do not point two client workspaces at one Brand ID.

Stage 2: API key and Brand ID

  1. Open brandiseer.com/developers or Settings → API.
  2. Create an API key (bds_...). Put it in the agent host's secret store. Do not paste it into a Linear issue, a page, Slack, or a git repo.
  3. Copy the Brand ID (bds_c_...).
  4. Confirm the key with GET https://brandiseer.com/api/v1/credits and GET https://brandiseer.com/api/v1/brands, header X-API-Key. The credits JSON includes credits. The brands list includes each brand id.
  5. Optional reachability check, no key required: GET https://brandiseer.com/api/v1/health returns { "status": "ok" }.

Read guide/docs and guide/image once while you write the standing instructions. Those pages are the live contract if a field name ever moves.

Stage 3: Connect Brandiseer to the agent host

Use Cursor or Cowork. One host is enough.

A. MCP

  1. Add the Brandiseer MCP server at https://brandiseer.com/api/mcp.
  2. Authenticate with Authorization: Bearer and the API key, in the secret field your host provides.
  3. Confirm the tool list shows brandiseer_list_brands, brandiseer_check_credits, and brandiseer_generate_image. If a fourth Brandiseer tool appears, ignore it until guide/docs documents it.

B. REST

  1. On Developers, open View Docs and use Copy for LLM if you want the reference inside the session.
  2. Tell the agent to prefer POST https://brandiseer.com/api/v2/generate or POST https://brandiseer.com/api/v2/generate/async with X-API-Key.
  3. If Design mode times out, switch that call to async and poll GET https://brandiseer.com/api/v2/jobs/:jobId every 3 to 5 seconds.

Stage 4: Save the standing instructions

  1. Paste the System prompt from this guide into the Cursor rule or Cowork instructions that will run Linear stills.
  2. Fill the defaults the prompt asks for: Brand ID, default crops per slot, resolution, mode, and whether useLogo is on when the brief is silent.
  3. If GraphQL writes are in scope, also note that the Linear key lives in the secret store. Leave issue and project ids blank until the human names them.
  4. Ask the agent to call brandiseer_list_brands (or GET /api/v1/brands) and confirm the Brand ID exists before the first paid still.

Stage 5: Smoke test an issue cover in the Linear UI

  1. Ask for one issue cover with a layout-only brief (the Spring cover in Example flows).
  2. Confirm the agent checked the brand, avoided colour and mood words, and returned imageUrl plus crop and mode.
  3. Open the Linear issue, attach that file in the UI, and confirm the image shows.
  4. Leave the issue unmarked as approved if you are only proving the handoff.
  5. Send a second brief that only says "make the cover premium and moody". The agent should ask what is physically in the frame instead of generating.

Stage 6: Optional Linear GraphQL write

Skip this stage if people will attach stills by hand. Use it only when a Linear API key is already in the secret store and the human named the issue or project.

  1. Create a personal API key in Linear at Settings → Account → Security & access. Give it access only to the teams the human named. For a description or project content write, the key needs write access. For a comment-only image, Create comments is enough. See API and webhooks. An OAuth access token is the other supported credential. Brandiseer is not the OAuth app.
  2. Send every Linear call as POST https://api.linear.app/graphql with Content-Type: application/json. A personal API key uses the header Authorization: <API_KEY> with no Bearer prefix. An OAuth access token uses Authorization: Bearer <token>. See GraphQL.
  3. Markdown public URL. Prefer commentCreate when the issue description must stay untouched. Set body to markdown that includes ![alt](https://public-imageUrl). To put the image in the issue body, read description first, then issueUpdate with the full markdown. To put a project banner in the project document, read content first, then projectUpdate with the full markdown. Do not send icon unless the human asked to change the icon name. Never set icon to imageUrl. Linear fetches the public URL into private storage. If the URL is not publicly fetchable, this path fails.
  4. Bytes upload. Only from a server. Call fileUpload with contentType, filename, and size (bytes). PUT the file to uploadFile.uploadUrl. Set Content-Type, then copy every { key, value } in uploadFile.headers onto that PUT. The official SDK example also sets Cache-Control: public, max-age=31536000 before applying the returned headers, so a returned header of the same name wins. A browser PUT is blocked by Linear's content security policy. A 403 on the PUT usually means a required header was left off. After the PUT succeeds, place uploadFile.assetUrl in markdown with commentCreate, issueUpdate, or projectUpdate. See How to upload a file.
  5. Confirm the mutation success flag is true and the response errors array is absent. Then stop. A person reviews the issue or project. Do not call attachmentCreate to attach the PNG. Do not treat a Linear-hosted asset URL as a public CDN link.

Prompting rules for Brandiseer images

When the agent builds prompt:

  • Keep subject, layout, composition, and on-image text (what the words say and where they sit).
  • Never send brand colours, fonts, mood, or visual style. Brand memory already applies those. Style words fight the brand and produce off-brand stills.
  • Put crop in aspectRatio, quality in mode, pixel size in resolution, and logo on/off in useLogo. Those are body fields, not prompt prose.
  • Map the Linear slot to a documented ratio. Issue cover or attachment still: 16:9 or 1:1, matching the frame the human named. Project banner or thumbnail (via project content markdown or the UI): 16:9. Cycle or roadmap still: 16:9. Changelog or launch still: 16:9. Portrait, only when the brief truly needs it: 9:16. Brandiseer also offers 4:3. None of the slots above require it. Use 4:3 only when the human asks for that frame.
  • Prefer design for finished Linear stills. fast is the API default if you omit mode, and it is the right fallback if a design job fails once.
  • Prefer 1K or 2K for these stills unless the brief asks for 4K.
  • Set useLogo true only when the brief asked for a logo (or your default says so). Titles that the Linear issue title will hold should stay off the image. Say "empty region" for that area instead of describing a font.
  • Full rules: Prompt compatibility.

System prompt

Paste this into Cursor or Cowork as the standing instructions for Linear stills. Replace the placeholders before the first generate.

Copy-ready
You generate on-brand stills for Linear issues and projects. Brandiseer
returns an image URL. You do not create Linear issues or projects, you do
not sync Linear, and you do not claim an issue or project changed unless a
Linear call you actually performed returned success.

Brandiseer:
- MCP: https://brandiseer.com/api/mcp with Authorization: Bearer.
  Tools you may call: brandiseer_list_brands, brandiseer_check_credits,
  brandiseer_generate_image. Do not invent tool names.
- REST: X-API-Key: bds_... (secret store only).
  Prefer POST https://brandiseer.com/api/v2/generate
  or POST https://brandiseer.com/api/v2/generate/async
  then poll GET https://brandiseer.com/api/v2/jobs/:jobId every 3 to 5 seconds
  until data.status is done or failed, or about 10 minutes have passed.
- Live contract: https://brandiseer.com/guide/docs and
  https://brandiseer.com/guide/image
- Brandiseer does not host a Linear integration. There is no native
  Brandiseer to Linear app in this guide, and there is no Linear MCP.

Defaults for this workspace (fill in once):
- Brand ID: bds_c_...
- Issue cover aspectRatio: ask if the frame is not known (16:9 or 1:1)
- Project banner aspectRatio: 16:9
- Cycle or roadmap still aspectRatio: 16:9
- Changelog or launch still aspectRatio: 16:9
- Portrait aspectRatio, only if the brief truly needs it: 9:16
- mode: design
- resolution: 2K (use 1K if file size or speed matters)
- useLogo when the brief is silent: false
- Multi-brand map, if any: brand name to bds_c_...

On every request:
1. Resolve brandId from the brief or the default. If more than one brand
   exists and the brief does not name one, ask. Never fall through to
   another client's Brand ID.
2. Check credits before a batch (brandiseer_check_credits or
   GET https://brandiseer.com/api/v1/credits). A single still can proceed
   if the last known balance covers the mode and resolution in the table
   you were given. On 402, stop.
3. Rewrite the brief into subject, layout, composition, and on-image text.
   Strip colours, fonts, mood, and visual style. If stripping leaves no
   physical subject, ask what is in the frame. Do not generate.
4. Choose aspectRatio from the slot. Put resolution, mode, and useLogo in
   API fields, not in the prompt.
5. Generate one still. Return imageUrl, aspectRatio, mode, resolution,
   useLogo, and a placement note: which issue or project the human named,
   and which path (UI attach, comment, issue description, or project
   content).
6. Do not tell the user Linear changed unless a Linear GraphQL call you
   actually performed returned success true and no errors array.

Linear placement, only when the human already asked for it:
- Default path: the human downloads imageUrl (or opens it) and attaches it
  in the Linear UI. You do not claim that happened.
- Endpoint: POST https://api.linear.app/graphql
  Personal API key header: Authorization: <API_KEY> (no Bearer).
  OAuth access token header: Authorization: Bearer <token>.
  Keys: Linear Settings, Account, Security & access.
  Docs: https://linear.app/developers/graphql
  https://linear.app/docs/api-and-webhooks
- Markdown public URL (easiest API path): put
  ![alt](https://public-imageUrl) in issue description (issueUpdate),
  a comment (commentCreate), or project content (projectUpdate).
  Linear fetches that public image into private storage. imageUrl must be
  publicly fetchable.
  Docs: https://linear.app/developers/how-to-upload-a-file-to-linear
- issueUpdate replaces description. projectUpdate replaces content. Read
  the current markdown first when existing text must stay, then send the
  full markdown plus the new image. commentCreate adds a comment and leaves
  the description in place.
- fileUpload then PUT (server-side only): fileUpload(contentType, filename,
  size) then PUT bytes to uploadFile.uploadUrl with Content-Type and every
  returned header. Use uploadFile.assetUrl in markdown afterwards. Linear
  CSP blocks a client-side PUT.
- Do not use attachmentCreate to attach the PNG. attachmentCreate links an
  external resource (title, subtitle, url, iconUrl), like a GitHub PR card.
  Docs: https://linear.app/developers/attachments
- Project icon on projectUpdate is an icon name. Do not write imageUrl into
  icon. There is no documented "set project cover image URL" field. Put
  banners in project content markdown, or leave the upload to the human.
- Do not create issues or projects. Do not invent a Linear MCP.
- Linear-hosted file URLs are private. Keep imageUrl as the public URL
  until Linear has stored the file.

Out of scope:
- Slack replies, Buffer scheduling, and Zapier, Make, or n8n scenarios.
- Airtable, Webflow, WordPress, and Framer handoffs (use those guides).
- Creating Linear issues, projects, cycles, or changelog entries.
- Legal approval of claims, prices, or offers printed on the image.
- GET https://brandiseer.com/api/v1/usage (browser session only).

Prompt rewriter

Use this when a brief arrives as messy notes. It does not call Brandiseer. It only cleans text before you put it in prompt. The word list is a starting filter, not an official dictionary. If a phrase still describes colour, type, mood, or look, delete it by hand.

Copy-ready
// Local rewriter for a Cursor or Cowork helper script.
// Keeps subject, layout, composition, and on-image text.
// Strips colour, type, mood, and visual-style language.

const STYLE_WORDS =
  /\b(colou?rs?|palette|hex|fonts?|typefaces?|typography|serif|sans-serif|moods?|vibes?|aesthetics?|minimalist|luxury|playful|cinematic|pastel|neon|gradients?|moody|elegant|premium|on-brand)\b/gi;

const SLOT_TO_RATIO = {
  issue: "16:9",
  "issue cover": "16:9",
  cover: "16:9",
  attachment: "16:9",
  "attachment still": "16:9",
  "16:9": "16:9",
  project: "16:9",
  banner: "16:9",
  "project banner": "16:9",
  thumbnail: "16:9",
  "project thumbnail": "16:9",
  cycle: "16:9",
  roadmap: "16:9",
  "cycle still": "16:9",
  "roadmap still": "16:9",
  changelog: "16:9",
  launch: "16:9",
  "changelog still": "16:9",
  "launch still": "16:9",
  square: "1:1",
  "1:1": "1:1",
  portrait: "9:16",
  "9:16": "9:16",
  "4:3": "4:3",
};

function rewriteBrandiseerPrompt(input) {
  const headline = String(input.headline || "").trim();
  const subject = String(input.subject || input.brief || "").trim();
  const slotKey = String(input.slot || input.crop || "").trim().toLowerCase();
  const aspectRatio = SLOT_TO_RATIO[slotKey] || "";

  let prompt = [subject, headline ? `On-image text: "${headline}".` : ""]
    .filter(Boolean)
    .join(" ");

  prompt = prompt
    .replace(/#[0-9a-f]{3,8}\b/gi, " ")
    .replace(STYLE_WORDS, " ")
    .replace(/\s+/g, " ")
    .trim();

  return {
    prompt,
    aspectRatio,
    needsSubject: prompt.length < 12 || !aspectRatio,
  };
}

If needsSubject is true, ask the human for a subject and a slot. Do not POST. The cover and attachment keys above default to 16:9 only so a named cover can proceed. If the human has not confirmed 16:9 or 1:1 for an issue cover, ask before you generate. Use 9:16 only when the brief truly needs a portrait. Use 4:3 only when the human names that frame.

Add-ons

Add these after the cover smoke test passes. They do not change Brandiseer's image contract, and they do not give Brandiseer a Linear sync.

Approval before the issue is treated as final

After imageUrl is on the issue or project, a named reviewer checks the still in Linear. Until that happens, leave the issue status on its current workflow state. Brandiseer does not approve legal claims, prices, or offers. Brandiseer also does not know your workflow state names. Do not invent one, and do not move the issue to Done as part of generating a still.

One brand per client workspace

Keep a table: Linear workspace name, team ids the key may touch, secret name, and bds_c_.... Require a named issue or project on every GraphQL write when more than one workspace exists. If the name misses, stop and list the workspaces you configured. Confirm Brand IDs with GET /api/v1/brands. Never fall through to another client's Brand ID or another workspace's key.

Credit soft-limit

When credits is under a threshold you choose (for example 40), switch new Linear stills to mode: fast and resolution: 1K, or skip them. Fast at 1K costs 4 credits. An explicit "run anyway" from the human can override the skip. The threshold is yours, not a Brandiseer default.

Placement log

Keep a short note next to the work: issue or project id, slot, brandId, mode, aspectRatio, resolution, imageUrl, and whether a person attached it, a markdown URL write placed it, or a fileUpload PUT placed it. Brandiseer does not write this log. Linear does not send it back to Brandiseer.

Handoff to the sibling guides

Do not embed an Airtable base, a Buffer schedule, a Slack desk, an always-on HTTP scenario, Webflow, WordPress, or Framer inside this Linear guide.

Optional motion (not the issue MVP)

Video is image-to-video only. Generate the still first, then send its public imageUrl to POST https://brandiseer.com/api/v2.1/video/generate. Poll GET https://brandiseer.com/api/v2.1/video/jobs/:jobId. Video jobs use status completed and a videoUrl, which is a different contract from image jobs (done and imageUrl). Read guide/video for tier, duration, and resolution before you enable this. Video credits are per second and are not the still table below. A video failure does not undo the still. A Linear issue can hold the video file only after a person, or a Linear markdown or fileUpload write, puts that file on the issue or project. Leave this off unless the brief explicitly asks to animate.

Limits & FAQ

Does Brandiseer update the Linear issue?

No. Brandiseer returns imageUrl. A person attaches that file in the Linear UI, or an agent with a Linear API key places the public URL in markdown (or uploads the bytes through fileUpload). None of those paths create an issue or a project, and none of them sync Linear back to Brandiseer.

Is there a Brandiseer app inside Linear?

No. There is no Brandiseer Linear integration in this guide. Do not look for a native "send to Linear" button on the Brandiseer side.

Do I need a Linear MCP?

No. This guide uses the Linear UI, or the documented GraphQL API with a personal API key or an OAuth access token. Do not invent a Linear MCP tool. Brandiseer still only returns the still.

Which Linear calls are in this guide?

Only calls confirmed on Linear's developer docs:

  • Endpoint: POST https://api.linear.app/graphql on GraphQL
  • Personal API key header: Authorization: <API_KEY> (no Bearer). OAuth access token header: Authorization: Bearer <token>
  • Markdown image on an existing issue description: issueUpdate with description. This replaces the description. Read it first if the current text must stay
  • Markdown image on a new comment: commentCreate with issueId and body. This leaves the description in place
  • Markdown image on existing project content: projectUpdate with content. This replaces the content. Read it first if the current text must stay. Do not set icon to an image URL
  • Direct byte upload: fileUpload(contentType, filename, size), then a server-side PUT to uploadFile.uploadUrl, then uploadFile.assetUrl in markdown. Docs: How to upload a file

attachmentCreate is documented on Attachments and is out of scope here. It creates an external link card. It does not upload the Brandiseer PNG.

Issue ids, project ids, and team access come from your workspace. This guide does not invent a "set issue cover" Brandiseer endpoint, and it does not create issues or projects.

Linear's upload guide also shows a base64 data URL inside markdown. This guide does not use that path. Use a public imageUrl or fileUpload.

Where do the keys live?

The Brandiseer key (bds_...) and the Linear personal API key or OAuth token live in the agent host's secret store. Never in a Linear issue, a Slack message, a prompt you commit, or a workflow export in git. Create the Linear personal key at Settings → Account → Security & access. Limit it to the teams the human named. A description or project write needs write access. A comment-only image can use a key limited to Create comments. Rotate either secret if it leaks. Poll a Brandiseer job with the same Brandiseer key that submitted it. Send the Linear personal key as Authorization: <API_KEY> with no Bearer word. Send an OAuth token as Authorization: Bearer <token>.

What do stills cost?

Credits per still. Failures refund. Async submits deduct immediately and refund if the job fails.

Mode1K2K4K
Fast468
Design568
Pro6810

402 means not enough credits. Pricing context: brandiseer.com/pricing.

Why is the still off-brand?

Almost always because the prompt contained colours, fonts, mood, or visual style. Strip them and generate again. See guide/image.

Why does the crop look wrong on the issue?

Brandiseer aspect ratios are 1:1, 16:9, 9:16, and 4:3. Generate the issue cover at 16:9 or 1:1, and generate the project banner, the cycle or roadmap still, and the changelog or launch still at 16:9. Use 9:16 only for a real portrait slot. Crop in Linear if that view uses a tighter frame.

How long can I poll?

About 10 minutes, then the Brandiseer job expires. Poll every 3 to 5 seconds. Stop on done or failed as well. Do not start a second job for the same slot while the first is still open, or you will pay twice.

Which generate URL should new Linear work use?

POST /api/v2/generate when the agent host waits long enough. POST /api/v2/generate/async when it does not. Legacy POST /api/v1/generate remains for older callers and only supports Fast and Pro via proMode boolean.

Do Linear-hosted image URLs stay public?

No. After Linear fetches a public markdown image, or after a fileUpload PUT, the file lives in Linear's private storage. Those Linear URLs need authentication outside the Linear app. The Brandiseer imageUrl is the public URL you hand to Linear. It must be publicly fetchable for the markdown path. See How to upload a file and Accessing Images on GraphQL.

What if the markdown image does not appear?

Check four things. The imageUrl must be public, because Linear fetches it. The personal key must be Authorization: <API_KEY> with no Bearer prefix, or the OAuth token must be Authorization: Bearer <token>. The key must be allowed to write that team (or to create comments, if you only added a comment). The mutation response must have success: true and no errors array. GraphQL can return HTTP 200 with errors while part of the payload is empty. If you replaced description or content without reading it first, restore the previous markdown and send the full text plus the image. A 403 on the byte PUT means the request missed headers from uploadFile.headers or ran in the browser. Do not tell the human the issue updated when success is false or errors is present.

Can I set a project cover image URL?

This guide has no documented Linear field that sets a custom project cover from a Brandiseer URL. projectUpdate.icon is an icon name. Put the banner in project content as a markdown image, or upload the file in the Linear UI.

Does the agent approve claims on the image?

No. If the still has a price, a discount, or a legal line, a person reads it before the issue or project is treated as final. Brandiseer does not review the claim.

Demo / motion

Motion brief only. This is not an embed, and there is no walkthrough URL yet.

Working title: Linear issue visuals Length: 45 to 75 seconds

Story beats:

  1. A person asks Cursor or Cowork for an issue cover with a layout-only brief
  2. The agent calls Brandiseer (MCP or REST v2) and shows an on-brand still
  3. imageUrl is attached on a Linear issue in the UI
  4. Optional cut: a comment is created with the public URL through Linear GraphQL, still unapproved
  5. A human reviews the issue only after the still looks right next to the title

On screen: the agent transcript with the prompt (no colour or mood words), one finished still, and the Linear issue with the image visible. Blur the Brandiseer API key and the Linear API key. Do not show a fake video player in the guide.

Example flows

Flow A: Issue cover, attached by a person

The brief: "Issue cover for the spring launch ticket. Product on the left. Leave the right half empty. The issue title will hold the headline. No logo on the image. 16:9."

The agent selects the default brandId, sets aspectRatio to 16:9, mode to design, resolution to 2K, and useLogo to false, and returns imageUrl. You download the file and attach it on that issue in the Linear UI. You mark the issue reviewed later, after the still looks right next to the title.

Flow B: Issue covers for a set of tickets

The brief lists four issues. Each one says what is in the frame and that the cover is square, with the issue title kept in Linear rather than on the image. The agent generates four stills, all 1:1, and labels them with the issue identifier. You attach each file on its issue. Four Design 2K stills cost 24 credits if all succeed. Failures refund.

Flow C: Comment image via public URL, not yet approved

The issue already exists. You have asked for an API write, and the Linear key is in the secret store with access to that team. The brief names a 16:9 cover. The agent generates the still, then commentCreate with a body of ![Spring cover](https://public-imageUrl). Linear fetches the public file. You open the issue, confirm the image shows on the comment, and leave approval for a separate step. The issue description is unchanged because this path adds a comment. If you had used issueUpdate instead, the agent would have read description first and sent the existing markdown plus the image.

Flow D: Project banner, plus a cycle still and a launch still

The brief: "Project banner for the launch project. Product left of centre. On-image text Spring launch on the right. 16:9."

The agent uses 16:9 and says so in the placement note. You attach the file in the project UI, or the agent reads project content and projectUpdates the full markdown with ![Project banner](https://public-imageUrl). It does not write imageUrl into icon. icon stays whatever icon name the project already has, unless you asked to change that name.

A cycle or roadmap still, and a changelog or launch still, use the same 16:9 generation and the same handoff onto the issue or project you named. There is no cycle cover URL field and no changelog image field in this guide.

Flow E: Credit check before an issue set

Before Flow B, the agent calls brandiseer_check_credits or GET https://brandiseer.com/api/v1/credits and reads credits. Four Design 2K stills need 24 credits. If the balance is lower, it stops or switches the batch to fast at 1K (4 credits each) only if you asked for that fallback. GET /api/v1/usage is a browser session endpoint. Do not call it from the agent.

Flow F: Style-only brief

The brief: "Make the issue cover feel premium and moody."

The agent does not generate. It asks which issue, what is physically in the frame, whether any words must appear on the image, whether the frame is 16:9 or 1:1, and whether the logo is on. Colour, font, and mood stay out of the prompt.

Related use cases

Links

Source

Public Use case README for Linear issue visuals. Brandiseer MCP and REST v2 image generation from Cursor or Claude Cowork, then a human attaches imageUrl in the Linear UI, or an agent with a Linear API key places that public URL in issue or project markdown (or uploads bytes with fileUpload and a server-side PUT). Aligned with guide/docs and guide/image (1 October 2026), and with Linear's GraphQL, file upload, and attachments docs. Brandiseer returns imageUrl only. It does not create Linear issues or projects and does not sync. Distinct from Airtable record visuals, Slack creative desk, Buffer social scheduling, Zapier/Make/n8n always-on HTTP, Webflow site visuals, WordPress site visuals, and Framer site visuals.

API reference

Wiring reference for agents and HTTP modules. The sections above are the human path.

MCP (interactive stills in Cursor or Cowork)

Connect https://brandiseer.com/api/mcp with Authorization: Bearer and the API key. The only tools this guide uses are:

  • brandiseer_list_brands
  • brandiseer_check_credits
  • brandiseer_generate_image

Do not invent extra Brandiseer tool names. If the host needs explicit mode, aspectRatio, or async polling, use REST v2 below. MCP does not edit Linear. Do not invent a Linear MCP.

REST (prefer v2 images)

Send X-API-Key: bds_... on every authenticated Brandiseer call. There is no official SDK. Any HTTP client the agent host can run is enough.

CallWhat you get
POST https://brandiseer.com/api/v2/generateSync still. 200 body { "imageUrl", "creditsUsed" }. Design mode often takes 40 to 70 seconds.
POST https://brandiseer.com/api/v2/generate/async202 body { "data": { "jobId" } } (ids look like bds_job_...). Credits are deducted when you submit.
GET https://brandiseer.com/api/v2/jobs/:jobIdPoll every 3 to 5 seconds with the same API key. data.status becomes done or failed. Jobs expire after about 10 minutes. Failures refund credits.
POST https://brandiseer.com/api/v1/generateLegacy only. proMode boolean. Fast and Pro only. Prefer v2.
GET https://brandiseer.com/api/v1/creditsJSON includes credits, the current balance for that key.
GET https://brandiseer.com/api/v1/brandsBrands for the key. Each brand id looks like bds_c_....
GET https://brandiseer.com/api/v1/health{ "status": "ok" }. No API key.

Image body fields: brandId, prompt, optional aspectRatio (1:1, 16:9, 9:16, 4:3), resolution (1K, 2K, 4K), mode (fast, design, or pro; default fast if you omit it), useLogo.

Sync status codes you should branch on: 200 success, 400 bad body, 401 bad key, 402 not enough credits, 404 brand not found, 500 server error.

Done job body: { "data": { "status": "done", "imageUrl", "creditsUsed", "referenceImage" } }. Hand data.imageUrl to Linear. Failed job body: { "data": { "status": "failed", "error": { "code", "message" } } }.

Cursor and Cowork can wait on a sync Design call when the session allows it. If the HTTP client times out under about 60 seconds, use async and poll. Do not point Linear at the Brandiseer MCP URL.

Linear GraphQL (placement only)

Send every call to POST https://api.linear.app/graphql with Content-Type: application/json.

CredentialHeader
Personal API keyAuthorization: <API_KEY> (no Bearer)
OAuth access tokenAuthorization: Bearer <token>

Create personal keys at Settings → Account → Security & access. A key can be full access or restricted to Read, Write, Admin, Create issues, or Create comments, and it can be limited to specific teams. See API and webhooks and GraphQL.

Check the response errors array before you treat a call as successful. GraphQL can return HTTP 200 with errors. Also require success: true on these mutations.

CallWhat you get
query issue(id:)Current issue. id may be a UUID or an identifier such as BLA-123. Read description before an issueUpdate that must keep existing text.
issueUpdate(id, input: { description })Replaces the issue description with the markdown you send. Include ![alt](public imageUrl) in that markdown.
commentCreate(input: { issueId, body })Adds a comment. body is markdown. This leaves description in place.
query project(id:)Current project. Use the project UUID from Copy model UUID. Read content before a projectUpdate that must keep existing text. icon is an icon name.
projectUpdate(id, input: { content })Replaces project content with the markdown you send. Do not pass icon unless the human asked to change the icon name. Never set icon to imageUrl.
fileUpload(contentType, filename, size)Returns uploadFile.uploadUrl, uploadFile.assetUrl, and uploadFile.headers (key, value). size is the file size in bytes.

Markdown public URL is the easiest API path. Linear fetches the image at that public URL into private storage. Docs: How to upload a file.

Byte upload, server-side only:

  1. Call fileUpload. If success is false or uploadFile is missing, stop.
  2. PUT the raw bytes to uploadFile.uploadUrl. Set Content-Type to the same type you sent as contentType. The SDK example also sets Cache-Control: public, max-age=31536000. Then set every header from uploadFile.headers so those values are on the request.
  3. A browser PUT fails Linear's content security policy. Run the PUT on a server. A 403 usually means a returned header was missing.
  4. Place uploadFile.assetUrl in markdown with commentCreate, issueUpdate, or projectUpdate. That URL points at Linear's private storage.

attachmentCreate is not in the table above on purpose. It links an external resource with title, subtitle, url, and iconUrl. Use it for a GitHub-style link card if you have that separate job. Do not use it to attach a Brandiseer PNG. Docs: Attachments.

The TypeScript SDK can call the same mutations, including fileUpload(contentType, filename, size). This guide does not require the SDK. A GraphQL HTTP client is enough. The SDK does not remove the server-side PUT rule.

This guide does not call issueCreate or projectCreate. Linear's upload doc shows issueCreate as an example of a markdown image. Placement here targets issues and projects the human already named.

Field mapping

Brandiseer generate body:

Incoming fieldAPIRule
Slot (issue cover, project banner, cycle or roadmap, changelog or launch)aspectRatioIssue cover: 16:9 or 1:1. Project banner: 16:9. Cycle or roadmap: 16:9. Changelog or launch: 16:9. Portrait: 9:16 only when needed. 4:3 only when the human names it.
headlineinside prompt onlyOn-image text and placement. Not a separate body field. Leave it out when the Linear issue title will hold the headline.
brandIdbrandIdbds_c_.... Required.
modemodefast, design, or pro. Omit and the API uses fast. Prefer design for these stills.
useLogouseLogotrue or false.
subject or briefpromptAfter the rewriter. If the cleaned text has no subject, do not call Brandiseer.
resolutionresolution1K, 2K, or 4K. Prefer 2K or 1K for these stills.

Linear placement. The issue and project below are whatever the human named. Do not invent them.

Incoming valueLinearRule
Public Brandiseer imageUrl![alt](url) inside markdownRequired for the markdown path. Linear fetches this URL. It must be public.
Issue descriptionissueUpdate input descriptionReplaces the description. Read the current value first when text must stay.
CommentcommentCreate input body, plus issueIdAdds a comment. Does not replace the description.
Project documentprojectUpdate input contentReplaces project content. Read the current value first when text must stay.
Project iconprojectUpdate input iconIcon name only. Do not write imageUrl here. There is no project cover image URL field in this guide.
File bytesfileUpload contentType, filename, size, then PUTServer-side PUT to uploadFile.uploadUrl with every returned header plus Content-Type. Then use uploadFile.assetUrl in markdown.
External link cardattachmentCreateOut of scope for this PNG. Title, subtitle, url, and iconUrl. Not a file upload.

Example JSON

Use this body for both Brandiseer sync and async. Only the URL and the response change.

Copy-ready
{
  "brandId": "bds_c_...",
  "prompt": "Wide issue cover. Product centred on the left third. Empty right half for a title the Linear issue will hold. No on-image text.",
  "aspectRatio": "16:9",
  "resolution": "2K",
  "mode": "design",
  "useLogo": false
}

Sync: POST https://brandiseer.com/api/v2/generate. A Design 2K still costs 6 credits, so a success body looks like:

Copy-ready
{
  "imageUrl": "https://...",
  "creditsUsed": 6
}

Async: POST https://brandiseer.com/api/v2/generate/async. HTTP 202:

Copy-ready
{
  "data": {
    "jobId": "bds_job_..."
  }
}

Poll GET https://brandiseer.com/api/v2/jobs/bds_job_... with the same X-API-Key. When it finishes:

Copy-ready
{
  "data": {
    "status": "done",
    "imageUrl": "https://...",
    "creditsUsed": 6,
    "referenceImage": null
  }
}

When it fails (credits are refunded):

Copy-ready
{
  "data": {
    "status": "failed",
    "error": {
      "code": "...",
      "message": "..."
    }
  }
}

Ignore referenceImage for delivery. Linear gets imageUrl (or a file you downloaded from it).

Comment image. POST this JSON to https://api.linear.app/graphql. Use Authorization: <API_KEY> for a personal key. This adds a comment and leaves the issue description in place.

Copy-ready
{
  "query": "mutation CommentCreate($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success comment { id } } }",
  "variables": {
    "issueId": "BLA-123",
    "body": "![Spring cover](https://...)"
  }
}

Issue description. Read description first. This example sends existing text plus the new image, which replaces the description with that full string.

Copy-ready
{
  "query": "mutation IssueUpdate($id: String!, $description: String!) { issueUpdate(id: $id, input: { description: $description }) { success issue { id } } }",
  "variables": {
    "id": "BLA-123",
    "description": "Existing issue text.\n\n![Spring cover](https://...)"
  }
}

Project content. Read content first. Do not include icon in this input.

Copy-ready
{
  "query": "mutation ProjectUpdate($id: String!, $content: String!) { projectUpdate(id: $id, input: { content: $content }) { success project { id } } }",
  "variables": {
    "id": "PROJECT_UUID",
    "content": "Existing project text.\n\n![Project banner](https://...)"
  }
}

fileUpload. size is the byte length. If introspection shows a different scalar for size, use that scalar. The arguments stay contentType, filename, and size.

Copy-ready
{
  "query": "mutation FileUpload($contentType: String!, $filename: String!, $size: Int!) { fileUpload(contentType: $contentType, filename: $filename, size: $size) { success uploadFile { uploadUrl assetUrl headers { key value } } } }",
  "variables": {
    "contentType": "image/png",
    "filename": "spring-issue-cover.png",
    "size": 1828983
  }
}

Then PUT the bytes to uploadUrl from a server, with Content-Type: image/png and every returned header. Use assetUrl in the markdown examples above.

Slot prompts that stay layout-only:

SlotaspectRatioPrompt
Issue cover16:9Wide issue cover. Product centred on the left third. Empty right half for a title the Linear issue will hold. No on-image text.
Issue cover1:1Square issue cover. Single subject centred, full item in frame. Plain surrounding space. No on-image text.
Project banner16:9Landscape project banner. Subject left of centre. On-image text: "Spring launch" in the right third.
Cycle or roadmap still16:9Landscape planning still. Three work areas left to right. Empty caption band along the bottom. No on-image text.
Changelog or launch still16:9Landscape launch still. Product left of centre. On-image text: "Spring launch" in the right third.
Portrait9:16Portrait still. Single subject in the upper two thirds. Empty band along the bottom. No on-image text.

Run it with your brand

Connect the workflow to brand memory.

Save your visual identity in Brandiseer, then use MCP or the API to generate from the same brand across every supported workflow.