A developer re-measuring padding by eye in Figma's Inspect panel is the modern version of eyeballing a blueprint with a ruler. The Figma REST API exposes everything inside a design file programmatically - components, styles, frames, images, and variables - which means asset export, spec extraction, and token sync can run as code instead of as a conversation between two people staring at the same screen. This guide builds an automated handoff pipeline, and flags what's changed in the API recently: a new cap on token lifetimes, an HTTPS-only requirement, and a rate-limit update.
1. Getting Started with the Figma REST API
All API calls are authenticated via a Personal Access Token (PAT) or an OAuth 2.0 token for team integrations. Generate a PAT from Figma → Account Settings → Security. Two changes from 2025 are worth knowing before wiring this into CI: as of April 2025, PATs can no longer be created without an expiry - the maximum is 90 days, so a token stored in a GitHub Actions secret needs a rotation reminder - and as of November 2024, plain HTTP requests to api.figma.com are rejected outright with a 403; every call must go over HTTPS.
# Base URL
https://api.figma.com/v1/
# Auth header
X-Figma-Token: <your-personal-access-token>
# or
Authorization: Bearer <oauth-token>
Your Figma file key is the long string in the file URL: figma.com/design/FILE_KEY/.... When creating the token, scope it narrowly - file_content:read covers everything in this guide except Variables, which needs its own file_variables:read scope and an Enterprise-plan seat. The older files:read scope still works but is deprecated and will eventually stop being issued.
2. Key Endpoints for Design Handoff
GET /v1/files/:file_key
Returns the entire file as a JSON document tree. Every frame, component, group, and text layer is nested here with its full property set: fills, strokes, constraints, auto-layout settings, effects, and more.
const file = await figmaFetch(`/files/${FILE_KEY}`);
const canvas = file.document.children[0]; // first page
GET /v1/files/:file_key/nodes?ids=...
Fetch only specific nodes by ID instead of downloading the entire file. Essential for large files - pass a comma-separated list of node IDs to get just the frames you care about.
const ids = ['1:23', '1:45', '2:100'].join(',');
const { nodes } = await figmaFetch(`/files/${FILE_KEY}/nodes?ids=${ids}`);
GET /v1/files/:file_key/components
Returns all published components with their nodeId, name, description, and containingFrame. Use this to build a component inventory or validate that every component has a description before shipping.
const { meta } = await figmaFetch(`/files/${FILE_KEY}/components`);
const undocumented = meta.components.filter(c => !c.description);
console.log('Components missing descriptions:', undocumented.map(c => c.name));
GET /v1/images/:file_key?ids=...&format=svg
Exports nodes as images. Supported formats: jpg, png, svg, pdf. This is how you automate icon and illustration export instead of doing it by hand in Figma.
const iconIds = ['3:10', '3:11', '3:12'].join(',');
const { images } = await figmaFetch(
`/images/${FILE_KEY}?ids=${iconIds}&format=svg&svg_simplify_stroke=true`
);
// images = { '3:10': 'https://...svg', '3:11': '...', '3:12': '...' }
// Download each SVG
for (const [nodeId, url] of Object.entries(images)) {
const svg = await fetch(url).then(r => r.text());
fs.writeFileSync(`icons/${nodeId}.svg`, svg);
}
GET /v1/files/:file_key/styles
Lists all published styles (colors, text, effects, grids). Each style has a nodeId you can look up in the file document to get the full resolved value. Treat this endpoint as legacy scaffolding: teams building new systems are consolidating color and text styles into Variables, since Variables carry per-Mode values and Styles don't. If a file still has both, reconcile them before automating around either one.
GET /v1/files/:file_key/variables/local
Returns all Variables and Variable Collections with per-Mode values - but only if the org is on an Enterprise plan and the token carries file_variables:read. See the companion article on multi-brand design systems for a full endpoint walkthrough, including the extended-collections feature Figma shipped in November 2025.
3. Automating Design Spec Extraction
Instead of developers reading specs in Inspect mode, generate a machine-readable spec document from the API. Here's a function that walks a Figma node tree and extracts the spacing and color properties from every frame and component:
// extract-specs.mjs
function extractNodeSpecs(node, depth = 0) {
const indent = ' '.repeat(depth);
const specs = {};
if (node.absoluteBoundingBox) {
specs.size = {
w: Math.round(node.absoluteBoundingBox.width),
h: Math.round(node.absoluteBoundingBox.height)
};
}
if (node.paddingLeft !== undefined) {
specs.padding = {
top: node.paddingTop,
right: node.paddingRight,
bottom: node.paddingBottom,
left: node.paddingLeft
};
}
if (node.itemSpacing !== undefined) {
specs.gap = node.itemSpacing;
}
if (node.cornerRadius !== undefined) {
specs.borderRadius = node.cornerRadius;
}
if (node.fills?.length) {
const solidFill = node.fills.find(f => f.type === 'SOLID' && f.visible !== false);
if (solidFill) {
const { r, g, b, a } = solidFill.color;
const toHex = n => Math.round(n * 255).toString(16).padStart(2, '0');
specs.background = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
if (a < 1) specs.backgroundOpacity = Math.round(a * 100) + '%';
}
}
return { name: node.name, type: node.type, specs };
}
4. Generating a Living Design Inventory
Run this on a schedule and push the output to your repo. Developers get a queryable JSON inventory of every component and its properties - no Figma account needed, no digging through Inspect panel:
// scripts/generate-inventory.mjs
import fs from 'fs/promises';
import path from 'path';
const FILE_KEY = process.env.FIGMA_FILE_KEY;
const API_TOKEN = process.env.FIGMA_TOKEN;
const figmaFetch = async (endpoint) => {
const res = await fetch(`https://api.figma.com/v1${endpoint}`, {
headers: { 'X-Figma-Token': API_TOKEN }
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
};
async function run() {
const [fileData, componentData, stylesData] = await Promise.all([
figmaFetch(`/files/${FILE_KEY}`),
figmaFetch(`/files/${FILE_KEY}/components`),
figmaFetch(`/files/${FILE_KEY}/styles`)
]);
const inventory = {
generatedAt: new Date().toISOString(),
fileName: fileData.name,
lastModified: fileData.lastModified,
components: componentData.meta.components.map(c => ({
id: c.node_id,
name: c.name,
description: c.description,
containingFrame: c.containing_frame?.name
})),
styles: stylesData.meta.styles.map(s => ({
id: s.node_id,
name: s.name,
type: s.style_type,
description: s.description
}))
};
await fs.mkdir('design-inventory', { recursive: true });
await fs.writeFile(
path.join('design-inventory', 'inventory.json'),
JSON.stringify(inventory, null, 2)
);
console.log(`✅ Inventory generated: ${inventory.components.length} components, ${inventory.styles.length} styles`);
}
run().catch(console.error);
5. Full CI/CD Design Handoff Pipeline
Wire all of the above into a GitHub Actions workflow that runs on every push to main and also on a daily schedule. The pipeline:
- Pulls the latest file data from Figma
- Generates the component inventory
- Exports all icon SVGs
- Extracts Variables and builds design tokens via Style Dictionary
- Opens a PR if anything changed
Figma updated its REST API rate limits in November 2025, tiering endpoints by cost - file reads and variable reads sit in a higher tier than lightweight metadata calls. A workflow that fetches the whole file, the component list, and every icon export on every push can bump into those limits on a large file; cache aggressively (see the tips below) rather than widening the schedule as the first fix.
# .github/workflows/design-sync.yml
name: Design Sync
on:
push:
branches: [main]
schedule:
- cron: '0 7 * * 1-5' # weekdays at 7 AM
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- name: Fetch Figma inventory
run: node scripts/generate-inventory.mjs
env:
FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}
FIGMA_FILE_KEY: ${{ secrets.FIGMA_FILE_KEY }}
- name: Export icons
run: node scripts/export-icons.mjs
env:
FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}
FIGMA_FILE_KEY: ${{ secrets.FIGMA_FILE_KEY }}
- name: Build design tokens
run: node scripts/fetch-figma-variables.mjs && npx style-dictionary build
env:
FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}
FIGMA_FILE_KEY: ${{ secrets.FIGMA_FILE_KEY }}
- name: Open PR if files changed
uses: peter-evans/create-pull-request@v6
with:
title: 'chore: sync design assets from Figma'
body: 'Automated design handoff - components, icons, and tokens updated from Figma.'
branch: 'design-sync/auto'
commit-message: 'chore: sync design assets from Figma'
6. Practical Tips for Production
- Cache file responses - the
GET /files/:keyresponse is large. Cache it by the file'slastModifiedtimestamp and skip re-fetching if unchanged. - Use webhooks for real-time sync - register a
FILE_UPDATEwebhook on your Figma team to trigger your pipeline the moment a designer publishes changes, instead of waiting for a scheduled run. - Scope your tokens with variable scopes - when fetching variables, filter by
scopesto separate color tokens from spacing tokens before running them through Style Dictionary. - Version your token output - commit the generated token files to Git so developers have a changelog of every design decision over time.
- Add a validation step - before merging the auto-PR, run a script that checks for broken aliases, missing descriptions, or tokens that fall outside your defined naming conventions.
- Rotate PATs on a schedule - personal access tokens now expire after a maximum of 90 days (Figma removed non-expiring tokens in April 2025). Store the token in a secret manager with a rotation reminder, or move to OAuth 2.0 with refresh tokens for a pipeline that shouldn't need manual intervention.
Key Takeaways
- The Figma REST API makes design files queryable assets, not static specs locked inside a browser tab.
- Automate the boring parts: icon export, token extraction, inventory generation - all can run in CI without human intervention.
- A living inventory JSON means every developer can answer "what components exist and what are their properties?" without ever opening Figma.
- Pairing the API with Style Dictionary and GitHub Actions creates a continuous design-to-code pipeline where design decisions propagate to production automatically.
- Recent API changes - the 90-day PAT cap, HTTPS-only requests, and the November 2025 rate-limit update - are easy to miss and will break a pipeline built before they shipped.
The teams that do this well stop treating design and engineering as two separate disciplines that hand off to each other - and start treating the Figma file as a shared source of truth that both sides read and write directly.


