Connect Figma
Connect Figma to Minion with a personal access token or a custom OAuth app, then read files and comments from api.figma.com.
Connecting Figma lets your agents and packages read files, comments and components through the Figma REST API at api.figma.com, without the credential ever reaching their code.
Minion has no built-in Figma preset. You have two options:
- A personal access token saved as a secret. This is the simplest path and the one to pick for most automations. It works as long as the token is valid, and you control its expiry.
- A custom OAuth app. Pick this when you want people to approve access through Figma's consent screen, or you don't want a long-lived token tied to your Figma account settings. Expect to reconnect when Figma's access token expires (see Troubleshooting).
Both options use the same API host, api.figma.com. This guide covers the Figma REST API only, not Figma's own MCP server.
Before you start
- You need to be an owner or admin of your Minion organization to save a secret or add an OAuth app. Members can use them once they exist.
- You need a Figma account with access to the files you want to read.
- For the OAuth option, you need access to Figma's developer apps page at www.figma.com/developers/apps .
Option A: personal access token
Create the token on Figma
- In Figma, open Settings > Security and find Personal access tokens. Click Generate new token.
- Name the token and choose an expiration.
- Pick only the scopes you need. For reading files and comments:
current_user:read,file_content:read,file_metadata:read,file_comments:read. - Generate the token and copy it. Figma shows it once.
Add it in Minion
- Open app.minion.codes/app/secrets .
- Set Name to
figmaPat. Secret names use letters, digits and underscores only, so a second token would befigmaPatWork, notfigmaPat-work. - Paste the token into Value.
- Set Approved hosts to
api.figma.com. - Optionally set Expires (UTC, optional) to the date you chose on Figma, so Minion stops sending it after that.
- Click Save secret.
Never paste the token into chat. See /docs/secrets .
Check it works
Run this with execute. It only reads your Figma profile. Figma takes a personal access token in the X-Figma-Token header.
export default async function main() {
const res = await fetch('https://api.figma.com/v1/me', {
headers: { 'X-Figma-Token': '{{secret:figmaPat}}' },
})
if (!res.ok) throw new Error(`Figma returned ${res.status}: ${await res.text()}`)
const me = await res.json()
return { handle: me.handle }
} Use it in a package
const FIGMA = 'https://api.figma.com/v1'
export default async function main({ fileKey }) {
const res = await fetch(`${FIGMA}/files/${fileKey}?depth=1`, {
headers: { 'X-Figma-Token': '{{secret:figmaPat}}' },
})
if (!res.ok) throw new Error(`Figma returned ${res.status}`)
const file = await res.json()
return { name: file.name, pages: file.document.children.map((p) => p.name) }
} The file key is the part of a Figma URL after /design/ or /file/. Say in the package's README that it needs the figmaPat secret approved for api.figma.com.
Option B: custom OAuth app
Create the OAuth app on Figma
- Open www.figma.com/developers/apps and create a new app.
- Add this redirect URL, exactly:
https://app.minion.codes/oauth/callback - Enable the scopes you will request. A read-mostly set is
current_user:read,file_content:read,file_metadata:read,file_comments:read,file_versions:read,library_content:read,folders:read. Addfile_comments:writeonly if code should post comments. - Copy the Client ID and Client secret.
Request only scopes the app has enabled. Figma rejects the authorization with "Invalid scopes" if you ask for a scope the app doesn't offer, a deprecated scope such as files:read, or an Enterprise-only scope on a plan without it.
Add it in Minion
- Open app.minion.codes/app/integrations .
- In the OAuth apps card, set Provider to Custom (any OAuth 2 provider).
- Set App name (optional) to
figma. Don't leave it empty: a custom app's name defaults tocustom. - Paste the Client ID and Client secret.
- Set Default scopes (optional) to the scopes you enabled on Figma, separated by spaces.
- Set API hosts (custom: required) to
api.figma.com. - Open Custom provider endpoints and fill in:
- Authorization URL:
https://www.figma.com/oauth - Token URL:
https://api.figma.com/v1/oauth/token - Revocation URL (optional): leave empty.
- Client authentication: HTTP Basic. Figma's docs ask for the client ID and secret in a Basic header.
- Keep Use PKCE (S256) checked. Figma supports S256.
- Authorization URL:
- Click Save app.
Connect
- In the Integrations card, choose
figmaunder OAuth app. - Leave Name (optional) empty to call the integration
figma. - Leave Extra scopes (optional) empty to use the app's default scopes.
- Click Connect and approve on Figma.
Figma sends you back to the Integrations page with Connected "figma". and status connected. The account column stays empty for custom apps. That is expected.
An agent can start the same flow with minion.integrations.connect({ provider: 'figma' }). An owner or admin opens the returned connectUrl while signed in to Minion.
Check it works
Run this with execute. It only reads your Figma profile.
export default async function main() {
const res = await minion.integrations.fetch({ name: 'figma', url: 'https://api.figma.com/v1/me' })
if (!res.ok) throw new Error(`Figma returned ${res.status}: ${await res.text()}`)
const me = await res.json()
return { handle: me.handle }
} Use it in a package
package.json:
{
"name": "@your-org/figma-tools",
"exports": { "./pages": "./src/pages.ts" }
} src/pages.ts:
import { createAuthenticatedFetch } from 'minion:runtime'
const figma = createAuthenticatedFetch('figma')
export default async function main({ fileKey }) {
const res = await figma(`https://api.figma.com/v1/files/${fileKey}?depth=1`)
if (!res.ok) throw new Error(`Figma returned ${res.status}`)
const file = await res.json()
return { name: file.name, pages: file.document.children.map((p) => p.name) }
} Save and publish it with minion.packages.save and publish: true. Publishing needs an owner or admin and a README.md that says the package needs the figma integration. Other code can only import a published version. Then call it from execute:
import pages from 'minion:@your-org/figma-tools/pages'
export default async function main() {
return pages({ fileKey: 'YOUR_FILE_KEY' })
} Replace your-org with your organization slug. See /docs/packages for saving and publishing.
Troubleshooting
- Figma says the redirect URI is invalid. The app's redirect URL must be exactly
https://app.minion.codes/oauth/callback. No trailing slash. - "Invalid scopes" on Figma's consent page. The scopes in Minion don't match the scopes enabled on the Figma app. Make them match, save the app in Minion, and click Connect again.
- 403 from an endpoint after a successful connect. The token lacks the scope that endpoint needs. Enable the scope on the Figma app or token, add it in Minion, and reconnect (or create a new token and rotate the secret).
- A secret or integration error about the host. If you see an error saying integration "figma" may not send its token to a host, or
Secret "figmaPat" isn't approved for <host>, the URL isn't onapi.figma.com. Check the URL. For the secret, approve the host on the Secrets page. - Secret "figmaPat" has expired, or 403 Invalid token. The token expired on Figma or in Minion. Create a new token and use Rotate on the Secrets page.
- The OAuth integration turns error and says it needs a person to reconnect. Minion refreshes tokens by calling the Token URL with a refresh grant. Figma documents a separate refresh endpoint, so refresh may not succeed when the access token expires. You also see this when you revoke the app's access in Figma. Minion fires an
integration.auth.failedevent with areconnectUrl. Click Connect on the Integrations page. A package can subscribe tointegration.auth.failedto alert you. If this keeps happening, use a personal access token instead.
Disconnecting the integration in Minion deletes the token in Minion but doesn't revoke it at Figma. Remove the app's access in your Figma settings too.
For how OAuth integrations work in general, see /docs/oauth . For setting up several connections at once, see /docs/integration-bootstrap .