Skip to main content
Praveen Manchi
Journal№ 02
  • Figma
  • REST API
  • Design Handoff
  • Automation
  • CI/CD
By Praveen Manchi · Mar 14, 2026 · 13 min

Streamlining Design Handoff with the Figma REST API

Design handoff still degrades into a developer re-measuring padding by eye in Figma’s Inspect panel. This guide scripts an automated handoff pipeline against the Figma REST API, and flags what changed recently: a 90-day cap on personal access tokens, an HTTPS-only requirement, and a November 2025 rate-limit update.

Streamlining Design Handoff with the Figma REST API

Figma REST APIAutomated Handoff

  1. Scoped Tokens

    Personal access tokens now expire after at most 90 days and every call must use HTTPS. Scope tokens narrowly with file_content:read.

  2. Spec Extraction

    Walk the node tree to pull size, padding, gap, radius and fills into a machine-readable spec instead of reading Inspect by eye.

  3. Living Inventory

    A scheduled script combines file, components and styles endpoints into a queryable JSON inventory pushed to the repo.

  4. CI/CD Sync

    A GitHub Actions workflow exports icons, builds tokens with Style Dictionary and opens a PR whenever anything changed.

[ HANDOFF PIPELINE ]

Design handoff that runs as code, not a meeting

The Figma REST API exposes components, styles, frames, images and variables programmatically. A CI pipeline fetches file data, generates an inventory, exports SVG icons and builds design tokens, caching responses to stay inside the tiered rate limits.

  1. Fetch file - GET /v1/files/:key
  2. Inventory - components + styles
  3. Export icons - images endpoint, SVG
  4. Build tokens - Style Dictionary
  5. Open PR - only if files changed
On this page

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:

  1. Pulls the latest file data from Figma
  2. Generates the component inventory
  3. Exports all icon SVGs
  4. Extracts Variables and builds design tokens via Style Dictionary
  5. 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/:key response is large. Cache it by the file's lastModified timestamp and skip re-fetching if unchanged.
  • Use webhooks for real-time sync - register a FILE_UPDATE webhook 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 scopes to 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.

References & Further Reading