From 7d919fe0e02f227c3635e7caf3d724ea1471b65e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 04:31:15 +0000 Subject: [PATCH 1/2] feat(figma): add Figma API module Add @friggframework/api-module-figma under packages/v1-ready/figma with OAuth2 client, curated OpenAPI subset, capability manifest, Fenestra draft, HubSpot-style webhooks extension, and offline jest/spec-sync coverage. Co-authored-by: Sean Matthews --- package-lock.json | 99 ++ packages/v1-ready/figma/.env.example | 7 + packages/v1-ready/figma/LICENSE.md | 21 + packages/v1-ready/figma/README.md | 102 +++ packages/v1-ready/figma/api.js | 281 ++++++ packages/v1-ready/figma/defaultConfig.json | 8 + packages/v1-ready/figma/definition.js | 87 ++ .../figma/extensions/webhooks/handlers.js | 86 ++ .../figma/extensions/webhooks/index.js | 43 + .../figma/extensions/webhooks/lookup.js | 34 + .../extensions/webhooks/passcode-verifier.js | 34 + .../v1-ready/figma/figma.capabilities.json | 119 +++ packages/v1-ready/figma/figma.fenestra.yaml | 730 +++++++++++++++ packages/v1-ready/figma/figma.openapi.yaml | 862 ++++++++++++++++++ packages/v1-ready/figma/index.js | 11 + packages/v1-ready/figma/package.json | 32 + packages/v1-ready/figma/tests/api.test.js | 185 ++++ .../v1-ready/figma/tests/definition.test.js | 162 ++++ .../figma/tests/passcode-verifier.test.js | 35 + .../v1-ready/figma/tests/spec-sync.test.js | 46 + 20 files changed, 2984 insertions(+) create mode 100644 packages/v1-ready/figma/.env.example create mode 100644 packages/v1-ready/figma/LICENSE.md create mode 100644 packages/v1-ready/figma/README.md create mode 100644 packages/v1-ready/figma/api.js create mode 100644 packages/v1-ready/figma/defaultConfig.json create mode 100644 packages/v1-ready/figma/definition.js create mode 100644 packages/v1-ready/figma/extensions/webhooks/handlers.js create mode 100644 packages/v1-ready/figma/extensions/webhooks/index.js create mode 100644 packages/v1-ready/figma/extensions/webhooks/lookup.js create mode 100644 packages/v1-ready/figma/extensions/webhooks/passcode-verifier.js create mode 100644 packages/v1-ready/figma/figma.capabilities.json create mode 100644 packages/v1-ready/figma/figma.fenestra.yaml create mode 100644 packages/v1-ready/figma/figma.openapi.yaml create mode 100644 packages/v1-ready/figma/index.js create mode 100644 packages/v1-ready/figma/package.json create mode 100644 packages/v1-ready/figma/tests/api.test.js create mode 100644 packages/v1-ready/figma/tests/definition.test.js create mode 100644 packages/v1-ready/figma/tests/passcode-verifier.test.js create mode 100644 packages/v1-ready/figma/tests/spec-sync.test.js diff --git a/package-lock.json b/package-lock.json index c73a4a2e..81fd2cc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15887,6 +15887,10 @@ "resolved": "packages/v1-ready/fathom", "link": true }, + "node_modules/@friggframework/api-module-figma": { + "resolved": "packages/v1-ready/figma", + "link": true + }, "node_modules/@friggframework/api-module-fireflies": { "resolved": "packages/v1-ready/fireflies", "link": true @@ -45731,6 +45735,101 @@ "uuid": "dist/esm/bin/uuid" } }, + "packages/v1-ready/figma": { + "name": "@friggframework/api-module-figma", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@friggframework/core": "^2.0.0-next.107" + }, + "devDependencies": { + "@aws-sdk/client-scheduler": "^3.1113.0", + "dotenv": "^16.0.3", + "jest": "^28.1.3", + "js-yaml": "^4.1.0" + } + }, + "packages/v1-ready/figma/node_modules/@friggframework/core": { + "version": "2.0.0-next.118", + "resolved": "https://registry.npmjs.org/@friggframework/core/-/core-2.0.0-next.118.tgz", + "integrity": "sha512-SPxk7F5st3zQyB2p/orzS5Xs9PAo54RtZ82lfen6VW3Qf475GS5SlPGHttnSRjEQPlpfZ7piRs2Wqe3qjv9E3g==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0", + "@aws-sdk/client-kms": "^3.588.0", + "@aws-sdk/client-lambda": "^3.714.0", + "@aws-sdk/client-s3": "^3.588.0", + "@aws-sdk/client-sqs": "^3.588.0", + "@aws-sdk/client-ssm": "^3.588.0", + "@aws-sdk/s3-request-presigner": "^3.588.0", + "@hapi/boom": "^10.0.1", + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/context-async-hooks": "^2.9.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.220.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.220.0", + "@opentelemetry/resources": "^2.9.0", + "@opentelemetry/sdk-metrics": "^2.9.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.5", + "bson": "^4.7.2", + "chalk": "^4.1.2", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.22.2", + "express-async-handler": "^1.2.0", + "form-data": "^4.0.6", + "fs-extra": "^11.2.0", + "lodash": "4.18.1", + "lodash.get": "^4.4.2", + "node-fetch": "^2.6.7", + "serverless-http": "^2.7.0", + "uuid": "^11.1.1" + }, + "peerDependencies": { + "@prisma/client": "^6.19.3", + "prisma": "^6.19.3" + }, + "peerDependenciesMeta": { + "@prisma/client": { + "optional": true + }, + "prisma": { + "optional": true + } + } + }, + "packages/v1-ready/figma/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "packages/v1-ready/figma/node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "packages/v1-ready/figma/node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "packages/v1-ready/fireflies": { "name": "@friggframework/api-module-fireflies", "version": "1.0.0", diff --git a/packages/v1-ready/figma/.env.example b/packages/v1-ready/figma/.env.example new file mode 100644 index 00000000..090dbcb9 --- /dev/null +++ b/packages/v1-ready/figma/.env.example @@ -0,0 +1,7 @@ +FIGMA_CLIENT_ID= +FIGMA_CLIENT_SECRET= +# Comma- or space-separated scopes. Defaults cover user, files, comments, projects/folders, webhooks. +# FIGMA_SCOPE=current_user:read,file_content:read,file_metadata:read,file_comments:read,file_comments:write,projects:read,folders:read,folder_metadata:read,webhooks:read,webhooks:write +REDIRECT_URI=https://localhost:3000/redirect +# Shared passcode used when creating webhooks and verifying inbound deliveries +FIGMA_WEBHOOK_PASSCODE= diff --git a/packages/v1-ready/figma/LICENSE.md b/packages/v1-ready/figma/LICENSE.md new file mode 100644 index 00000000..c307ce69 --- /dev/null +++ b/packages/v1-ready/figma/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Left Hook + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/v1-ready/figma/README.md b/packages/v1-ready/figma/README.md new file mode 100644 index 00000000..c4f8210d --- /dev/null +++ b/packages/v1-ready/figma/README.md @@ -0,0 +1,102 @@ +# Figma API Module (`@friggframework/api-module-figma`) + +A [Frigg](https://friggframework.org) API module for the [Figma REST API](https://developers.figma.com/docs/rest-api/). +It wraps the curated subset of endpoints an integration needs: identity, files, +projects/folders, comments, and Webhooks V2. + +```javascript +const me = await this.figma.api.getMe(); +const file = await this.figma.api.getFile(fileKey, { depth: 1 }); +const { comments } = await this.figma.api.getComments(fileKey); +``` + +## Authentication + +Figma recommends **OAuth 2** (authorization code) for apps acting on behalf of +users. Token exchange and refresh use HTTP Basic +(`Authorization: Basic base64(client_id:client_secret)`): + +| Step | URL | +|---|---| +| Authorize | `https://www.figma.com/oauth` | +| Token | `https://api.figma.com/v1/oauth/token` | +| Refresh | `https://api.figma.com/v1/oauth/refresh` | + +- **Base URL:** `https://api.figma.com` +- Register an OAuth app at [figma.com/developers/apps](https://www.figma.com/developers/apps) +- **Public** OAuth apps require Figma review before end-user authorization; + **private** apps are limited to the associated team/org. +- Set `FIGMA_CLIENT_ID`, `FIGMA_CLIENT_SECRET`, and `REDIRECT_URI` (see `.env.example`). + +Personal and plan access tokens can also be used as a Bearer `access_token` for +local tooling; the Frigg definition is wired for the OAuth flow. + +## API Methods + +Method names match official OpenAPI `operationId`s +([figma/rest-api-spec](https://github.com/figma/rest-api-spec)). + +| Method | HTTP | Purpose | +|---|---|---| +| `getMe()` | `GET /v1/me` | Current user (id, handle, email, img_url). | +| `getFile(fileKey, query)` | `GET /v1/files/{key}` | File JSON document. | +| `getFileMeta(fileKey)` | `GET /v1/files/{key}/meta` | File metadata. | +| `getComments(fileKey, query)` | `GET /v1/files/{key}/comments` | List comments. | +| `postComment(fileKey, data)` | `POST /v1/files/{key}/comments` | Create a comment. | +| `deleteComment(fileKey, id)` | `DELETE /v1/files/{key}/comments/{id}` | Delete a comment. | +| `getTeamProjects(teamId)` | `GET /v1/teams/{id}/projects` | List projects (legacy). | +| `getProjectFiles(projectId, query)` | `GET /v1/projects/{id}/files` | Files in a project. | +| `getProjectMeta(projectId)` | `GET /v1/projects/{id}/meta` | Project metadata. | +| `getTeamFolders(teamId)` | `GET /v2/teams/{id}/folders` | List folders. | +| `getFolderFiles(folderId)` | `GET /v2/folders/{id}/files` | Files in a folder. | +| `getFolderMeta(folderId)` | `GET /v2/folders/{id}/meta` | Folder metadata. | +| `getWebhooks(query)` | `GET /v2/webhooks` | List webhooks by context or plan. | +| `postWebhook(data)` | `POST /v2/webhooks` | Create a webhook. | +| `getWebhook(id)` | `GET /v2/webhooks/{id}` | Get a webhook. | +| `putWebhook(id, data)` | `PUT /v2/webhooks/{id}` | Update a webhook. | +| `deleteWebhook(id)` | `DELETE /v2/webhooks/{id}` | Delete a webhook. | +| `getTeamWebhooks(teamId)` | `GET /v2/teams/{id}/webhooks` | List team webhooks. | +| `getWebhookRequests(id)` | `GET /v2/webhooks/{id}/requests` | Recent delivery attempts. | + +### Skipped (not in this curated subset) + +Images/renders, versions, components/styles, variables, dev resources, activity +logs, library analytics, and other enterprise/admin surfaces. Add them when an +integration needs them — always from the official OpenAPI, never invented. + +There is **no update-comment** endpoint in the public REST API (only create/delete). + +## Webhooks extension + +`extensions/webhooks/` is a HubSpot-style Tier 3 receiver: + +- `POST /webhooks` → verify `body.passcode` against `FIGMA_WEBHOOK_PASSCODE` → enqueue resolve +- Worker resolves tenant when `figma_user_id` is present, then emits `FIGMA_WEBHOOK` + +Figma verifies deliveries via the passcode field in the JSON body (not a +signature header). See +[webhooks security](https://developers.figma.com/docs/rest-api/webhooks-security/). + +## Capability manifest & Fenestra + +- `figma.capabilities.json` — `capability-manifest/v0.1` describing auth and + objects (`user`, `file`, `project`, `folder`, `comment`, `webhook`) with + tagged fields. Every declared method maps to an `Api` method. +- `figma.fenestra.yaml` — draft UI ecosystem description for Figma’s Plugin / + Widget platform (iframe + main-thread dual context). Validated against the + draft Fenestra schema on branch `claude/fenestra-spec-draft-Q367t`. + +## Sources + +- https://developers.figma.com/docs/rest-api/ +- https://developers.figma.com/docs/rest-api/authentication/ +- https://developers.figma.com/docs/rest-api/oauth-apps/ +- https://developers.figma.com/docs/rest-api/scopes/ +- https://developers.figma.com/docs/rest-api/webhooks/ +- https://github.com/figma/rest-api-spec + +## Tests + +```bash +cd packages/v1-ready/figma && npm test +``` diff --git a/packages/v1-ready/figma/api.js b/packages/v1-ready/figma/api.js new file mode 100644 index 00000000..6bb5a8ad --- /dev/null +++ b/packages/v1-ready/figma/api.js @@ -0,0 +1,281 @@ +const { OAuth2Requester, get } = require('@friggframework/core'); + +/** + * Figma REST API client. + * + * Auth: OAuth 2 authorization-code (recommended for multi-user apps). Token + * exchange and refresh use HTTP Basic (`client_id:client_secret`) against + * `https://api.figma.com/v1/oauth/{token,refresh}`. Personal / plan access + * tokens are also accepted as a Bearer `access_token` for local tooling. + * + * Base URL: https://api.figma.com + * Docs: https://developers.figma.com/docs/rest-api/ + * Official OpenAPI: https://github.com/figma/rest-api-spec + * + * Method names match OpenAPI operationIds 1:1 (see figma.openapi.yaml). + */ +class Api extends OAuth2Requester { + constructor(params = {}) { + super(params); + + this.baseUrl = 'https://api.figma.com'; + + this.access_token = get(params, 'access_token', null); + this.refresh_token = get(params, 'refresh_token', null); + + // OAuth 2 (authorization code). Scopes are space- or comma-separated + // per https://developers.figma.com/docs/rest-api/oauth-apps/ + this.authorizationUri = encodeURI( + `https://www.figma.com/oauth` + + `?client_id=${this.client_id}` + + `&redirect_uri=${this.redirect_uri}` + + `&scope=${this.scope}` + + `&state=${this.state}` + + `&response_type=code` + ); + this.tokenUri = 'https://api.figma.com/v1/oauth/token'; + this.refreshUri = 'https://api.figma.com/v1/oauth/refresh'; + + this.URLs = { + me: '/v1/me', + file: (fileKey) => `/v1/files/${fileKey}`, + fileMeta: (fileKey) => `/v1/files/${fileKey}/meta`, + comments: (fileKey) => `/v1/files/${fileKey}/comments`, + comment: (fileKey, commentId) => + `/v1/files/${fileKey}/comments/${commentId}`, + teamProjects: (teamId) => `/v1/teams/${teamId}/projects`, + projectFiles: (projectId) => `/v1/projects/${projectId}/files`, + projectMeta: (projectId) => `/v1/projects/${projectId}/meta`, + teamFolders: (teamId) => `/v2/teams/${teamId}/folders`, + folderFiles: (folderId) => `/v2/folders/${folderId}/files`, + folderMeta: (folderId) => `/v2/folders/${folderId}/meta`, + webhooks: '/v2/webhooks', + webhook: (webhookId) => `/v2/webhooks/${webhookId}`, + teamWebhooks: (teamId) => `/v2/teams/${teamId}/webhooks`, + webhookRequests: (webhookId) => + `/v2/webhooks/${webhookId}/requests`, + }; + } + + getAuthUri() { + return this.authorizationUri; + } + + /** + * Exchange an authorization code for tokens. + * Figma requires HTTP Basic client credentials on the token endpoint. + * Auth codes expire 30 seconds after issue. + */ + async getTokenFromCode(code) { + delete this.access_token; + return this.getTokenFromCodeBasicAuthHeader(code); + } + + /** + * Refresh an access token. + * Figma uses a dedicated refresh URL (not the token URL) and Basic auth. + * Body is only `refresh_token` (no grant_type). + * Docs: https://developers.figma.com/docs/rest-api/oauth-apps/ + */ + async refreshAccessToken(refreshTokenObject) { + this.access_token = undefined; + const refreshToken = + get(refreshTokenObject, 'refresh_token', null) || this.refresh_token; + if (!refreshToken) { + throw new Error( + 'Figma refreshAccessToken requires a refresh_token' + ); + } + + const params = new URLSearchParams(); + params.append('refresh_token', refreshToken); + + const options = { + url: this.refreshUri, + body: params, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${Buffer.from( + `${this.client_id}:${this.client_secret}` + ).toString('base64')}`, + }, + }; + const response = await this._post(options, false); + await this.setTokens(response); + return response; + } + + // ---- Users ------------------------------------------------------------- + + /** GET /v1/me — current user (id, handle, img_url, email). */ + async getMe() { + return this._get({ url: this.baseUrl + this.URLs.me }); + } + + // ---- Files ------------------------------------------------------------- + + /** + * GET /v1/files/{file_key} + * Optional query: version, ids, depth, geometry, plugin_data, branch_data + */ + async getFile(fileKey, query = {}) { + return this._get({ + url: this.baseUrl + this.URLs.file(fileKey), + query, + }); + } + + /** GET /v1/files/{file_key}/meta */ + async getFileMeta(fileKey) { + return this._get({ + url: this.baseUrl + this.URLs.fileMeta(fileKey), + }); + } + + // ---- Comments ---------------------------------------------------------- + + /** + * GET /v1/files/{file_key}/comments + * Optional query: as_md (boolean) + */ + async getComments(fileKey, query = {}) { + return this._get({ + url: this.baseUrl + this.URLs.comments(fileKey), + query, + }); + } + + /** + * POST /v1/files/{file_key}/comments + * Body: { message, client_meta?, comment_id? } + * There is no update-comment endpoint in the public REST API. + */ + async postComment(fileKey, data) { + return this._post({ + url: this.baseUrl + this.URLs.comments(fileKey), + headers: { 'Content-Type': 'application/json' }, + body: data, + }); + } + + /** DELETE /v1/files/{file_key}/comments/{comment_id} */ + async deleteComment(fileKey, commentId) { + return this._delete({ + url: this.baseUrl + this.URLs.comment(fileKey, commentId), + }); + } + + // ---- Projects (legacy; prefer folders) --------------------------------- + + /** GET /v1/teams/{team_id}/projects */ + async getTeamProjects(teamId) { + return this._get({ + url: this.baseUrl + this.URLs.teamProjects(teamId), + }); + } + + /** + * GET /v1/projects/{project_id}/files + * Optional query: branch_data + */ + async getProjectFiles(projectId, query = {}) { + return this._get({ + url: this.baseUrl + this.URLs.projectFiles(projectId), + query, + }); + } + + /** GET /v1/projects/{project_id}/meta */ + async getProjectMeta(projectId) { + return this._get({ + url: this.baseUrl + this.URLs.projectMeta(projectId), + }); + } + + // ---- Folders (replaces projects) --------------------------------------- + + /** GET /v2/teams/{team_id}/folders */ + async getTeamFolders(teamId) { + return this._get({ + url: this.baseUrl + this.URLs.teamFolders(teamId), + }); + } + + /** GET /v2/folders/{folder_id}/files */ + async getFolderFiles(folderId) { + return this._get({ + url: this.baseUrl + this.URLs.folderFiles(folderId), + }); + } + + /** GET /v2/folders/{folder_id}/meta */ + async getFolderMeta(folderId) { + return this._get({ + url: this.baseUrl + this.URLs.folderMeta(folderId), + }); + } + + // ---- Webhooks V2 ------------------------------------------------------- + + /** + * GET /v2/webhooks + * Query: context + context_id, or plan_api_id (+ optional cursor) + */ + async getWebhooks(query = {}) { + return this._get({ + url: this.baseUrl + this.URLs.webhooks, + query, + }); + } + + /** + * POST /v2/webhooks + * Body requires event_type, endpoint, passcode, context, context_id. + */ + async postWebhook(data) { + return this._post({ + url: this.baseUrl + this.URLs.webhooks, + headers: { 'Content-Type': 'application/json' }, + body: data, + }); + } + + /** GET /v2/webhooks/{webhook_id} */ + async getWebhook(webhookId) { + return this._get({ + url: this.baseUrl + this.URLs.webhook(webhookId), + }); + } + + /** PUT /v2/webhooks/{webhook_id} */ + async putWebhook(webhookId, data) { + return this._put({ + url: this.baseUrl + this.URLs.webhook(webhookId), + headers: { 'Content-Type': 'application/json' }, + body: data, + }); + } + + /** DELETE /v2/webhooks/{webhook_id} */ + async deleteWebhook(webhookId) { + return this._delete({ + url: this.baseUrl + this.URLs.webhook(webhookId), + }); + } + + /** GET /v2/teams/{team_id}/webhooks */ + async getTeamWebhooks(teamId) { + return this._get({ + url: this.baseUrl + this.URLs.teamWebhooks(teamId), + }); + } + + /** GET /v2/webhooks/{webhook_id}/requests */ + async getWebhookRequests(webhookId) { + return this._get({ + url: this.baseUrl + this.URLs.webhookRequests(webhookId), + }); + } +} + +module.exports = { Api }; diff --git a/packages/v1-ready/figma/defaultConfig.json b/packages/v1-ready/figma/defaultConfig.json new file mode 100644 index 00000000..5521a0b5 --- /dev/null +++ b/packages/v1-ready/figma/defaultConfig.json @@ -0,0 +1,8 @@ +{ + "name": "figma", + "label": "Figma", + "productUrl": "https://www.figma.com", + "apiDocs": "https://developers.figma.com/docs/rest-api/", + "categories": ["Design", "Collaboration"], + "description": "Figma" +} diff --git a/packages/v1-ready/figma/definition.js b/packages/v1-ready/figma/definition.js new file mode 100644 index 00000000..7f62e465 --- /dev/null +++ b/packages/v1-ready/figma/definition.js @@ -0,0 +1,87 @@ +require('dotenv').config(); +const { Api } = require('./api'); +const { get } = require('@friggframework/core'); +const config = require('./defaultConfig.json'); + +/** + * Figma is OAuth 2 (authorization code). Public OAuth apps require Figma + * review before they can authorize end users; private apps are limited to the + * team/org the app is associated with. Identity comes from GET /v1/me. + * + * Default scopes cover the core objects this module implements. Override via + * FIGMA_SCOPE (space- or comma-separated). + */ +const DEFAULT_SCOPES = [ + 'current_user:read', + 'file_content:read', + 'file_metadata:read', + 'file_comments:read', + 'file_comments:write', + 'projects:read', + 'folders:read', + 'folder_metadata:read', + 'webhooks:read', + 'webhooks:write', +].join(','); + +const Definition = { + API: Api, + getName: () => config.name, + moduleName: config.name, + modelName: 'Figma', + requiredAuthMethods: { + getToken: async (api, params) => { + const code = + get(params, 'code', null) || get(params.data || {}, 'code', null); + return api.getTokenFromCode(code); + }, + getEntityDetails: async (api, callbackParams, tokenResponse, userId) => { + const me = await api.getMe(); + if (!me?.id) { + throw new Error( + 'Figma /v1/me did not return a user id. Response: ' + + JSON.stringify(me) + ); + } + return { + identifiers: { externalId: String(me.id), userId }, + details: { + name: me.handle || me.email || String(me.id), + email: me.email, + img_url: me.img_url, + }, + }; + }, + getCredentialDetails: async (api, userId) => { + const me = await api.getMe(); + if (!me?.id) { + throw new Error( + 'Figma /v1/me did not return a user id. Response: ' + + JSON.stringify(me) + ); + } + return { + identifiers: { externalId: String(me.id), userId }, + details: {}, + }; + }, + apiPropertiesToPersist: { + credential: ['access_token', 'refresh_token'], + entity: [], + }, + testAuthRequest: async (api) => { + return api.getMe(); + }, + }, + env: { + client_id: process.env.FIGMA_CLIENT_ID, + client_secret: process.env.FIGMA_CLIENT_SECRET, + scope: process.env.FIGMA_SCOPE || DEFAULT_SCOPES, + // Omit when unset so schema validation does not see the string "undefined/figma". + redirect_uri: process.env.REDIRECT_URI + ? `${process.env.REDIRECT_URI}/figma` + : undefined, + }, +}; + +module.exports = { Definition }; diff --git a/packages/v1-ready/figma/extensions/webhooks/handlers.js b/packages/v1-ready/figma/extensions/webhooks/handlers.js new file mode 100644 index 00000000..2acab3a8 --- /dev/null +++ b/packages/v1-ready/figma/extensions/webhooks/handlers.js @@ -0,0 +1,86 @@ +const { verifyFigmaPasscode } = require('./passcode-verifier'); +const { findIntegrationByFigmaUserId } = require('./lookup'); + +/** + * Receiver for `POST /webhooks` — DB-free. Verifies the Figma passcode in the + * JSON body, then enqueues FIGMA_WEBHOOK_RESOLVE. Integration lookup needs the + * DB and runs in the worker. + * + * Expected env: FIGMA_WEBHOOK_PASSCODE (must match the passcode used when + * creating webhooks via postWebhook). + */ +async function onFigmaWebhookReceived({ req, res }) { + const verification = verifyFigmaPasscode({ + body: req.body, + expectedPasscode: process.env.FIGMA_WEBHOOK_PASSCODE, + }); + if (!verification.valid) { + console.warn( + `[figma-webhooks] rejecting webhook: ${verification.reason}` + ); + // Figma docs: respond 400 on wrong passcode to stop the webhook. + res.status(400).json({ error: 'invalid passcode' }); + return; + } + + const body = req.body || {}; + await this.queueWebhook({ + event: 'FIGMA_WEBHOOK_RESOLVE', + body, + }); + + res.status(200).json({ received: 1, queued: 1 }); +} + +/** + * FIGMA_WEBHOOK_RESOLVE — worker hop. Resolves the owning integration when + * `body.figma_user_id` (or `body.externalId`) is present. Figma's native + * payloads do not include the authorizing user id; integrations that need + * multi-tenant routing should stamp `figma_user_id` onto the stored webhook + * mapping (or use a per-tenant endpoint / passcode). Without it we skip. + */ +async function onFigmaWebhookResolve({ data }) { + const body = data && data.body; + const externalId = + (body && (body.figma_user_id || body.externalId)) || null; + if (externalId === undefined || externalId === null) { + console.warn( + '[figma-webhooks] resolve: event missing figma_user_id/externalId; ' + + 'skipping tenant binding (override FIGMA_WEBHOOK via binding.handlers ' + + 'or stamp figma_user_id when registering webhooks)' + ); + // Still deliver unbound for single-tenant adopters who override the handler. + await this.queueWebhook({ + event: 'FIGMA_WEBHOOK', + body, + }); + return; + } + + const integrationId = await findIntegrationByFigmaUserId(this, externalId); + if (!integrationId) { + console.warn( + `[figma-webhooks] resolve: no integration for figma_user_id=${externalId}; skipping` + ); + return; + } + + await this.queueWebhook({ + event: 'FIGMA_WEBHOOK', + integrationId, + body, + }); +} + +/** + * FIGMA_WEBHOOK — default no-op; consumers override via binding.handlers. + */ +async function onFigmaWebhook({ data }) { + // no-op — override via binding.handlers.FIGMA_WEBHOOK +} + +module.exports = { + onFigmaWebhookReceived, + onFigmaWebhookResolve, + onFigmaWebhook, +}; diff --git a/packages/v1-ready/figma/extensions/webhooks/index.js b/packages/v1-ready/figma/extensions/webhooks/index.js new file mode 100644 index 00000000..d5260e3a --- /dev/null +++ b/packages/v1-ready/figma/extensions/webhooks/index.js @@ -0,0 +1,43 @@ +const { + onFigmaWebhookReceived, + onFigmaWebhookResolve, + onFigmaWebhook, +} = require('./handlers'); + +/** + * Figma Webhooks V2 — Tier 3 Integration Extension bundle. + * + * DB-free receiver (`useDatabase: false`): FIGMA_WEBHOOK_RECEIVED verifies the + * passcode in the JSON body and enqueues FIGMA_WEBHOOK_RESOLVE. Tenant lookup + * (when `figma_user_id` is available) runs in the worker, which re-enqueues + * FIGMA_WEBHOOK bound to the resolved integration. Consumers override + * FIGMA_WEBHOOK via binding.handlers. + * + * Contract: https://github.com/friggframework/frigg/blob/next/packages/core/integrations/EXTENSIONS.md + * Security: https://developers.figma.com/docs/rest-api/webhooks-security/ + */ +module.exports = { + name: 'figma-webhooks', + useDatabase: false, + routes: [ + { + path: '/webhooks', + method: 'POST', + event: 'FIGMA_WEBHOOK_RECEIVED', + }, + ], + events: { + FIGMA_WEBHOOK_RECEIVED: { + type: 'LIFE_CYCLE_EVENT', + handler: onFigmaWebhookReceived, + }, + FIGMA_WEBHOOK_RESOLVE: { + type: 'LIFE_CYCLE_EVENT', + handler: onFigmaWebhookResolve, + }, + FIGMA_WEBHOOK: { + type: 'LIFE_CYCLE_EVENT', + handler: onFigmaWebhook, + }, + }, +}; diff --git a/packages/v1-ready/figma/extensions/webhooks/lookup.js b/packages/v1-ready/figma/extensions/webhooks/lookup.js new file mode 100644 index 00000000..58c34e5f --- /dev/null +++ b/packages/v1-ready/figma/extensions/webhooks/lookup.js @@ -0,0 +1,34 @@ +const { Definition } = require('../../definition'); + +/** + * Figma-vocabulary wrapper around `findIntegrationByEntityExternalId`. + * Webhook payloads identify the delivering webhook (`webhook_id`) and often + * a `file_key`, but the owning Frigg entity is the authorizing Figma user + * (`/v1/me`.id). Callers that persist a mapping from webhook_id → user + * externalId should resolve that before calling this helper, or pass the + * user id directly when known. + * + * @param {Object} integration - IntegrationBase instance with `commands` + * @param {string|number} externalId - Figma user id (entity.externalId) + * @returns {Promise} + */ +async function findIntegrationByFigmaUserId(integration, externalId) { + if ( + !integration || + !integration.commands || + typeof integration.commands.findIntegrationByEntityExternalId !== + 'function' + ) { + throw new Error( + 'findIntegrationByFigmaUserId: integration instance must expose commands.findIntegrationByEntityExternalId() — wire up createFriggCommands in your integration constructor.' + ); + } + return integration.commands.findIntegrationByEntityExternalId( + externalId, + Definition.moduleName + ); +} + +module.exports = { + findIntegrationByFigmaUserId, +}; diff --git a/packages/v1-ready/figma/extensions/webhooks/passcode-verifier.js b/packages/v1-ready/figma/extensions/webhooks/passcode-verifier.js new file mode 100644 index 00000000..005b773c --- /dev/null +++ b/packages/v1-ready/figma/extensions/webhooks/passcode-verifier.js @@ -0,0 +1,34 @@ +/** + * Verify a Figma Webhooks V2 delivery by comparing the `passcode` field in the + * JSON body to the passcode configured when the webhook was created. + * + * Official docs (https://developers.figma.com/docs/rest-api/webhooks-security/): + * Figma does not sign webhooks with a header; the passcode in the payload is + * the verification mechanism. On mismatch, respond 400 so Figma stops the + * webhook. + * + * @param {object} params + * @param {object} params.body - Parsed JSON body from the webhook POST + * @param {string} params.expectedPasscode - Passcode supplied at webhook create + * @returns {{ valid: boolean, reason?: string }} + */ +function verifyFigmaPasscode({ body, expectedPasscode }) { + if (!expectedPasscode) { + return { + valid: false, + reason: 'missing expected passcode (FIGMA_WEBHOOK_PASSCODE)', + }; + } + if (!body || typeof body !== 'object') { + return { valid: false, reason: 'missing or non-object body' }; + } + if (typeof body.passcode !== 'string' || body.passcode.length === 0) { + return { valid: false, reason: 'body.passcode missing' }; + } + if (body.passcode !== expectedPasscode) { + return { valid: false, reason: 'passcode mismatch' }; + } + return { valid: true }; +} + +module.exports = { verifyFigmaPasscode }; diff --git a/packages/v1-ready/figma/figma.capabilities.json b/packages/v1-ready/figma/figma.capabilities.json new file mode 100644 index 00000000..4591edee --- /dev/null +++ b/packages/v1-ready/figma/figma.capabilities.json @@ -0,0 +1,119 @@ +{ + "schema": "capability-manifest/v0.1", + "module": "figma", + "package": "@friggframework/api-module-figma", + "version": "1.0.0", + "auth": { + "type": "oauth2", + "authorizationUrl": "https://www.figma.com/oauth", + "tokenUrl": "https://api.figma.com/v1/oauth/token", + "refreshUrl": "https://api.figma.com/v1/oauth/refresh", + "tokenAuth": "basic", + "scopes": [ + "current_user:read", + "file_content:read", + "file_metadata:read", + "file_comments:read", + "file_comments:write", + "projects:read", + "folders:read", + "folder_metadata:read", + "webhooks:read", + "webhooks:write" + ], + "identityMethod": "getMe", + "notes": "Public OAuth apps require Figma review before end-user authorization. Private apps are limited to the associated team/org." + }, + "objects": { + "user": { + "description": "Authenticated Figma user (GET /v1/me)", + "methods": { + "get": "getMe" + }, + "fields": [ + { "name": "id", "type": "string", "tags": ["identifier", "externalId"] }, + { "name": "handle", "type": "string", "tags": ["displayName"] }, + { "name": "email", "type": "string", "tags": ["email", "pii"] }, + { "name": "img_url", "type": "string", "tags": ["avatar"] } + ] + }, + "file": { + "description": "Figma design file document and metadata", + "methods": { + "get": "getFile", + "getMeta": "getFileMeta" + }, + "fields": [ + { "name": "key", "type": "string", "tags": ["identifier"] }, + { "name": "name", "type": "string", "tags": ["displayName"] }, + { "name": "lastModified", "type": "string", "tags": ["timestamp"] }, + { "name": "thumbnailUrl", "type": "string", "tags": ["thumbnail"] }, + { "name": "version", "type": "string", "tags": [] }, + { "name": "editorType", "type": "string", "tags": [] }, + { "name": "document", "type": "object", "tags": ["content"] } + ] + }, + "project": { + "description": "Legacy team project (folder). Prefer folder object for new work.", + "methods": { + "listByTeam": "getTeamProjects", + "listFiles": "getProjectFiles", + "getMeta": "getProjectMeta" + }, + "fields": [ + { "name": "id", "type": "string", "tags": ["identifier"] }, + { "name": "name", "type": "string", "tags": ["displayName"] } + ] + }, + "folder": { + "description": "Team folder (replaces projects). List files and read metadata.", + "methods": { + "listByTeam": "getTeamFolders", + "listFiles": "getFolderFiles", + "getMeta": "getFolderMeta" + }, + "fields": [ + { "name": "id", "type": "string", "tags": ["identifier"] }, + { "name": "name", "type": "string", "tags": ["displayName"] } + ] + }, + "comment": { + "description": "File comments. Public API supports list/create/delete (no update).", + "methods": { + "list": "getComments", + "create": "postComment", + "delete": "deleteComment" + }, + "fields": [ + { "name": "id", "type": "string", "tags": ["identifier"] }, + { "name": "message", "type": "string", "tags": ["content"] }, + { "name": "file_key", "type": "string", "tags": ["parent"] }, + { "name": "user", "type": "object", "tags": ["author"] }, + { "name": "created_at", "type": "string", "tags": ["timestamp"] }, + { "name": "resolved_at", "type": "string", "tags": ["timestamp"] } + ] + }, + "webhook": { + "description": "Webhooks V2 subscription on a team, project/folder, or file context", + "methods": { + "list": "getWebhooks", + "create": "postWebhook", + "get": "getWebhook", + "update": "putWebhook", + "delete": "deleteWebhook", + "listByTeam": "getTeamWebhooks", + "listRequests": "getWebhookRequests" + }, + "fields": [ + { "name": "id", "type": "string", "tags": ["identifier"] }, + { "name": "event_type", "type": "string", "tags": ["event"] }, + { "name": "context", "type": "string", "tags": [] }, + { "name": "context_id", "type": "string", "tags": ["parent"] }, + { "name": "endpoint", "type": "string", "tags": ["url"] }, + { "name": "passcode", "type": "string", "tags": ["secret", "redactedOnRead"] }, + { "name": "status", "type": "string", "tags": ["status"] }, + { "name": "description", "type": "string", "tags": [] } + ] + } + } +} diff --git a/packages/v1-ready/figma/figma.fenestra.yaml b/packages/v1-ready/figma/figma.fenestra.yaml new file mode 100644 index 00000000..8203baa0 --- /dev/null +++ b/packages/v1-ready/figma/figma.fenestra.yaml @@ -0,0 +1,730 @@ +# Fenestra Document: Figma Plugin Platform +# +# Draft sourced from api-module-library branch claude/fenestra-spec-draft-Q367t +# (specs/fenestra/examples/0.1.0/figma.fenestra.yaml). One duplicate YAML key in +# developmentTools/Hot Reload was merged so `fenestra.js validate` passes. +# +# Figma represents an iframe-based execution model with dual-context architecture. +# Plugins run in two separate contexts: main thread (Figma API access) and UI thread (sandboxed iframe). + +fenestra: "0.1.0" + +info: + title: Figma Plugin Platform UI Ecosystem + version: "1.0.0" + description: | + Figma's Plugin Platform enables developers to extend Figma and FigJam + with custom functionality through a dual-context architecture. + + Key characteristics: + - Two execution contexts: Main thread (Figma API) + UI thread (iframe) + - Communication via postMessage between contexts + - Sandboxed iframe for all UI rendering + - Component libraries are OPTIONAL (developers can use figma-plugin-ds or custom HTML/CSS) + - Widget API for on-canvas interactive elements + - Separate FigJam plugin capabilities + +platform: + name: Figma + vendor: Figma, Inc. + documentation: https://www.figma.com/plugin-docs/ + appMarketplace: https://www.figma.com/community/plugins + +sdks: + figma-plugin-api: + name: Figma Plugin API + packageManager: npm + package: "@figma/plugin-typings" + documentation: https://www.figma.com/plugin-docs/api/api-overview/ + description: | + TypeScript type definitions for the Figma Plugin API. + Provides types for the global 'figma' object available in the main thread. + exports: + figma: + type: utility + description: Global figma object for document manipulation and API access + PluginAPI: + type: type + description: TypeScript interface for the Figma Plugin API + SceneNode: + type: type + description: Base type for all nodes in the Figma document + FrameNode: + type: type + description: Frame node type + TextNode: + type: type + description: Text node type + RectangleNode: + type: type + description: Rectangle node type + + figma-plugin-ds: + name: Figma Plugin DS (Community) + packageManager: npm + package: "figma-plugin-ds" + repository: https://github.com/thomas-lowry/figma-plugin-ds + documentation: https://github.com/thomas-lowry/figma-plugin-ds + description: | + Community-created design system that provides CSS styles matching + Figma's UI. Not officially maintained by Figma but widely used. + Works with plain HTML similar to Mirotone for Miro. + exports: + button: + type: component + description: ".button CSS class for buttons" + input: + type: component + description: ".input CSS class for text inputs" + select: + type: component + description: ".select-menu CSS class for dropdowns" + checkbox: + type: component + description: ".checkbox CSS class for checkboxes" + radio: + type: component + description: ".radio CSS class for radio buttons" + switch: + type: component + description: ".switch CSS class for toggle switches" + textarea: + type: component + description: ".textarea CSS class for text areas" + icon: + type: component + description: ".icon CSS class for icons" + disclosure: + type: component + description: ".disclosure CSS class for collapsible sections" + label: + type: component + description: ".label CSS class for form labels" + section: + type: component + description: ".section CSS class for content sections" + + figma-widget-api: + name: Figma Widget API + packageManager: npm + package: "@figma/plugin-typings" + documentation: https://www.figma.com/widget-docs/ + description: | + API for creating interactive on-canvas widgets in Figma. + Widgets use a React-like declarative syntax and run directly on the canvas. + exports: + widget: + type: utility + description: Global widget namespace for creating on-canvas widgets + AutoLayout: + type: component + description: Auto layout container for widgets + Frame: + type: component + description: Frame container for widgets + Text: + type: component + description: Text display for widgets + Rectangle: + type: component + description: Rectangle shape for widgets + SVG: + type: component + description: SVG display for widgets + Input: + type: component + description: Text input for widgets + useSyncedState: + type: hook + description: Hook for synced state across widget instances + +componentCatalog: + categories: + action: + name: Actions + description: Interactive elements + components: [button, iconButton] + + input: + name: Inputs + description: Form controls + components: [input, textarea, select, checkbox, radio, switch] + + layout: + name: Layout + description: Layout components + components: [section, disclosure, divider] + + display: + name: Display + description: Display components + components: [label, icon] + + widget: + name: Widgets + description: On-canvas widget components + components: [widgetAutoLayout, widgetText, widgetInput, widgetFrame] + + components: + button: + name: Button + description: Figma-styled button component + category: action + sdkBinding: + sdk: figma-plugin-ds + export: button + webStandardMapping: + htmlElement: button + ariaRole: button + semanticType: action + props: + class: + type: string + description: "CSS classes: .button, .button--primary, .button--secondary, .button--tertiary" + variants: + - primary + - secondary + - tertiary + - destructive + examples: + - title: Primary Button + code: | + + language: html + - title: Icon Button + code: | + + language: html + + input: + name: Input + description: Text input field styled to match Figma + category: input + sdkBinding: + sdk: figma-plugin-ds + export: input + webStandardMapping: + htmlElement: input + ariaRole: textbox + semanticType: input + props: + class: + type: string + description: "CSS class: .input" + placeholder: + type: string + examples: + - title: Text Input + code: | + + language: html + + select: + name: Select Menu + description: Dropdown menu styled to match Figma + category: input + sdkBinding: + sdk: figma-plugin-ds + export: select + webStandardMapping: + htmlElement: select + ariaRole: listbox + semanticType: input + examples: + - title: Select Menu + code: | +
+ +
+
+ language: html + + checkbox: + name: Checkbox + description: Checkbox styled to match Figma + category: input + sdkBinding: + sdk: figma-plugin-ds + export: checkbox + webStandardMapping: + htmlElement: input[type="checkbox"] + ariaRole: checkbox + semanticType: input + examples: + - title: Checkbox + code: | + + language: html + + switch: + name: Switch + description: Toggle switch styled to match Figma + category: input + sdkBinding: + sdk: figma-plugin-ds + export: switch + webStandardMapping: + htmlElement: input[type="checkbox"] + ariaRole: switch + semanticType: input + examples: + - title: Switch + code: | + + language: html + + section: + name: Section + description: Content section container + category: layout + sdkBinding: + sdk: figma-plugin-ds + export: section + webStandardMapping: + htmlElement: section + ariaRole: region + semanticType: container + examples: + - title: Section + code: | +
+
Section Title
+
+ Content goes here +
+
+ language: html + + disclosure: + name: Disclosure + description: Collapsible section with expand/collapse + category: layout + sdkBinding: + sdk: figma-plugin-ds + export: disclosure + webStandardMapping: + htmlElement: details + ariaRole: group + semanticType: container + examples: + - title: Disclosure + code: | +
+
+
▶
+ Advanced Options +
+
+ Hidden content +
+
+ language: html + + widgetAutoLayout: + name: Widget AutoLayout + description: Auto layout container for on-canvas widgets + category: widget + sdkBinding: + sdk: figma-widget-api + export: AutoLayout + webStandardMapping: + htmlElement: div + semanticType: layout + examples: + - title: Widget AutoLayout + code: | + const { widget } = figma; + const { AutoLayout, Text } = widget; + + function Widget() { + return ( + + Hello World + + ); + } + language: jsx + + widgetText: + name: Widget Text + description: Text display for on-canvas widgets + category: widget + sdkBinding: + sdk: figma-widget-api + export: Text + webStandardMapping: + htmlElement: span + semanticType: display + examples: + - title: Widget Text + code: | + + Widget text content + + language: jsx + + widgetInput: + name: Widget Input + description: Text input field for on-canvas widgets + category: widget + sdkBinding: + sdk: figma-widget-api + export: Input + webStandardMapping: + htmlElement: input + semanticType: input + examples: + - title: Widget Input + code: | + const [value, setValue] = useSyncedState('text', ''); + return ( + setValue(e.characters)} + placeholder="Enter text" + /> + ); + language: jsx + +policies: + componentRequirement: + level: optional + scope: all + rationale: | + Figma provides no official component library. The community-maintained + figma-plugin-ds is popular but entirely optional. Developers have + complete freedom to build UIs with any HTML/CSS/JS approach. + + customComponents: + allowed: true + constraints: + - sandboxOnly + registrationRequired: false + approvalRequired: false + documentation: https://www.figma.com/plugin-docs/creating-ui/ + + publishing: + privateApps: + componentRequirement: optional + customComponentsAllowed: true + reviewRequired: false + notes: "Private plugins have complete freedom" + + publicApps: + componentRequirement: optional + customComponentsAllowed: true + reviewRequired: true + notes: "Community plugins undergo review for security and quality" + + marketplaceApps: + componentRequirement: optional + customComponentsAllowed: true + certificationRequired: false + notes: "All community plugins follow same review process" + + designGuidelines: + required: false + designSystem: "figma-plugin-ds (community)" + designSystemUrl: https://github.com/thomas-lowry/figma-plugin-ds + designTokensProvided: false + + security: + sandbox: true + sandboxDescription: | + All plugin UI runs in a sandboxed iframe. The main plugin code + runs in a separate JavaScript context with access to the Figma API + but no DOM access. Communication between contexts uses postMessage. + cspPolicy: "default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src https:" + +extensionPoints: + - id: plugin-ui + name: Plugin UI + type: modal + extensionType: iframe + description: | + Sandboxed iframe UI that can be shown as a modal or panel. + Communicates with main plugin code via postMessage. + supportedComponents: + - all + constraints: + resizable: true + responsive: true + context: + provides: + - selection + - currentPage + - document + - user + actions: + - createNode + - modifyNode + - deleteNode + - showUI + - closePlugin + extensionTypeConfig: + sandboxAttributes: + - allow-scripts + - allow-forms + communication: + globalObject: "parent" + initialization: + method: "postMessage to parent" + async: true + asyncPattern: promises + capabilities: + context: true + uiActions: true + dataActions: true + events: true + + - id: widget-canvas + name: Canvas Widget (Figma) + type: canvas + extensionType: codedComponents + description: | + Interactive on-canvas widget that lives in the Figma document. + Uses a React-like declarative API with limited component set. + Available in Figma files (not FigJam). + supportedComponents: + - widgetAutoLayout + - widgetText + - widgetInput + - widgetFrame + constraints: + responsive: true + context: + provides: + - widgetNode + - syncedState + - document + actions: + - updateWidget + - openUI + extensionTypeConfig: + sdk: figma-widget-api + framework: react + runtime: + type: directExecution + bundling: platformBuild + + - id: figjam-widget + name: FigJam Widget + type: canvas + extensionType: codedComponents + description: | + Interactive widget for FigJam collaborative whiteboard. + Similar to Figma widgets but with FigJam-specific features. + Can include stickers, voting, and collaboration features. + supportedComponents: + - widgetAutoLayout + - widgetText + - widgetInput + - widgetFrame + constraints: + responsive: true + context: + provides: + - widgetNode + - syncedState + - figjamDocument + actions: + - updateWidget + - openUI + - createSticky + extensionTypeConfig: + sdk: figma-widget-api + framework: react + runtime: + type: directExecution + bundling: platformBuild + + - id: plugin-menu + name: Plugin Menu Command + type: contextMenu + extensionType: codedComponents + description: | + Command in the Figma plugins menu that executes code without showing UI. + Can optionally open UI after execution. + supportedComponents: [] + context: + provides: + - selection + - currentPage + - document + actions: + - createNode + - modifyNode + - showUI + +renderingEngines: + iframe-html: + type: custom + name: HTML + CSS in Iframe + description: | + Plugin UI is plain HTML/CSS/JavaScript rendered in a sandboxed iframe. + Developers can use figma-plugin-ds CSS library or custom styling. + sandboxing: + iframeSandbox: true + sandboxAttributes: + - allow-scripts + - allow-forms + cspPolicy: "default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" + + widget-declarative: + type: custom + name: Widget Declarative API + description: | + On-canvas widgets use a React-like declarative syntax with a limited + set of built-in components (AutoLayout, Text, Input, etc.) + sandboxing: + iframeSandbox: false + +architecture: + executionModel: dual-context + description: | + Figma plugins use a dual-context architecture: + + 1. Main Thread: Runs plugin code with access to Figma API (figma.*) + - No DOM access + - Can read/write document nodes + - Can trigger UI display + + 2. UI Thread: Sandboxed iframe with HTML/CSS/JS + - Full DOM access for rendering UI + - No direct Figma API access + - Communicates with main thread via postMessage + + This separation ensures security while allowing rich custom UIs. + + communication: + protocol: postMessage + description: | + Main thread and UI thread communicate via window.postMessage API. + + Main -> UI: figma.ui.postMessage(data) + UI -> Main: parent.postMessage({ pluginMessage: data }, '*') + + Main thread listens: figma.ui.onmessage = (msg) => { ... } + UI thread listens: window.onmessage = (event) => { ... } + +jsonFormsIntegration: + supported: true + rendererSet: custom + customRenderersAllowed: true + notes: | + Developers can integrate JSON Forms with custom renderers using + figma-plugin-ds or their own components in the UI iframe. + +mcpAppsIntegration: + supported: false + notes: | + Not currently integrated with MCP Apps specification, but the iframe + architecture could potentially support MCP Apps UI resources in the future. + +extensions: + compliance: + communityGuidelines: + url: https://www.figma.com/community/plugin-guidelines/ + description: | + Figma Community Guidelines govern what plugins can be published. + Key requirements: + - No malicious code or data harvesting + - Must respect user privacy + - Clear description of functionality + - Appropriate content only + - No trademark/copyright violations + + reviewProcess: + required: true + scope: public + description: | + All community plugins undergo review before publication: + 1. Automated security checks + 2. Manual review of code and functionality + 3. Testing for performance and stability + 4. Verification of description accuracy + timeline: "1-2 weeks typical review time" + + securityRequirements: + - name: Sandboxing + description: All UI must run in sandboxed iframe + required: true + - name: API Access + description: Only access Figma API from main thread + required: true + - name: Network Requests + description: Declare all network requests in manifest + required: true + - name: Data Privacy + description: No unauthorized data collection or transmission + required: true + + manifest: + file: manifest.json + description: | + Every plugin requires a manifest.json file defining: + - Plugin metadata (name, id, version) + - Main code file path + - UI file path (if using UI) + - Permissions and capabilities + - Menu commands + - Network access requirements + schema: + name: + type: string + required: true + id: + type: string + required: true + description: Unique plugin ID assigned by Figma + api: + type: string + required: true + description: Figma API version (e.g., "1.0.0") + main: + type: string + required: true + description: Path to main plugin code file + ui: + type: string + required: false + description: Path to UI HTML file + capabilities: + type: array + required: false + description: Special capabilities (e.g., "codegen", "inspect") + networkAccess: + type: object + required: false + description: Allowed network domains + + developmentTools: + - name: Figma Plugin API Typings + description: TypeScript definitions for Figma Plugin API + package: "@figma/plugin-typings" + + - name: Create Figma Plugin + description: CLI tool for scaffolding new plugins + package: "create-figma-plugin" + command: "npm init figma-plugin" + url: https://github.com/yuanqing/create-figma-plugin + + - name: Figma Plugin DS + description: Community design system matching Figma's UI + package: "figma-plugin-ds" + url: https://github.com/thomas-lowry/figma-plugin-ds + + - name: Hot Reload + description: "Built-in hot reload during development. Figma supports hot reload in development mode." + builtin: true diff --git a/packages/v1-ready/figma/figma.openapi.yaml b/packages/v1-ready/figma/figma.openapi.yaml new file mode 100644 index 00000000..4d3dc63e --- /dev/null +++ b/packages/v1-ready/figma/figma.openapi.yaml @@ -0,0 +1,862 @@ +openapi: 3.1.0 +info: + title: Figma REST API (Frigg curated subset) + version: 0.45.0 + description: Curated subset of the official Figma REST API OpenAPI spec (https://github.com/figma/rest-api-spec) + covering exactly the endpoints implemented by @friggframework/api-module-figma. One Api method per + operationId. Source of truth for spec-sync tests. + contact: + name: Left Hook + url: https://lefthook.com +servers: +- url: https://api.figma.com +security: +- OAuth2: [] +tags: +- name: Users +- name: Files +- name: Comments +- name: Projects +- name: Folders +- name: Webhooks +paths: + /v1/me: + get: + tags: + - Users + summary: Get current user + security: + - PersonalAccessToken: [] + - OAuth2: + - current_user:read + - files:read + description: Returns the user information for the currently authenticated user. + operationId: getMe + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/files/{file_key}: + get: + tags: + - Files + summary: Get file JSON + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - file_content:read + - files:read + description: 'Returns the document identified by `file_key` as a JSON object. The file key can be + parsed from any Figma file url: `https://www.figma.com/file/{file_key}/{title}`. + + + The `document` property contains a node of type `DOCUMENT`. + + + The `components` property contains a mapping from node IDs to component metadata. This is to help + you determine which components each instance comes from.' + operationId: getFile + parameters: + - name: file_key + in: path + description: File to export JSON from. This can be a file key or branch key. Use `GET /v1/files/:key` + with the `branch_data` query param to get the branch key. + required: true + schema: + type: string + - name: version + in: query + description: A specific version ID to get. Omitting this will get the current version of the file. + schema: + type: string + - name: ids + in: query + description: 'Comma separated list of nodes that you care about in the document. If specified, + only a subset of the document will be returned corresponding to the nodes listed, their children, + and everything between the root node and the listed nodes. + + + Note: There may be other nodes included in the returned JSON that are outside the ancestor chains + of the desired nodes. The response may also include dependencies of anything in the nodes'' + subtrees. For example, if a node subtree contains an instance of a local component that lives + elsewhere in that file, that component and its ancestor chain will also be included. + + + For historical reasons, top-level canvas nodes are always returned, regardless of whether they + are listed in the `ids` parameter. This quirk may be removed in a future version of the API.' + schema: + type: string + - name: depth + in: query + description: Positive integer representing how deep into the document tree to traverse. For example, + setting this to 1 returns only Pages, setting it to 2 returns Pages and all top level objects + on each page. Not setting this parameter returns all nodes. + schema: + type: number + - name: geometry + in: query + description: Set to "paths" to export vector data. + schema: + type: string + - name: plugin_data + in: query + description: A comma separated list of plugin IDs and/or the string "shared". Any data present + in the document written by those plugins will be included in the result in the `pluginData` + and `sharedPluginData` properties. + schema: + type: string + - name: branch_data + in: query + description: 'Returns branch metadata for the requested file. If the file is a branch, the main + file''s key will be included in the returned response. If the file has branches, their metadata + will be included in the returned response. Default: false.' + schema: + type: boolean + default: false + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/files/{file_key}/meta: + get: + tags: + - Files + summary: Get file metadata + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - file_metadata:read + - files:read + description: Get file metadata + operationId: getFileMeta + parameters: + - name: file_key + in: path + description: File to get metadata for. This can be a file key or branch key. Use `GET /v1/files/:key` + with the `branch_data` query param to get the branch key. + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/files/{file_key}/comments: + get: + tags: + - Comments + summary: Get comments in a file + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - file_comments:read + - files:read + description: Gets a list of comments left on the file. + operationId: getComments + parameters: + - name: file_key + in: path + description: File to get comments from. This can be a file key or branch key. Use `GET /v1/files/:key` + with the `branch_data` query param to get the branch key. + required: true + schema: + type: string + - name: as_md + in: query + description: If enabled, will return comments as their markdown equivalents when applicable. + schema: + type: boolean + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + post: + tags: + - Comments + summary: Add a comment to a file + security: + - PersonalAccessToken: [] + - OAuth2: + - file_comments:write + description: Posts a new comment on the file. + operationId: postComment + parameters: + - name: file_key + in: path + description: File to add comments in. This can be a file key or branch key. Use `GET /v1/files/:key` + with the `branch_data` query param to get the branch key. + required: true + schema: + type: string + requestBody: + description: Comment to post. + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + description: The text contents of the comment to post. + comment_id: + type: string + description: The ID of the comment to reply to, if any. This must be a root comment. + You cannot reply to other replies (a comment that has a parent_id). + client_meta: + description: The position where to place the comment. + oneOf: + - $ref: '#/components/schemas/Vector' + - $ref: '#/components/schemas/FrameOffset' + - $ref: '#/components/schemas/Region' + - $ref: '#/components/schemas/FrameOffsetRegion' + required: + - message + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/files/{file_key}/comments/{comment_id}: + delete: + tags: + - Comments + summary: Delete a comment + security: + - PersonalAccessToken: [] + - OAuth2: + - file_comments:write + description: Deletes a specific comment. Only the person who made the comment is allowed to delete + it. + operationId: deleteComment + parameters: + - name: file_key + in: path + description: File to delete comment from. This can be a file key or branch key. Use `GET /v1/files/:key` + with the `branch_data` query param to get the branch key. + required: true + schema: + type: string + - name: comment_id + in: path + description: Comment id of comment to delete + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/teams/{team_id}/projects: + get: + tags: + - Projects + summary: '[Deprecated] Get projects in a team' + deprecated: true + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - projects:read + - files:read + description: 'Deprecated in favor of [Get top-level folders in a team](https://developers.figma.com/docs/rest-api/folders-endpoints/). + You can use this endpoint to get a list of the top-level Projects (now called "folders") within + the specified team. This will only return projects visible to the authenticated user or owner + of the developer token. Note: it is not currently possible to programmatically obtain the team + id of a user just from a token. To obtain a team id, navigate to a team page of a team you are + a part of. The team id will be present in the URL after the word team and before your team name.' + operationId: getTeamProjects + parameters: + - name: team_id + in: path + description: ID of the team to list projects from + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/projects/{project_id}/files: + get: + tags: + - Projects + summary: '[Deprecated] Get files in a project' + deprecated: true + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - projects:read + - files:read + description: Deprecated in favor of [Get files in a folder](https://developers.figma.com/docs/rest-api/folders-endpoints/). + Get a list of all the Files within the specified project (now called a "folder"). + operationId: getProjectFiles + parameters: + - name: project_id + in: path + description: ID of the project to list files from + required: true + schema: + type: string + - name: branch_data + in: query + description: Returns branch metadata in the response for each main file with a branch inside the + project. + schema: + type: boolean + default: false + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v1/projects/{project_id}/meta: + get: + tags: + - Projects + summary: '[Deprecated] Get project metadata' + deprecated: true + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - project_metadata:read + description: Deprecated in favor of [Get folder metadata](https://developers.figma.com/docs/rest-api/folders-endpoints/). + Get metadata for a project (now called a "folder"). + operationId: getProjectMeta + parameters: + - name: project_id + in: path + description: ID of the project to get metadata for. + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/teams/{team_id}/folders: + get: + tags: + - Folders + summary: Get top-level folders in a team + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - folders:read + description: Get a list of the top-level folders (previously called "projects") within the specified + team. Subfolders can be traversed with the GET /v2/folders/{folder_id}/folders endpoint. It is + not possible to programmatically obtain team IDs. To obtain a team ID, navigate to the team page + in the Figma file browser. The team ID is present in the URL after the word team. For example, + in `https://www.figma.com/files/181033233908053158/team/1535685101263221741`, the team ID is `1535685101263221741`. + operationId: getTeamFolders + parameters: + - name: team_id + in: path + description: ID of the team to list folders from + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/folders/{folder_id}/files: + get: + tags: + - Folders + summary: Get files in a folder + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - folders:read + description: Get a list of the files directly within the specified folder. + operationId: getFolderFiles + parameters: + - name: folder_id + in: path + description: ID of the folder to list files from + required: true + schema: + type: string + - name: branch_data + in: query + description: Returns branch metadata in the response for each main file with a branch inside the + folder. + schema: + type: boolean + default: false + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/folders/{folder_id}/meta: + get: + tags: + - Folders + summary: Get folder metadata + security: + - PlanAccessToken: [] + - OAuth2: + - folder_metadata:read + description: Get metadata for a folder (name, thumbnail, file count, timestamps) without enumerating + its files. + operationId: getFolderMeta + parameters: + - name: folder_id + in: path + description: ID of the folder to get metadata for + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/webhooks: + get: + tags: + - Webhooks + summary: Get webhooks by context or plan + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:read + description: Returns a list of webhooks corresponding to the context or plan provided, if they exist. + For plan, the webhooks for all contexts that you have access to will be returned, and theresponse + is paginated + operationId: getWebhooks + parameters: + - name: context + in: query + required: false + schema: + type: string + description: Context to create the resource on. Should be "team", "project", or "file". + - name: context_id + in: query + required: false + schema: + type: string + description: The id of the context that you want to get attached webhooks for. If you're using + context_id, you cannot use plan_api_id. + - name: plan_api_id + in: query + required: false + schema: + type: string + description: The id of your plan. Use this to get all webhooks for all contexts you have access + to. If you're using plan_api_id, you cannot use context or context_id. When you use plan_api_id, + the response is paginated. + - name: cursor + in: query + required: false + schema: + type: string + description: If you're using plan_api_id, this is the cursor to use for pagination. If you're + using context or context_id, this parameter is ignored. Provide the next_page or prev_page value + from the previous response to get the next or previous page of results. + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + post: + tags: + - Webhooks + summary: Create a webhook + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:write + description: Create a new webhook which will call the specified endpoint when the event triggers. + By default, this webhook will automatically send a PING event to the endpoint when it is created. + If this behavior is not desired, you can create the webhook and set the status to PAUSED and reactivate + it later. + operationId: postWebhook + requestBody: + description: The webhook to create. + required: true + content: + application/json: + schema: + type: object + properties: + event_type: + type: string + description: 'see official schema #/components/schemas/WebhookV2Event' + team_id: + type: string + description: Team id to receive updates about. This is deprecated, use 'context' and + 'context_id' instead. + deprecated: true + context: + type: string + description: Context to create the webhook for. Must be "team", "project", or "file". + context_id: + type: string + description: The id of the context you want to receive updates about. + endpoint: + type: string + description: The HTTP endpoint that will receive a POST request when the event triggers. + Max length 2048 characters. + passcode: + type: string + description: String that will be passed back to your webhook endpoint to verify that + it is being called by Figma. Max length 100 characters. + status: + type: string + description: 'see official schema #/components/schemas/WebhookV2Status' + description: + type: string + description: User provided description or name for the webhook. Max length 150 characters. + required: + - event_type + - endpoint + - passcode + - context + - context_id + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/webhooks/{webhook_id}: + get: + tags: + - Webhooks + summary: Get a webhook + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:read + - files:read + description: Get a webhook by ID. + operationId: getWebhook + parameters: + - name: webhook_id + in: path + description: ID of webhook to get + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + put: + tags: + - Webhooks + summary: Update a webhook + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:write + description: Update a webhook by ID. + operationId: putWebhook + parameters: + - name: webhook_id + in: path + description: ID of webhook to update + required: true + schema: + type: string + requestBody: + description: The webhook to update. + required: true + content: + application/json: + schema: + type: object + properties: + event_type: + type: string + description: 'see official schema #/components/schemas/WebhookV2Event' + endpoint: + type: string + description: The HTTP endpoint that will receive a POST request when the event triggers. + Max length 2048 characters. + passcode: + type: string + description: String that will be passed back to your webhook endpoint to verify that + it is being called by Figma. Max length 100 characters. + status: + type: string + description: 'see official schema #/components/schemas/WebhookV2Status' + description: + type: string + description: User provided description or name for the webhook. Max length 150 characters. + required: + - event_type + - team_id + - endpoint + - passcode + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + delete: + tags: + - Webhooks + summary: Delete a webhook + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:write + description: Deletes the specified webhook. This operation cannot be reversed. + operationId: deleteWebhook + parameters: + - name: webhook_id + in: path + description: ID of webhook to delete + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/teams/{team_id}/webhooks: + get: + tags: + - Webhooks + summary: '[Deprecated] Get team webhooks' + deprecated: true + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:read + - files:read + description: Returns all webhooks registered under the specified team. + operationId: getTeamWebhooks + parameters: + - name: team_id + in: path + description: ID of team to get webhooks for + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) + /v2/webhooks/{webhook_id}/requests: + get: + tags: + - Webhooks + summary: Get webhook requests + security: + - PersonalAccessToken: [] + - PlanAccessToken: [] + - OAuth2: + - webhooks:read + - files:read + description: Returns all webhook requests sent within the last week. Useful for debugging. + operationId: getWebhookRequests + parameters: + - name: webhook_id + in: path + description: The id of the webhook subscription you want to see events from + required: true + schema: + type: string + responses: + '200': + description: Response 200 (see official Figma OpenAPI) + '400': + description: Response 400 (see official Figma OpenAPI) + '403': + description: Response 403 (see official Figma OpenAPI) + '404': + description: Response 404 (see official Figma OpenAPI) + '429': + description: Response 429 (see official Figma OpenAPI) + '500': + description: Response 500 (see official Figma OpenAPI) +components: + securitySchemes: + OAuth2: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://www.figma.com/oauth + tokenUrl: https://api.figma.com/v1/oauth/token + refreshUrl: https://api.figma.com/v1/oauth/refresh + scopes: + current_user:read: Read your name, email, and profile image. + file_comments:read: Read the comments for files. + file_comments:write: Post and delete comments and comment reactions in files. + file_content:read: Read the contents of files, such as nodes and the editor type. + file_dev_resources:read: Read dev resources in files. + file_dev_resources:write: Write to dev resources in files. + file_metadata:read: Read metadata of files. + file_variables:read: 'Read variables in Figma file. Note: this is only available to members + in Enterprise organizations.' + file_variables:write: 'Write to variables in Figma file. Note: this is only available to members + in Enterprise organizations.' + file_versions:read: Read the version history for files you can access. + files:read: Deprecated. Read files, projects, users, versions, comments, components & styles, + and webhooks. + library_analytics:read: Read library analytics data. + library_assets:read: Read data of individual published components and styles. + library_content:read: Read published components and styles of files. + project_metadata:read: Read metadata of projects. + projects:read: List projects and files in projects. + folders:read: List folders and files in folders. + folder_metadata:read: Read metadata of folders. + team_library_content:read: Read published components and styles of teams. + webhooks:read: Read metadata of webhooks. + webhooks:write: Create and manage webhooks. + PersonalAccessToken: + type: apiKey + name: X-Figma-Token + in: header + PlanAccessToken: + type: apiKey + name: X-Figma-Token + in: header + schemas: {} diff --git a/packages/v1-ready/figma/index.js b/packages/v1-ready/figma/index.js new file mode 100644 index 00000000..f79bac97 --- /dev/null +++ b/packages/v1-ready/figma/index.js @@ -0,0 +1,11 @@ +const { Api } = require('./api'); +const { Definition } = require('./definition'); +const webhooks = require('./extensions/webhooks'); + +module.exports = { + Api, + Definition, + extensions: { + webhooks, + }, +}; diff --git a/packages/v1-ready/figma/package.json b/packages/v1-ready/figma/package.json new file mode 100644 index 00000000..926c868e --- /dev/null +++ b/packages/v1-ready/figma/package.json @@ -0,0 +1,32 @@ +{ + "name": "@friggframework/api-module-figma", + "version": "1.0.0", + "description": "Figma REST API module that lets the Frigg Framework interact with Figma files, projects/folders, comments, users, and webhooks", + "main": "index.js", + "scripts": { + "test": "jest", + "lint:fix": "prettier --write --loglevel error . && eslint . --fix" + }, + "keywords": [ + "frigg", + "figma", + "api-module", + "design", + "files", + "webhooks" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@friggframework/core": "^2.0.0-next.107" + }, + "devDependencies": { + "@aws-sdk/client-scheduler": "^3.1113.0", + "dotenv": "^16.0.3", + "jest": "^28.1.3", + "js-yaml": "^4.1.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/v1-ready/figma/tests/api.test.js b/packages/v1-ready/figma/tests/api.test.js new file mode 100644 index 00000000..496aebda --- /dev/null +++ b/packages/v1-ready/figma/tests/api.test.js @@ -0,0 +1,185 @@ +const { Api } = require('../api'); + +function makeApi(overrides = {}) { + const api = new Api({ + client_id: 'cid', + client_secret: 'csecret', + redirect_uri: 'https://app.example.com/redirect/figma', + scope: 'current_user:read,file_content:read', + access_token: 'test-token', + ...overrides, + }); + api._captured = []; + api._get = async (options) => { + api._captured.push({ method: 'GET', ...options }); + return {}; + }; + api._post = async (options) => { + api._captured.push({ method: 'POST', ...options }); + return { access_token: 'new', refresh_token: 'ref', expires_in: 3600 }; + }; + api._put = async (options) => { + api._captured.push({ method: 'PUT', ...options }); + return {}; + }; + api._delete = async (options) => { + api._captured.push({ method: 'DELETE', ...options }); + return {}; + }; + return api; +} + +describe('Figma Api', () => { + describe('construction & auth', () => { + it('sets the base URL and OAuth endpoints', () => { + const api = makeApi(); + expect(api.baseUrl).toBe('https://api.figma.com'); + expect(api.tokenUri).toBe('https://api.figma.com/v1/oauth/token'); + expect(api.refreshUri).toBe( + 'https://api.figma.com/v1/oauth/refresh' + ); + expect(api.authorizationUri).toContain('https://www.figma.com/oauth'); + expect(api.authorizationUri).toContain('client_id=cid'); + expect(api.authorizationUri).toContain('response_type=code'); + }); + + it('getTokenFromCode delegates to Basic-auth exchange', async () => { + const api = makeApi(); + api.getTokenFromCodeBasicAuthHeader = jest + .fn() + .mockResolvedValue({ access_token: 't' }); + await api.getTokenFromCode('auth-code'); + expect(api.getTokenFromCodeBasicAuthHeader).toHaveBeenCalledWith( + 'auth-code' + ); + }); + + it('refreshAccessToken posts refresh_token with Basic auth to refreshUri', async () => { + const api = makeApi({ refresh_token: 'r1' }); + api.setTokens = jest.fn().mockResolvedValue(undefined); + await api.refreshAccessToken({ refresh_token: 'r1' }); + const captured = api._captured[0]; + expect(captured.method).toBe('POST'); + expect(captured.url).toBe( + 'https://api.figma.com/v1/oauth/refresh' + ); + expect(captured.headers.Authorization).toMatch(/^Basic /); + expect(captured.body.get('refresh_token')).toBe('r1'); + expect(captured.body.get('grant_type')).toBeNull(); + }); + }); + + describe('identity & files', () => { + it('getMe hits GET /v1/me', async () => { + const api = makeApi(); + await api.getMe(); + expect(api._captured[0].url).toBe('https://api.figma.com/v1/me'); + }); + + it('getFile hits GET /v1/files/{key} with query', async () => { + const api = makeApi(); + await api.getFile('abc', { depth: 1 }); + expect(api._captured[0].url).toBe( + 'https://api.figma.com/v1/files/abc' + ); + expect(api._captured[0].query).toEqual({ depth: 1 }); + }); + + it('getFileMeta hits GET /v1/files/{key}/meta', async () => { + const api = makeApi(); + await api.getFileMeta('abc'); + expect(api._captured[0].url).toBe( + 'https://api.figma.com/v1/files/abc/meta' + ); + }); + }); + + describe('comments', () => { + it('getComments / postComment / deleteComment hit comment paths', async () => { + const api = makeApi(); + await api.getComments('f1', { as_md: true }); + await api.postComment('f1', { message: 'hi' }); + await api.deleteComment('f1', 'c1'); + expect(api._captured[0].url).toBe( + 'https://api.figma.com/v1/files/f1/comments' + ); + expect(api._captured[1].method).toBe('POST'); + expect(api._captured[1].body).toEqual({ message: 'hi' }); + expect(api._captured[2].method).toBe('DELETE'); + expect(api._captured[2].url).toBe( + 'https://api.figma.com/v1/files/f1/comments/c1' + ); + }); + }); + + describe('projects & folders', () => { + it('lists team projects and project files', async () => { + const api = makeApi(); + await api.getTeamProjects('t1'); + await api.getProjectFiles('p1', { branch_data: true }); + await api.getProjectMeta('p1'); + expect(api._captured[0].url).toBe( + 'https://api.figma.com/v1/teams/t1/projects' + ); + expect(api._captured[1].url).toBe( + 'https://api.figma.com/v1/projects/p1/files' + ); + expect(api._captured[2].url).toBe( + 'https://api.figma.com/v1/projects/p1/meta' + ); + }); + + it('lists team folders and folder files/meta', async () => { + const api = makeApi(); + await api.getTeamFolders('t1'); + await api.getFolderFiles('fold1'); + await api.getFolderMeta('fold1'); + expect(api._captured[0].url).toBe( + 'https://api.figma.com/v2/teams/t1/folders' + ); + expect(api._captured[1].url).toBe( + 'https://api.figma.com/v2/folders/fold1/files' + ); + expect(api._captured[2].url).toBe( + 'https://api.figma.com/v2/folders/fold1/meta' + ); + }); + }); + + describe('webhooks', () => { + it('CRUD webhooks against /v2/webhooks', async () => { + const api = makeApi(); + const body = { + event_type: 'FILE_UPDATE', + endpoint: 'https://hook.example.com', + passcode: 'secret', + context: 'team', + context_id: 't1', + }; + await api.getWebhooks({ context: 'team', context_id: 't1' }); + await api.postWebhook(body); + await api.getWebhook('99'); + await api.putWebhook('99', { ...body, status: 'PAUSED' }); + await api.deleteWebhook('99'); + await api.getTeamWebhooks('t1'); + await api.getWebhookRequests('99'); + + expect(api._captured[0].url).toBe( + 'https://api.figma.com/v2/webhooks' + ); + expect(api._captured[1].method).toBe('POST'); + expect(api._captured[1].body).toEqual(body); + expect(api._captured[2].url).toBe( + 'https://api.figma.com/v2/webhooks/99' + ); + expect(api._captured[3].method).toBe('PUT'); + expect(api._captured[4].method).toBe('DELETE'); + expect(api._captured[5].url).toBe( + 'https://api.figma.com/v2/teams/t1/webhooks' + ); + expect(api._captured[6].url).toBe( + 'https://api.figma.com/v2/webhooks/99/requests' + ); + }); + }); +}); diff --git a/packages/v1-ready/figma/tests/definition.test.js b/packages/v1-ready/figma/tests/definition.test.js new file mode 100644 index 00000000..e74db211 --- /dev/null +++ b/packages/v1-ready/figma/tests/definition.test.js @@ -0,0 +1,162 @@ +const fs = require('fs'); +const path = require('path'); +const { Definition } = require('../definition'); +const { Api } = require('../api'); + +describe('Figma Definition', () => { + it('is an oauth module named figma wired to the Api class', () => { + expect(Definition.moduleName).toBe('figma'); + expect(Definition.getName()).toBe('figma'); + expect(Definition.API).toBe(Api); + expect(Definition.modelName).toBe('Figma'); + expect(Definition.moduleName).toMatch(/^[a-zA-Z][a-zA-Z0-9_-]*$/); + }); + + it('persists access_token and refresh_token credentials', () => { + expect(Definition.requiredAuthMethods.apiPropertiesToPersist).toEqual({ + credential: ['access_token', 'refresh_token'], + entity: [], + }); + }); + + it('exposes OAuth env keys', () => { + expect(Definition.env).toHaveProperty('client_id'); + expect(Definition.env).toHaveProperty('client_secret'); + expect(Definition.env).toHaveProperty('scope'); + expect(Definition.env).toHaveProperty('redirect_uri'); + }); + + it('getToken exchanges the authorization code', async () => { + const api = new Api({ + client_id: 'c', + client_secret: 's', + redirect_uri: 'https://example.com/r', + scope: 'current_user:read', + }); + api.getTokenFromCode = jest.fn().mockResolvedValue({ access_token: 't' }); + await Definition.requiredAuthMethods.getToken(api, { + data: { code: 'abc' }, + }); + expect(api.getTokenFromCode).toHaveBeenCalledWith('abc'); + }); + + it('getEntityDetails uses /v1/me id as externalId', async () => { + const api = new Api({ + client_id: 'c', + client_secret: 's', + redirect_uri: 'https://example.com/r', + scope: 'current_user:read', + }); + api.getMe = async () => ({ + id: '813845097374535682', + handle: 'Dylan Field', + email: 'dylan@figma.com', + img_url: 'https://example.com/a.png', + }); + const details = await Definition.requiredAuthMethods.getEntityDetails( + api, + {}, + {}, + 'user-1' + ); + expect(details.identifiers.externalId).toBe('813845097374535682'); + expect(details.identifiers.userId).toBe('user-1'); + expect(details.details.name).toBe('Dylan Field'); + expect(details.details.email).toBe('dylan@figma.com'); + }); + + it('getEntityDetails throws when /v1/me has no id', async () => { + const api = new Api({ + client_id: 'c', + client_secret: 's', + redirect_uri: 'https://example.com/r', + scope: 'current_user:read', + }); + api.getMe = async () => ({}); + await expect( + Definition.requiredAuthMethods.getEntityDetails(api, {}, {}, 'u') + ).rejects.toThrow(/did not return a user id/i); + }); + + it('getCredentialDetails mirrors entity externalId from /v1/me', async () => { + const api = new Api({ + client_id: 'c', + client_secret: 's', + redirect_uri: 'https://example.com/r', + scope: 'current_user:read', + }); + api.getMe = async () => ({ + id: '99', + handle: 'A', + email: 'a@x.com', + img_url: '', + }); + const details = + await Definition.requiredAuthMethods.getCredentialDetails( + api, + 'user-c' + ); + expect(details.identifiers.externalId).toBe('99'); + expect(details.identifiers.userId).toBe('user-c'); + }); + + it('testAuthRequest calls getMe', async () => { + const api = new Api({ + client_id: 'c', + client_secret: 's', + redirect_uri: 'https://example.com/r', + scope: 'current_user:read', + }); + let called = false; + api.getMe = async () => { + called = true; + return { id: '1', handle: 'x', email: 'x@y.z', img_url: '' }; + }; + await Definition.requiredAuthMethods.testAuthRequest(api); + expect(called).toBe(true); + }); + + it('requiredAuthMethods matches the api-module definition contract', () => { + const methods = Definition.requiredAuthMethods; + expect(typeof methods.getToken).toBe('function'); + expect(typeof methods.getEntityDetails).toBe('function'); + expect(typeof methods.getCredentialDetails).toBe('function'); + expect(typeof methods.testAuthRequest).toBe('function'); + expect(methods.apiPropertiesToPersist).toBeTruthy(); + // OAuth modules use getToken, not setAuthParams + expect(methods.setAuthParams).toBeUndefined(); + }); + + it('capability manifest maps every declared method to an Api method', () => { + const manifest = JSON.parse( + fs.readFileSync( + path.join(__dirname, '..', 'figma.capabilities.json'), + 'utf8' + ) + ); + expect(manifest.schema).toBe('capability-manifest/v0.1'); + expect(manifest.module).toBe('figma'); + expect(manifest.package).toBe('@friggframework/api-module-figma'); + expect(manifest.version).toBe( + require('../package.json').version + ); + + const apiMethods = Object.getOwnPropertyNames(Api.prototype).filter( + (m) => typeof Api.prototype[m] === 'function' && m !== 'constructor' + ); + + const declared = new Set(); + for (const obj of Object.values(manifest.objects)) { + expect(Array.isArray(obj.fields)).toBe(true); + for (const field of obj.fields) { + expect(Array.isArray(field.tags)).toBe(true); + } + for (const methodName of Object.values(obj.methods || {})) { + declared.add(methodName); + expect(apiMethods).toContain(methodName); + } + } + expect(declared.has('getMe')).toBe(true); + expect(declared.has('postWebhook')).toBe(true); + }); +}); diff --git a/packages/v1-ready/figma/tests/passcode-verifier.test.js b/packages/v1-ready/figma/tests/passcode-verifier.test.js new file mode 100644 index 00000000..599df8d6 --- /dev/null +++ b/packages/v1-ready/figma/tests/passcode-verifier.test.js @@ -0,0 +1,35 @@ +const { + verifyFigmaPasscode, +} = require('../extensions/webhooks/passcode-verifier'); + +describe('verifyFigmaPasscode', () => { + it('accepts a matching passcode', () => { + expect( + verifyFigmaPasscode({ + body: { event_type: 'PING', passcode: 'secret' }, + expectedPasscode: 'secret', + }) + ).toEqual({ valid: true }); + }); + + it('rejects mismatches and missing values', () => { + expect( + verifyFigmaPasscode({ + body: { passcode: 'nope' }, + expectedPasscode: 'secret', + }).valid + ).toBe(false); + expect( + verifyFigmaPasscode({ + body: {}, + expectedPasscode: 'secret', + }).valid + ).toBe(false); + expect( + verifyFigmaPasscode({ + body: { passcode: 'secret' }, + expectedPasscode: '', + }).valid + ).toBe(false); + }); +}); diff --git a/packages/v1-ready/figma/tests/spec-sync.test.js b/packages/v1-ready/figma/tests/spec-sync.test.js new file mode 100644 index 00000000..c7714a2a --- /dev/null +++ b/packages/v1-ready/figma/tests/spec-sync.test.js @@ -0,0 +1,46 @@ +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); +const { Api } = require('../api'); + +const spec = yaml.load( + fs.readFileSync(path.join(__dirname, '..', 'figma.openapi.yaml'), 'utf8') +); + +const specOperationIds = Object.values(spec.paths).flatMap((item) => + Object.entries(item) + .filter(([m]) => ['get', 'post', 'patch', 'put', 'delete'].includes(m)) + .map(([, op]) => op.operationId) +); + +const clientMethods = Object.getOwnPropertyNames(Api.prototype).filter( + (m) => typeof Api.prototype[m] === 'function' && m !== 'constructor' +); + +describe('OpenAPI spec ↔ client sync', () => { + it('every operationId has a matching client method', () => { + const missing = specOperationIds.filter( + (op) => !clientMethods.includes(op) + ); + expect(missing).toEqual([]); + }); + + it('the base server URL matches the client baseUrl', () => { + const api = new Api({ + client_id: 'cid', + client_secret: 'csecret', + redirect_uri: 'https://example.com/redirect/figma', + scope: 'current_user:read', + }); + expect(spec.servers[0].url).toBe(api.baseUrl); + }); + + it('declares OAuth2 security with Figma authorize/token/refresh URLs', () => { + const scheme = spec.components.securitySchemes.OAuth2; + expect(scheme.type).toBe('oauth2'); + const flow = scheme.flows.authorizationCode; + expect(flow.authorizationUrl).toBe('https://www.figma.com/oauth'); + expect(flow.tokenUrl).toBe('https://api.figma.com/v1/oauth/token'); + expect(flow.refreshUrl).toBe('https://api.figma.com/v1/oauth/refresh'); + }); +}); From ad2d1700138aaafb2d5793e096455885f4474164 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 04:43:29 +0000 Subject: [PATCH 2/2] fix(figma): align OpenAPI subset with official figma/rest-api-spec Regenerate figma.openapi.yaml as a path-filtered extract of the vendor OpenAPI (params, request/response schemas, per-op scopes, server URL). Add project_metadata:read, forward getFolderFiles branch_data, and sync capability field names to official schemas. Co-authored-by: Sean Matthews --- packages/v1-ready/figma/.env.example | 2 +- packages/v1-ready/figma/README.md | 7 +- packages/v1-ready/figma/api.js | 18 +- packages/v1-ready/figma/definition.js | 4 + .../v1-ready/figma/figma.capabilities.json | 42 +- packages/v1-ready/figma/figma.openapi.yaml | 4961 ++++++++++++++++- packages/v1-ready/figma/tests/api.test.js | 12 +- .../v1-ready/figma/tests/definition.test.js | 2 + .../v1-ready/figma/tests/spec-sync.test.js | 54 +- 9 files changed, 4967 insertions(+), 135 deletions(-) diff --git a/packages/v1-ready/figma/.env.example b/packages/v1-ready/figma/.env.example index 090dbcb9..b130a9cf 100644 --- a/packages/v1-ready/figma/.env.example +++ b/packages/v1-ready/figma/.env.example @@ -1,7 +1,7 @@ FIGMA_CLIENT_ID= FIGMA_CLIENT_SECRET= # Comma- or space-separated scopes. Defaults cover user, files, comments, projects/folders, webhooks. -# FIGMA_SCOPE=current_user:read,file_content:read,file_metadata:read,file_comments:read,file_comments:write,projects:read,folders:read,folder_metadata:read,webhooks:read,webhooks:write +# FIGMA_SCOPE=current_user:read,file_content:read,file_metadata:read,file_comments:read,file_comments:write,projects:read,project_metadata:read,folders:read,folder_metadata:read,webhooks:read,webhooks:write REDIRECT_URI=https://localhost:3000/redirect # Shared passcode used when creating webhooks and verifying inbound deliveries FIGMA_WEBHOOK_PASSCODE= diff --git a/packages/v1-ready/figma/README.md b/packages/v1-ready/figma/README.md index c4f8210d..684f6aa3 100644 --- a/packages/v1-ready/figma/README.md +++ b/packages/v1-ready/figma/README.md @@ -48,7 +48,7 @@ Method names match official OpenAPI `operationId`s | `getProjectFiles(projectId, query)` | `GET /v1/projects/{id}/files` | Files in a project. | | `getProjectMeta(projectId)` | `GET /v1/projects/{id}/meta` | Project metadata. | | `getTeamFolders(teamId)` | `GET /v2/teams/{id}/folders` | List folders. | -| `getFolderFiles(folderId)` | `GET /v2/folders/{id}/files` | Files in a folder. | +| `getFolderFiles(folderId, query)` | `GET /v2/folders/{id}/files` | Files in a folder (`branch_data` optional). | | `getFolderMeta(folderId)` | `GET /v2/folders/{id}/meta` | Folder metadata. | | `getWebhooks(query)` | `GET /v2/webhooks` | List webhooks by context or plan. | | `postWebhook(data)` | `POST /v2/webhooks` | Create a webhook. | @@ -88,12 +88,15 @@ signature header). See ## Sources +- **Vendor OpenAPI (canonical for paths/params/schemas/scopes):** https://raw.githubusercontent.com/figma/rest-api-spec/main/openapi/openapi.yaml +- https://github.com/figma/rest-api-spec - https://developers.figma.com/docs/rest-api/ - https://developers.figma.com/docs/rest-api/authentication/ - https://developers.figma.com/docs/rest-api/oauth-apps/ - https://developers.figma.com/docs/rest-api/scopes/ - https://developers.figma.com/docs/rest-api/webhooks/ -- https://github.com/figma/rest-api-spec + +`figma.openapi.yaml` is a path-filtered extract of that vendor document (callbacks omitted). Server URL, per-operation security/scopes, parameters, and request/response schemas are taken from it. ## Tests diff --git a/packages/v1-ready/figma/api.js b/packages/v1-ready/figma/api.js index 6bb5a8ad..2f3472ce 100644 --- a/packages/v1-ready/figma/api.js +++ b/packages/v1-ready/figma/api.js @@ -201,10 +201,14 @@ class Api extends OAuth2Requester { }); } - /** GET /v2/folders/{folder_id}/files */ - async getFolderFiles(folderId) { + /** + * GET /v2/folders/{folder_id}/files + * Optional query: branch_data (boolean, default false) + */ + async getFolderFiles(folderId, query = {}) { return this._get({ url: this.baseUrl + this.URLs.folderFiles(folderId), + query, }); } @@ -231,6 +235,9 @@ class Api extends OAuth2Requester { /** * POST /v2/webhooks * Body requires event_type, endpoint, passcode, context, context_id. + * event_type enum (official): FILE_UPDATE | FILE_VERSION_UPDATE | + * FILE_DELETE | LIBRARY_PUBLISH | FILE_COMMENT | DEV_MODE_STATUS_UPDATE + * (PING is delivery-only; not a creatable subscription). */ async postWebhook(data) { return this._post({ @@ -247,7 +254,12 @@ class Api extends OAuth2Requester { }); } - /** PUT /v2/webhooks/{webhook_id} */ + /** + * PUT /v2/webhooks/{webhook_id} + * Official OpenAPI marks required: event_type, team_id, endpoint, passcode + * (team_id is required in the schema but not listed under properties — + * preserved as published by figma/rest-api-spec). + */ async putWebhook(webhookId, data) { return this._put({ url: this.baseUrl + this.URLs.webhook(webhookId), diff --git a/packages/v1-ready/figma/definition.js b/packages/v1-ready/figma/definition.js index 7f62e465..47062600 100644 --- a/packages/v1-ready/figma/definition.js +++ b/packages/v1-ready/figma/definition.js @@ -11,6 +11,9 @@ const config = require('./defaultConfig.json'); * Default scopes cover the core objects this module implements. Override via * FIGMA_SCOPE (space- or comma-separated). */ +// Scopes required by the curated OpenAPI operations (figma/rest-api-spec). +// Includes project_metadata:read for GET /v1/projects/{id}/meta and +// folder_metadata:read for GET /v2/folders/{id}/meta. const DEFAULT_SCOPES = [ 'current_user:read', 'file_content:read', @@ -18,6 +21,7 @@ const DEFAULT_SCOPES = [ 'file_comments:read', 'file_comments:write', 'projects:read', + 'project_metadata:read', 'folders:read', 'folder_metadata:read', 'webhooks:read', diff --git a/packages/v1-ready/figma/figma.capabilities.json b/packages/v1-ready/figma/figma.capabilities.json index 4591edee..ca27c1c0 100644 --- a/packages/v1-ready/figma/figma.capabilities.json +++ b/packages/v1-ready/figma/figma.capabilities.json @@ -16,17 +16,18 @@ "file_comments:read", "file_comments:write", "projects:read", + "project_metadata:read", "folders:read", "folder_metadata:read", "webhooks:read", "webhooks:write" ], "identityMethod": "getMe", - "notes": "Public OAuth apps require Figma review before end-user authorization. Private apps are limited to the associated team/org." + "notes": "Public OAuth apps require Figma review before end-user authorization. Private apps are limited to the associated team/org. Scope list matches the curated OpenAPI operations from figma/rest-api-spec." }, "objects": { "user": { - "description": "Authenticated Figma user (GET /v1/me)", + "description": "Authenticated Figma user (GET /v1/me → User + email)", "methods": { "get": "getMe" }, @@ -38,23 +39,25 @@ ] }, "file": { - "description": "Figma design file document and metadata", + "description": "Figma design file. getFile returns camelCase document fields; list/meta endpoints use snake_case on nested file objects.", "methods": { "get": "getFile", "getMeta": "getFileMeta" }, "fields": [ - { "name": "key", "type": "string", "tags": ["identifier"] }, { "name": "name", "type": "string", "tags": ["displayName"] }, { "name": "lastModified", "type": "string", "tags": ["timestamp"] }, { "name": "thumbnailUrl", "type": "string", "tags": ["thumbnail"] }, { "name": "version", "type": "string", "tags": [] }, { "name": "editorType", "type": "string", "tags": [] }, - { "name": "document", "type": "object", "tags": ["content"] } + { "name": "role", "type": "string", "tags": [] }, + { "name": "linkAccess", "type": "string", "tags": [] }, + { "name": "document", "type": "object", "tags": ["content"] }, + { "name": "schemaVersion", "type": "number", "tags": [] } ] }, "project": { - "description": "Legacy team project (folder). Prefer folder object for new work.", + "description": "Legacy team project. List items: id, name. Meta: id, name, created_at, updated_at, file_count, thumbnail_url.", "methods": { "listByTeam": "getTeamProjects", "listFiles": "getProjectFiles", @@ -62,11 +65,15 @@ }, "fields": [ { "name": "id", "type": "string", "tags": ["identifier"] }, - { "name": "name", "type": "string", "tags": ["displayName"] } + { "name": "name", "type": "string", "tags": ["displayName"] }, + { "name": "created_at", "type": "string", "tags": ["timestamp"] }, + { "name": "updated_at", "type": "string", "tags": ["timestamp"] }, + { "name": "file_count", "type": "number", "tags": [] }, + { "name": "thumbnail_url", "type": "string", "tags": ["thumbnail"] } ] }, "folder": { - "description": "Team folder (replaces projects). List files and read metadata.", + "description": "Team folder (replaces projects). List items include parent_folder_id and sharing_audience_control; meta matches project meta shape.", "methods": { "listByTeam": "getTeamFolders", "listFiles": "getFolderFiles", @@ -74,11 +81,17 @@ }, "fields": [ { "name": "id", "type": "string", "tags": ["identifier"] }, - { "name": "name", "type": "string", "tags": ["displayName"] } + { "name": "name", "type": "string", "tags": ["displayName"] }, + { "name": "created_at", "type": "string", "tags": ["timestamp"] }, + { "name": "parent_folder_id", "type": "string", "tags": ["parent"] }, + { "name": "sharing_audience_control", "type": "string", "tags": [] }, + { "name": "updated_at", "type": "string", "tags": ["timestamp"] }, + { "name": "file_count", "type": "number", "tags": [] }, + { "name": "thumbnail_url", "type": "string", "tags": ["thumbnail"] } ] }, "comment": { - "description": "File comments. Public API supports list/create/delete (no update).", + "description": "File comments (Comment schema). Public API supports list/create/delete (no update).", "methods": { "list": "getComments", "create": "postComment", @@ -88,13 +101,17 @@ { "name": "id", "type": "string", "tags": ["identifier"] }, { "name": "message", "type": "string", "tags": ["content"] }, { "name": "file_key", "type": "string", "tags": ["parent"] }, + { "name": "parent_id", "type": "string", "tags": ["parent"] }, + { "name": "order_id", "type": "string", "tags": [] }, + { "name": "client_meta", "type": "object", "tags": [] }, + { "name": "reactions", "type": "array", "tags": [] }, { "name": "user", "type": "object", "tags": ["author"] }, { "name": "created_at", "type": "string", "tags": ["timestamp"] }, { "name": "resolved_at", "type": "string", "tags": ["timestamp"] } ] }, "webhook": { - "description": "Webhooks V2 subscription on a team, project/folder, or file context", + "description": "Webhooks V2 (WebhookV2 schema) on team, project/folder, or file context", "methods": { "list": "getWebhooks", "create": "postWebhook", @@ -107,8 +124,11 @@ "fields": [ { "name": "id", "type": "string", "tags": ["identifier"] }, { "name": "event_type", "type": "string", "tags": ["event"] }, + { "name": "team_id", "type": "string", "tags": [] }, { "name": "context", "type": "string", "tags": [] }, { "name": "context_id", "type": "string", "tags": ["parent"] }, + { "name": "plan_api_id", "type": "string", "tags": [] }, + { "name": "client_id", "type": "string", "tags": [] }, { "name": "endpoint", "type": "string", "tags": ["url"] }, { "name": "passcode", "type": "string", "tags": ["secret", "redactedOnRead"] }, { "name": "status", "type": "string", "tags": ["status"] }, diff --git a/packages/v1-ready/figma/figma.openapi.yaml b/packages/v1-ready/figma/figma.openapi.yaml index 4d3dc63e..542b0d8d 100644 --- a/packages/v1-ready/figma/figma.openapi.yaml +++ b/packages/v1-ready/figma/figma.openapi.yaml @@ -2,16 +2,17 @@ openapi: 3.1.0 info: title: Figma REST API (Frigg curated subset) version: 0.45.0 - description: Curated subset of the official Figma REST API OpenAPI spec (https://github.com/figma/rest-api-spec) - covering exactly the endpoints implemented by @friggframework/api-module-figma. One Api method per - operationId. Source of truth for spec-sync tests. + description: 'Curated subset of the official Figma REST API OpenAPI specification https://raw.githubusercontent.com/figma/rest-api-spec/main/openapi/openapi.yaml + (repo: https://github.com/figma/rest-api-spec). Paths, methods, parameters, request/response schemas, + auth/scopes, and server URL are taken verbatim from that document for the operations implemented by + @friggframework/api-module-figma. Webhook delivery callbacks are omitted (not client operations). + One Api method per operationId; source of truth for spec-sync tests.' contact: name: Left Hook url: https://lefthook.com + license: null servers: - url: https://api.figma.com -security: -- OAuth2: [] tags: - name: Users - name: Files @@ -34,13 +35,13 @@ paths: operationId: getMe responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetMeResponse' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/files/{file_key}: get: tags: @@ -121,17 +122,17 @@ paths: default: false responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetFileResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrMessage' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/files/{file_key}/meta: get: tags: @@ -155,17 +156,17 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetFileMetaResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrMessage' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/files/{file_key}/comments: get: tags: @@ -194,15 +195,15 @@ paths: type: boolean responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetCommentsResponse' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' post: tags: - Comments @@ -247,17 +248,17 @@ paths: - message responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/PostCommentResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/files/{file_key}/comments/{comment_id}: delete: tags: @@ -286,15 +287,15 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/DeleteCommentResponse' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/teams/{team_id}/projects: get: tags: @@ -323,15 +324,15 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetTeamProjectsResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/projects/{project_id}/files: get: tags: @@ -363,15 +364,15 @@ paths: default: false responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetProjectFilesResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v1/projects/{project_id}/meta: get: tags: @@ -395,15 +396,15 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetProjectMetaResponse' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v2/teams/{team_id}/folders: get: tags: @@ -429,17 +430,17 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetTeamFoldersResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v2/folders/{folder_id}/files: get: tags: @@ -468,17 +469,17 @@ paths: default: false responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetFolderFilesResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v2/folders/{folder_id}/meta: get: tags: @@ -500,15 +501,15 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetFolderMetaResponse' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v2/webhooks: get: tags: @@ -555,11 +556,11 @@ paths: from the previous response to get the next or previous page of results. responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetWebhooksResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' post: tags: - Webhooks @@ -583,8 +584,7 @@ paths: type: object properties: event_type: - type: string - description: 'see official schema #/components/schemas/WebhookV2Event' + $ref: '#/components/schemas/WebhookV2Event' team_id: type: string description: Team id to receive updates about. This is deprecated, use 'context' and @@ -605,8 +605,8 @@ paths: description: String that will be passed back to your webhook endpoint to verify that it is being called by Figma. Max length 100 characters. status: - type: string - description: 'see official schema #/components/schemas/WebhookV2Status' + $ref: '#/components/schemas/WebhookV2Status' + description: State of the webhook, including any error state it may be in description: type: string description: User provided description or name for the webhook. Max length 150 characters. @@ -618,15 +618,15 @@ paths: - context_id responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/PostWebhookResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v2/webhooks/{webhook_id}: get: tags: @@ -649,17 +649,17 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetWebhookResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrorBoolean' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' put: tags: - Webhooks @@ -687,8 +687,7 @@ paths: type: object properties: event_type: - type: string - description: 'see official schema #/components/schemas/WebhookV2Event' + $ref: '#/components/schemas/WebhookV2Event' endpoint: type: string description: The HTTP endpoint that will receive a POST request when the event triggers. @@ -698,8 +697,8 @@ paths: description: String that will be passed back to your webhook endpoint to verify that it is being called by Figma. Max length 100 characters. status: - type: string - description: 'see official schema #/components/schemas/WebhookV2Status' + $ref: '#/components/schemas/WebhookV2Status' + description: State of the webhook, including any error state it may be in description: type: string description: User provided description or name for the webhook. Max length 150 characters. @@ -710,17 +709,17 @@ paths: - passcode responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/PutWebhookResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrorBoolean' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' delete: tags: - Webhooks @@ -741,17 +740,17 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/DeleteWebhookResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrorBoolean' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrorBoolean' /v2/teams/{team_id}/webhooks: get: tags: @@ -775,15 +774,15 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetTeamWebhooksResponse' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrMessage' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrMessage' /v2/webhooks/{webhook_id}/requests: get: tags: @@ -806,18 +805,4751 @@ paths: type: string responses: '200': - description: Response 200 (see official Figma OpenAPI) + $ref: '#/components/responses/GetWebhookRequestsResponse' '400': - description: Response 400 (see official Figma OpenAPI) + $ref: '#/components/responses/BadRequestErrorResponseWithErrorBoolean' '403': - description: Response 403 (see official Figma OpenAPI) + $ref: '#/components/responses/ForbiddenErrorResponseWithErrMessage' '404': - description: Response 404 (see official Figma OpenAPI) + $ref: '#/components/responses/NotFoundErrorResponseWithErrorBoolean' '429': - description: Response 429 (see official Figma OpenAPI) + $ref: '#/components/responses/TooManyRequestsErrorResponseWithErrMessage' '500': - description: Response 500 (see official Figma OpenAPI) + $ref: '#/components/responses/InternalServerErrorResponseWithErrorBoolean' components: + responses: + BadRequestErrorResponseWithErrMessage: + description: Bad request. Parameters are invalid or malformed. Please check the input formats. This + error can also happen if the requested resources are too large to complete the request, which + results in a timeout. Please reduce the number and size of objects requested. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrMessage' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 400 + required: + - status + BadRequestErrorResponseWithErrorBoolean: + description: Bad request. Parameters are invalid or malformed. Please check the input formats. This + error can also happen if the requested resources are too large to complete the request, which + results in a timeout. Please reduce the number and size of objects requested. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrorBoolean' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 400 + required: + - status + DeleteCommentResponse: + description: Response from the DELETE /v1/files/{file_key}/comments/{comment_id} endpoint. + content: + application/json: + schema: + type: object + properties: + status: + type: number + description: The status of the request. + enum: + - 200 + error: + type: boolean + description: For successful requests, this value is always `false`. + enum: + - false + required: + - status + - error + DeleteWebhookResponse: + description: Response from the DELETE /v2/webhooks/{webhook_id} endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookV2' + ForbiddenErrorResponseWithErrMessage: + description: The request was valid, but the server is refusing action. The user might not have the + necessary permissions for a resource, or may need an account of some sort. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrMessage' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 403 + required: + - status + GetCommentsResponse: + description: Response from the GET /v1/files/{file_key}/comments endpoint. + content: + application/json: + schema: + type: object + properties: + comments: + type: array + description: An array of comments. + items: + $ref: '#/components/schemas/Comment' + required: + - comments + GetFileMetaResponse: + description: Response from the GET /v1/files/{file_key}/meta endpoint. + content: + application/json: + schema: + type: object + properties: + file: + type: object + description: The file metadata. + properties: + name: + type: string + description: The name of the file. + folder_name: + type: string + description: The name of the project containing the file. + last_touched_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the file content was last modified. + creator: + description: The user who created the file. + $ref: '#/components/schemas/User' + last_touched_by: + description: The user who last modified the file contents. + $ref: '#/components/schemas/User' + thumbnail_url: + type: string + description: A URL to a thumbnail image of the file. + editorType: + type: string + enum: + - figma + - figjam + - slides + - buzz + - sites + - make + description: The type of editor associated with this file. + role: + $ref: '#/components/schemas/Role' + description: The role of the user making the API request in relation to the file. + link_access: + $ref: '#/components/schemas/LinkAccess' + description: Access policy for users who have the link to the file. + url: + type: string + description: The URL of the file. + version: + type: string + description: The version number of the file. This number is incremented when a file + is modified and can be used to check if the file has changed between requests. + required: + - name + - last_touched_at + - creator + - editorType + required: + - file + GetFileResponse: + description: Response from the GET /v1/files/{file_key} endpoint. + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The name of the file as it appears in the editor. + role: + $ref: '#/components/schemas/Role' + description: The role of the user making the API request in relation to the file. + lastModified: + type: string + format: date-time + description: The UTC ISO 8601 time at which the file was last modified. + editorType: + type: string + enum: + - figma + - figjam + description: The type of editor associated with this file. + thumbnailUrl: + type: string + description: A URL to a thumbnail image of the file. + version: + type: string + description: The version number of the file. This number is incremented when a file is + modified and can be used to check if the file has changed between requests. + document: + $ref: '#/components/schemas/DocumentNode' + components: + type: object + additionalProperties: + $ref: '#/components/schemas/Component' + description: A mapping from component IDs to component metadata. + componentSets: + type: object + additionalProperties: + $ref: '#/components/schemas/ComponentSet' + description: A mapping from component set IDs to component set metadata. + schemaVersion: + type: number + description: The version of the file schema that this file uses. + default: 0 + styles: + type: object + additionalProperties: + $ref: '#/components/schemas/Style' + description: A mapping from style IDs to style metadata. + linkAccess: + type: string + description: The share permission level of the file link. + mainFileKey: + type: string + description: The key of the main file for this file. If present, this file is a component + or component set. + branches: + type: array + description: A list of branches for this file. + items: + type: object + properties: + key: + type: string + description: The key of the branch. + name: + type: string + description: The name of the branch. + thumbnail_url: + type: string + description: A URL to a thumbnail image of the branch. + last_modified: + type: string + format: date-time + description: The UTC ISO 8601 time at which the branch was last modified. + required: + - key + - name + - thumbnail_url + - last_modified + required: + - name + - role + - lastModified + - editorType + - version + - document + - components + - componentSets + - schemaVersion + - styles + GetFolderFilesResponse: + description: Response from the GET /v2/folders/{folder_id}/files endpoint. + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The folder's name. + files: + type: array + description: An array of the files directly in the folder. + items: + type: object + properties: + key: + type: string + description: The file's key. + name: + type: string + description: The file's name. + thumbnail_url: + type: string + description: The file's thumbnail URL. + last_modified: + type: string + format: date-time + description: The UTC ISO 8601 time at which the file was last modified. + branches: + type: array + description: Present only when `branch_data` is true and the file has branches. + An array of the file's branches. + items: + type: object + properties: + key: + type: string + description: The branch's file key. + name: + type: string + description: The branch's name. + thumbnail_url: + type: string + description: The branch's thumbnail URL. + last_modified: + type: string + format: date-time + description: The UTC ISO 8601 time at which the branch was last modified. + required: + - key + - name + - last_modified + required: + - key + - name + - last_modified + required: + - name + - files + GetFolderMetaResponse: + description: Response from the GET /v2/folders/{folder_id}/meta endpoint. + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: The ID of the folder. + name: + type: string + description: The name of the folder. + thumbnail_url: + type: + - string + - 'null' + description: A URL to a thumbnail image of the folder. Null if the folder has no files. + file_count: + type: integer + description: The number of files in the folder. + updated_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the folder was last updated. + created_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the folder was created. + required: + - id + - name + - thumbnail_url + - file_count + - updated_at + - created_at + GetMeResponse: + description: Response from the GET /v1/me endpoint. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/User' + - type: object + properties: + email: + type: string + description: Email associated with the user's account. This property is only present + on the /v1/me endpoint. + required: + - email + GetProjectFilesResponse: + description: Response from the GET /v1/projects/{project_id}/files endpoint. + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The project's name. + files: + type: array + description: An array of files. + items: + type: object + properties: + key: + type: string + description: The file's key. + name: + type: string + description: The file's name. + thumbnail_url: + type: string + description: The file's thumbnail URL. + last_modified: + type: string + format: date-time + description: The UTC ISO 8601 time at which the file was last modified. + required: + - key + - name + - last_modified + required: + - name + - files + GetProjectMetaResponse: + description: Response from the GET /v1/projects/{project_id}/meta endpoint. + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: The unique identifier of the project. + name: + type: string + description: The name of the project. + thumbnail_url: + type: + - string + - 'null' + description: A URL to a thumbnail image of the project. May be null if the project has + no files. + file_count: + type: integer + description: The number of files in the project. + updated_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the project was last updated. + created_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the project was created. + required: + - id + - name + - thumbnail_url + - file_count + - updated_at + - created_at + GetTeamFoldersResponse: + description: Response from the GET /v2/teams/{team_id}/folders endpoint. + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The team's name. + folders: + type: array + description: An array of the team's top-level folders. Each folder's `parent_folder_id` + is null. + items: + $ref: '#/components/schemas/Folder' + required: + - name + - folders + GetTeamProjectsResponse: + description: Response from the GET /v1/teams/{team_id}/projects endpoint. + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The team's name. + projects: + type: array + description: An array of projects. + items: + $ref: '#/components/schemas/Project' + required: + - name + - projects + GetTeamWebhooksResponse: + description: Response from the GET /v2/teams/{team_id}/webhooks endpoint. + content: + application/json: + schema: + type: object + properties: + webhooks: + type: array + description: An array of webhooks. + items: + $ref: '#/components/schemas/WebhookV2' + required: + - webhooks + GetWebhookRequestsResponse: + description: Response from the GET /v2/webhooks/{webhook_id}/requests endpoint. + content: + application/json: + schema: + type: object + properties: + requests: + type: array + description: An array of webhook requests. + items: + $ref: '#/components/schemas/WebhookV2Request' + required: + - requests + GetWebhookResponse: + description: Response from the GET /v2/webhooks/{webhook_id} endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookV2' + GetWebhooksResponse: + description: Response from the GET /v2/webhooks endpoint. + content: + application/json: + schema: + type: object + properties: + webhooks: + type: array + description: An array of webhooks. + items: + $ref: '#/components/schemas/WebhookV2' + pagination: + $ref: '#/components/schemas/ResponsePagination' + required: + - webhooks + InternalServerErrorResponseWithErrMessage: + description: An internal server error occurred. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrMessage' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 500 + required: + - status + InternalServerErrorResponseWithErrorBoolean: + description: An internal server error occurred. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrorBoolean' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 500 + required: + - status + NotFoundErrorResponseWithErrMessage: + description: The requested file or resource was not found. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrMessage' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 404 + required: + - status + NotFoundErrorResponseWithErrorBoolean: + description: The requested file or resource was not found. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrorBoolean' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 404 + required: + - status + PostCommentResponse: + description: Response from the POST /v1/files/{file_key}/comments endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + PostWebhookResponse: + description: Response from the POST /v2/webhooks endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookV2' + PutWebhookResponse: + description: Response from the PUT /v2/webhooks/{webhook_id} endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookV2' + TooManyRequestsErrorResponseWithErrMessage: + description: In some cases API requests may be throttled or rate limited. Please wait a while before + attempting the request again (typically a minute). + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ErrorResponsePayloadWithErrMessage' + - type: object + properties: + status: + type: number + description: Status code + enum: + - 429 + required: + - status + schemas: + Action: + type: object + description: An action that is performed when a trigger is activated. + oneOf: + - type: object + properties: + type: + type: string + enum: + - BACK + - CLOSE + required: + - type + - $ref: '#/components/schemas/OpenURLAction' + - $ref: '#/components/schemas/UpdateMediaRuntimeAction' + - $ref: '#/components/schemas/SetVariableAction' + - $ref: '#/components/schemas/SetVariableModeAction' + - $ref: '#/components/schemas/ConditionalAction' + - $ref: '#/components/schemas/NodeAction' + AfterTimeoutTrigger: + type: object + properties: + type: + type: string + enum: + - AFTER_TIMEOUT + timeout: + type: number + required: + - type + - timeout + AnnotationsTrait: + type: object + properties: {} + ArcData: + type: object + description: Information about the arc properties of an ellipse. 0° is the x axis and increasing + angles rotate clockwise. + properties: + startingAngle: + type: number + description: Start of the sweep in radians. + default: 0 + endingAngle: + type: number + description: End of the sweep in radians. + default: 0 + innerRadius: + type: number + description: Inner radius value between 0 and 1 + default: 0 + minimum: 0 + maximum: 1 + required: + - startingAngle + - endingAngle + - innerRadius + BaseBlurEffect: + type: object + description: Base properties shared by all blur effects + properties: + type: + type: string + description: A string literal representing the effect's type. Always check the type before reading + other properties. + enum: + - LAYER_BLUR + - BACKGROUND_BLUR + visible: + type: boolean + description: Whether this blur is active. + radius: + type: number + description: Radius of the blur effect + minimum: 0 + boundVariables: + type: object + description: The variables bound to a particular field on this blur effect + properties: + radius: + $ref: '#/components/schemas/VariableAlias' + required: + - type + - visible + - radius + BaseNoiseEffect: + type: object + description: A noise effect + properties: + type: + type: string + description: The string literal 'NOISE' representing the effect's type. Always check the type + before reading other properties. + enum: + - NOISE + color: + $ref: '#/components/schemas/RGBA' + description: The color of the noise effect + visible: + type: boolean + description: Whether the noise effect is visible. + blendMode: + $ref: '#/components/schemas/BlendMode' + description: Blend mode of the noise effect + noiseSize: + type: number + description: The size of the noise effect + density: + type: number + description: The density of the noise effect + required: + - type + - color + - visible + - noiseSize + - density + - blendMode + BasePaint: + type: object + properties: + visible: + type: boolean + description: Is the paint enabled? + default: true + opacity: + type: number + description: Overall opacity of paint (colors within the paint can also have opacity values + which would blend with this) + minimum: 0 + maximum: 1 + default: 1 + blendMode: + $ref: '#/components/schemas/BlendMode' + description: How this node blends with nodes behind it in the scene + required: + - blendMode + BaseShadowEffect: + type: object + description: Base properties shared by all shadow effects + properties: + color: + $ref: '#/components/schemas/RGBA' + description: The color of the shadow + blendMode: + $ref: '#/components/schemas/BlendMode' + description: Blend mode of the shadow + offset: + $ref: '#/components/schemas/Vector' + description: How far the shadow is projected in the x and y directions + radius: + type: number + description: Radius of the blur effect (applies to shadows as well) + minimum: 0 + spread: + type: number + description: 'The distance by which to expand (or contract) the shadow. + + + For drop shadows, a positive `spread` value creates a shadow larger than the node, whereas + a negative value creates a shadow smaller than the node. + + + For inner shadows, a positive `spread` value contracts the shadow. Spread values are only + accepted on rectangles and ellipses, or on frames, components, and instances with visible + fill paints and `clipsContent` enabled. When left unspecified, the default value is 0.' + default: 0 + visible: + type: boolean + description: Whether this shadow is visible. + boundVariables: + type: object + description: The variables bound to a particular field on this shadow effect + properties: + radius: + $ref: '#/components/schemas/VariableAlias' + spread: + $ref: '#/components/schemas/VariableAlias' + color: + $ref: '#/components/schemas/VariableAlias' + offsetX: + $ref: '#/components/schemas/VariableAlias' + offsetY: + $ref: '#/components/schemas/VariableAlias' + required: + - color + - blendMode + - offset + - radius + - visible + BaseTypeStyle: + type: object + properties: + fontFamily: + type: string + description: Font family of text (standard name). + fontPostScriptName: + type: + - string + - 'null' + description: PostScript font name. + fontStyle: + type: string + description: Describes visual weight or emphasis, such as Bold or Italic. + italic: + type: boolean + description: Whether or not text is italicized. + default: false + fontWeight: + type: number + description: Numeric font weight. + fontSize: + type: number + description: Font size in px. + textCase: + type: string + description: Text casing applied to the node, default is the original casing. + enum: + - ORIGINAL + - UPPER + - LOWER + - TITLE + - SMALL_CAPS + - SMALL_CAPS_FORCED + textAlignHorizontal: + type: string + description: Horizontal text alignment as string enum. + enum: + - LEFT + - RIGHT + - CENTER + - JUSTIFIED + textAlignVertical: + type: string + description: Vertical text alignment as string enum. + enum: + - TOP + - CENTER + - BOTTOM + letterSpacing: + type: number + description: Space between characters in px. + fills: + type: array + items: + $ref: '#/components/schemas/Paint' + description: An array of fill paints applied to the characters. + hyperlink: + $ref: '#/components/schemas/Hyperlink' + description: Link to a URL or frame. + opentypeFlags: + type: object + additionalProperties: + type: number + description: A map of OpenType feature flags to 1 or 0, 1 if it is enabled and 0 if it is disabled. + Note that some flags aren't reflected here. For example, SMCP (small caps) is still represented + by the `textCase` field. + semanticWeight: + type: string + description: Indicates how the font weight was overridden when there is a text style override. + enum: + - BOLD + - NORMAL + semanticItalic: + type: string + description: Indicates how the font style was overridden when there is a text style override. + enum: + - ITALIC + - NORMAL + BlendMode: + type: string + description: 'This type is a string enum with the following possible values + + + Normal blends: + + - `PASS_THROUGH` (only applicable to objects with children) + + - `NORMAL` + + + Darken: + + - `DARKEN` + + - `MULTIPLY` + + - `LINEAR_BURN` + + - `COLOR_BURN` + + + Lighten: + + - `LIGHTEN` + + - `SCREEN` + + - `LINEAR_DODGE` + + - `COLOR_DODGE` + + + Contrast: + + - `OVERLAY` + + - `SOFT_LIGHT` + + - `HARD_LIGHT` + + + Inversion: + + - `DIFFERENCE` + + - `EXCLUSION` + + + Component: + + - `HUE` + + - `SATURATION` + + - `COLOR` + + - `LUMINOSITY`' + enum: + - PASS_THROUGH + - NORMAL + - DARKEN + - MULTIPLY + - LINEAR_BURN + - COLOR_BURN + - LIGHTEN + - SCREEN + - LINEAR_DODGE + - COLOR_DODGE + - OVERLAY + - SOFT_LIGHT + - HARD_LIGHT + - DIFFERENCE + - EXCLUSION + - HUE + - SATURATION + - COLOR + - LUMINOSITY + BlurEffect: + oneOf: + - $ref: '#/components/schemas/NormalBlurEffect' + - $ref: '#/components/schemas/ProgressiveBlurEffect' + BooleanOperationNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - BOOLEAN_OPERATION + description: The type of this node, represented by the string literal "BOOLEAN_OPERATION" + booleanOperation: + type: string + description: A string enum indicating the type of boolean operation applied. + enum: + - UNION + - INTERSECT + - SUBTRACT + - EXCLUDE + required: + - type + - booleanOperation + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/HasChildrenTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasGeometryTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasMaskTrait' + - $ref: '#/components/schemas/TransitionSourceTrait' + CanvasNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - CANVAS + children: + type: array + items: + $ref: '#/components/schemas/SubcanvasNode' + backgroundColor: + $ref: '#/components/schemas/RGBA' + description: Background color of the canvas. + prototypeStartNodeID: + type: + - string + - 'null' + description: Node ID that corresponds to the start frame for prototypes. This is deprecated + with the introduction of multiple flows. Please use the `flowStartingPoints` field. + deprecated: true + flowStartingPoints: + type: array + items: + $ref: '#/components/schemas/FlowStartingPoint' + description: An array of flow starting points sorted by its position in the prototype settings + panel. + prototypeDevice: + $ref: '#/components/schemas/PrototypeDevice' + description: The device used to view a prototype. + prototypeBackgrounds: + type: array + description: The background color of the prototype (currently only supports a single solid + color paint). + items: + $ref: '#/components/schemas/RGBA' + measurements: + type: array + items: + $ref: '#/components/schemas/Measurement' + required: + - type + - children + - backgroundColor + - flowStartingPoints + - prototypeDevice + - prototypeStartNodeID + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + ColorStop: + type: object + description: A single color stop with its position along the gradient axis, color, and bound variables + if any + properties: + position: + type: number + description: Value between 0 and 1 representing position along gradient axis. + color: + $ref: '#/components/schemas/RGBA' + description: Color attached to corresponding position. + boundVariables: + type: object + description: The variables bound to a particular gradient stop + properties: + color: + $ref: '#/components/schemas/VariableAlias' + required: + - position + - color + Comment: + type: object + description: A comment or reply left by a user. + properties: + id: + type: string + description: Unique identifier for comment. + client_meta: + description: Positioning information of the comment. Includes information on the location of + the comment pin, which is either the absolute coordinates on the canvas or a relative offset + within a frame. If the comment is a region, it will also contain the region height, width, + and position of the anchor in regards to the region. + oneOf: + - $ref: '#/components/schemas/Vector' + - $ref: '#/components/schemas/FrameOffset' + - $ref: '#/components/schemas/Region' + - $ref: '#/components/schemas/FrameOffsetRegion' + file_key: + type: string + description: The file in which the comment lives + parent_id: + type: string + description: If present, the id of the comment to which this is the reply + user: + $ref: '#/components/schemas/User' + description: The user who left the comment + created_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the comment was left + resolved_at: + type: + - string + - 'null' + format: date-time + description: If set, the UTC ISO 8601 time the comment was resolved + message: + type: string + description: The content of the comment + order_id: + type: + - string + - 'null' + description: Only set for top level comments. The number displayed with the comment in the UI + reactions: + type: array + description: An array of reactions to the comment + items: + $ref: '#/components/schemas/Reaction' + required: + - id + - client_meta + - file_key + - user + - created_at + - message + - reactions + - order_id + ComplexStrokesTrait: + type: object + properties: {} + Component: + type: object + description: A description of a main component. Helps you identify which component instances are + attached to. + properties: + key: + type: string + description: The key of the component + name: + type: string + description: Name of the component + description: + type: string + description: The description of the component as entered in the editor + componentSetId: + type: string + description: The ID of the component set if the component belongs to one + documentationLinks: + type: array + items: + $ref: '#/components/schemas/DocumentationLink' + description: An array of documentation links attached to this component + remote: + type: boolean + description: Whether this component is a remote component that doesn't live in this file + required: + - key + - name + - description + - documentationLinks + - remote + ComponentNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - COMPONENT + description: The type of this node, represented by the string literal "COMPONENT" + required: + - type + - $ref: '#/components/schemas/FrameTraits' + - $ref: '#/components/schemas/ComponentPropertiesTrait' + ComponentPropertiesTrait: + type: object + properties: + componentPropertyDefinitions: + type: object + additionalProperties: + $ref: '#/components/schemas/ComponentPropertyDefinition' + description: A mapping of name to `ComponentPropertyDefinition` for every component property + on this component. Each property has a type, defaultValue, and other optional values. + ComponentProperty: + type: object + description: A property of a component. + properties: + type: + $ref: '#/components/schemas/ComponentPropertyType' + description: Type of this component property. + value: + oneOf: + - type: boolean + - type: string + - $ref: '#/components/schemas/SlotContentId' + description: Value of the property for this component instance. + preferredValues: + type: array + items: + $ref: '#/components/schemas/InstanceSwapPreferredValue' + description: Preferred values for this property. Only applicable if type is `INSTANCE_SWAP` + or `SLOT`. + boundVariables: + type: object + description: The variables bound to a particular field on this component property + properties: + value: + $ref: '#/components/schemas/VariableAlias' + required: + - type + - value + ComponentPropertyDefinition: + type: object + description: A property of a component. + properties: + type: + $ref: '#/components/schemas/ComponentPropertyType' + description: Type of this component property. + defaultValue: + oneOf: + - type: boolean + - type: string + - $ref: '#/components/schemas/SlotContentId' + description: Initial value of this property for instances. + variantOptions: + type: array + items: + type: string + description: All possible values for this property. Only exists on VARIANT properties. + preferredValues: + type: array + items: + $ref: '#/components/schemas/InstanceSwapPreferredValue' + description: Preferred values for this property. Only applicable if type is `INSTANCE_SWAP` + or `SLOT`. + slotSettings: + $ref: '#/components/schemas/SlotSettings' + description: Settings for a SLOT property. + required: + - type + - defaultValue + ComponentPropertyType: + type: string + description: Component property type. + enum: + - BOOLEAN + - INSTANCE_SWAP + - TEXT + - VARIANT + - SLOT + ComponentSet: + type: object + description: A description of a component set, which is a node containing a set of variants of a + component. + properties: + key: + type: string + description: The key of the component set + name: + type: string + description: Name of the component set + description: + type: string + description: The description of the component set as entered in the editor + documentationLinks: + type: array + items: + $ref: '#/components/schemas/DocumentationLink' + description: An array of documentation links attached to this component set + remote: + type: boolean + description: Whether this component set is a remote component set that doesn't live in this + file + required: + - key + - name + - description + ComponentSetNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - COMPONENT_SET + description: The type of this node, represented by the string literal "COMPONENT_SET" + required: + - type + - $ref: '#/components/schemas/FrameTraits' + - $ref: '#/components/schemas/ComponentPropertiesTrait' + ConditionalAction: + type: object + description: Checks if a condition is met before performing certain actions by using an if/else + conditional statement. + properties: + type: + type: string + enum: + - CONDITIONAL + conditionalBlocks: + type: array + items: + $ref: '#/components/schemas/ConditionalBlock' + required: + - type + - conditionalBlocks + ConditionalBlock: + type: object + description: Either the if or else conditional blocks. The if block contains a condition to check. + If that condition is met then it will run those list of actions, else it will run the actions + in the else block. + properties: + condition: + $ref: '#/components/schemas/VariableData' + actions: + type: array + items: + $ref: '#/components/schemas/Action' + required: + - actions + ConnectorEndpoint: + description: Stores canvas location for a connector start/end point. + oneOf: + - type: object + properties: + endpointNodeId: + type: string + description: Node ID that this endpoint attaches to. + position: + $ref: '#/components/schemas/Vector' + description: The position of the endpoint relative to the node. + - type: object + properties: + endpointNodeId: + type: string + description: Node ID that this endpoint attaches to. + magnet: + type: string + description: The magnet type is a string enum. + enum: + - AUTO + - TOP + - BOTTOM + - LEFT + - RIGHT + - CENTER + ConnectorLineType: + type: string + description: Connector line type. + enum: + - STRAIGHT + - ELBOWED + - CURVED + ConnectorNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - CONNECTOR + description: The type of this node, represented by the string literal "CONNECTOR" + connectorStart: + $ref: '#/components/schemas/ConnectorEndpoint' + description: The starting point of the connector. + connectorEnd: + $ref: '#/components/schemas/ConnectorEndpoint' + description: The ending point of the connector. + connectorStartStrokeCap: + type: string + description: A string enum describing the end cap of the start of the connector. + enum: + - NONE + - LINE_ARROW + - TRIANGLE_ARROW + - DIAMOND_FILLED + - CIRCLE_FILLED + - TRIANGLE_FILLED + default: NONE + connectorEndStrokeCap: + type: string + description: A string enum describing the end cap of the end of the connector. + enum: + - NONE + - LINE_ARROW + - TRIANGLE_ARROW + - DIAMOND_FILLED + - CIRCLE_FILLED + - TRIANGLE_FILLED + default: NONE + connectorLineType: + $ref: '#/components/schemas/ConnectorLineType' + description: Connector line type. + textBackground: + $ref: '#/components/schemas/ConnectorTextBackground' + description: Connector text background. + required: + - type + - connectorStart + - connectorEnd + - connectorStartStrokeCap + - connectorEndStrokeCap + - connectorLineType + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasTextSublayerTrait' + - $ref: '#/components/schemas/MinimalStrokesTrait' + ConnectorTextBackground: + allOf: + - $ref: '#/components/schemas/CornerTrait' + - $ref: '#/components/schemas/MinimalFillsTrait' + Constraint: + type: object + description: Sizing constraint for exports. + properties: + type: + type: string + description: 'Type of constraint to apply: + + + - `SCALE`: Scale by `value`. + + - `WIDTH`: Scale proportionally and set width to `value`. + + - `HEIGHT`: Scale proportionally and set height to `value`.' + enum: + - SCALE + - WIDTH + - HEIGHT + value: + type: number + description: See type property for effect of this field. + required: + - type + - value + CornerRadiusShapeTraits: + allOf: + - $ref: '#/components/schemas/DefaultShapeTraits' + - $ref: '#/components/schemas/CornerTrait' + CornerTrait: + type: object + properties: + cornerRadius: + type: number + description: Radius of each corner if a single radius is set for all corners + default: 0 + cornerSmoothing: + type: number + description: A value that lets you control how "smooth" the corners are. Ranges from 0 to 1. + 0 is the default and means that the corner is perfectly circular. A value of 0.6 means the + corner matches the iOS 7 "squircle" icon shape. Other values produce various other curves. + rectangleCornerRadii: + type: array + items: + type: number + minItems: 4 + maxItems: 4 + description: 'Array of length 4 of the radius of each corner of the frame, starting in the top + left and proceeding clockwise. + + + Values are given in the order top-left, top-right, bottom-right, bottom-left.' + DefaultShapeTraits: + allOf: + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasGeometryTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasMaskTrait' + - $ref: '#/components/schemas/TransitionSourceTrait' + - $ref: '#/components/schemas/VariableWidthStrokesTrait' + - $ref: '#/components/schemas/ComplexStrokesTrait' + DevStatusTrait: + type: object + properties: + devStatus: + type: object + description: Represents whether or not a node has a particular handoff (or dev) status applied + to it. + properties: + type: + type: string + enum: + - NONE + - READY_FOR_DEV + - COMPLETED + description: + type: string + description: An optional field where the designer can add more information about the design + and what has changed. + required: + - type + DirectionalTransition: + type: object + description: Describes an animation used when navigating in a prototype. + properties: + type: + type: string + enum: + - MOVE_IN + - MOVE_OUT + - PUSH + - SLIDE_IN + - SLIDE_OUT + direction: + type: string + enum: + - LEFT + - RIGHT + - TOP + - BOTTOM + duration: + type: number + description: The duration of the transition in milliseconds. + easing: + $ref: '#/components/schemas/Easing' + description: The easing curve of the transition. + matchLayers: + type: boolean + description: When the transition `type` is `"SMART_ANIMATE"` or when `matchLayers` is `true`, + then the transition will be performed using smart animate, which attempts to match corresponding + layers an interpolate other properties during the animation. + required: + - type + - direction + - duration + - easing + DocumentNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - DOCUMENT + children: + type: array + items: + $ref: '#/components/schemas/CanvasNode' + required: + - type + - children + - $ref: '#/components/schemas/IsLayerTrait' + DocumentationLink: + type: object + description: Represents a link to documentation for a component or component set. + properties: + uri: + type: string + description: Should be a valid URI (e.g. https://www.figma.com). + required: + - uri + DropShadowEffect: + allOf: + - type: object + description: A drop shadow effect + properties: + type: + type: string + description: A string literal representing the effect's type. Always check the type before + reading other properties. + enum: + - DROP_SHADOW + showShadowBehindNode: + type: boolean + description: Whether to show the shadow behind translucent or transparent pixels + default: false + required: + - type + - showShadowBehindNode + - $ref: '#/components/schemas/BaseShadowEffect' + DuotoneNoiseEffect: + allOf: + - type: object + description: A duotone noise effect + properties: + noiseType: + type: string + description: The string literal 'DUOTONE' representing the noise type. + enum: + - DUOTONE + secondaryColor: + $ref: '#/components/schemas/RGBA' + description: The secondary color of the noise effect + required: + - noiseType + - secondaryColor + - $ref: '#/components/schemas/BaseNoiseEffect' + Easing: + type: object + description: Describes an easing curve. + properties: + type: + $ref: '#/components/schemas/EasingType' + description: The type of easing curve. + easingFunctionCubicBezier: + type: object + description: A cubic bezier curve that defines the easing. + properties: + x1: + type: number + description: The x component of the first control point. + y1: + type: number + description: The y component of the first control point. + x2: + type: number + description: The x component of the second control point. + y2: + type: number + description: The y component of the second control point. + required: + - x1 + - y1 + - x2 + - y2 + easingFunctionSpring: + type: object + description: A spring function that defines the easing. + properties: + mass: + type: number + stiffness: + type: number + damping: + type: number + required: + - mass + - stiffness + - damping + required: + - type + EasingType: + type: string + description: 'This type is a string enum with the following possible values: + + + - `EASE_IN`: Ease in with an animation curve similar to CSS ease-in. + + - `EASE_OUT`: Ease out with an animation curve similar to CSS ease-out. + + - `EASE_IN_AND_OUT`: Ease in and then out with an animation curve similar to CSS ease-in-out. + + - `LINEAR`: No easing, similar to CSS linear. + + - `EASE_IN_BACK`: Ease in with an animation curve that moves past the initial keyframe''s value + and then accelerates as it reaches the end. + + - `EASE_OUT_BACK`: Ease out with an animation curve that starts fast, then slows and goes past + the ending keyframe''s value. + + - `EASE_IN_AND_OUT_BACK`: Ease in and then out with an animation curve that overshoots the initial + keyframe''s value, then accelerates quickly before it slows and overshoots the ending keyframes + value. + + - `CUSTOM_CUBIC_BEZIER`: User-defined cubic bezier curve. + + - `GENTLE`: Gentle animation similar to react-spring. + + - `QUICK`: Quick spring animation, great for toasts and notifications. + + - `BOUNCY`: Bouncy spring, for delightful animations like a heart bounce. + + - `SLOW`: Slow spring, useful as a steady, natural way to scale up fullscreen content. + + - `CUSTOM_SPRING`: User-defined spring animation.' + enum: + - EASE_IN + - EASE_OUT + - EASE_IN_AND_OUT + - LINEAR + - EASE_IN_BACK + - EASE_OUT_BACK + - EASE_IN_AND_OUT_BACK + - CUSTOM_CUBIC_BEZIER + - GENTLE + - QUICK + - BOUNCY + - SLOW + - CUSTOM_SPRING + Effect: + oneOf: + - $ref: '#/components/schemas/DropShadowEffect' + - $ref: '#/components/schemas/InnerShadowEffect' + - $ref: '#/components/schemas/BlurEffect' + - $ref: '#/components/schemas/TextureEffect' + - $ref: '#/components/schemas/NoiseEffect' + discriminator: + propertyName: type + mapping: + DROP_SHADOW: '#/components/schemas/DropShadowEffect' + INNER_SHADOW: '#/components/schemas/InnerShadowEffect' + LAYER_BLUR: '#/components/schemas/BlurEffect' + BACKGROUND_BLUR: '#/components/schemas/BlurEffect' + TEXTURE: '#/components/schemas/TextureEffect' + NOISE: '#/components/schemas/NoiseEffect' + EllipseNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - ELLIPSE + description: The type of this node, represented by the string literal "ELLIPSE" + arcData: + $ref: '#/components/schemas/ArcData' + required: + - type + - arcData + - $ref: '#/components/schemas/DefaultShapeTraits' + - $ref: '#/components/schemas/AnnotationsTrait' + EmbedNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - EMBED + description: The type of this node, represented by the string literal "EMBED" + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + Emoji: + type: string + description: The emoji type of reaction as shortcode (e.g. `:heart:`, `:+1::skin-tone-2:`). The + list of accepted emoji shortcodes can be found in [this file](https://raw.githubusercontent.com/missive/emoji-mart/main/packages/emoji-mart-data/sets/14/native.json) + under the top-level emojis and aliases fields, with optional skin tone modifiers when applicable. + ErrorResponsePayloadWithErrMessage: + type: object + description: A response indicating an error occurred. + properties: + status: + type: number + description: Status code + err: + type: string + description: A string describing the error + required: + - status + - err + ErrorResponsePayloadWithErrorBoolean: + type: object + description: A response indicating an error occurred. + properties: + error: + type: boolean + description: For erroneous requests, this value is always `true`. + enum: + - true + status: + type: number + description: Status code + message: + type: string + description: A string describing the error + required: + - error + - status + - message + ExportSetting: + type: object + description: An export setting. + properties: + suffix: + type: string + format: + type: string + enum: + - JPG + - PNG + - SVG + - PDF + constraint: + $ref: '#/components/schemas/Constraint' + required: + - suffix + - format + - constraint + Expression: + type: object + description: Defines the [Expression](https://help.figma.com/hc/en-us/articles/15253194385943) object, + which contains a list of `VariableData` objects strung together by operators (`ExpressionFunction`). + properties: + expressionFunction: + $ref: '#/components/schemas/ExpressionFunction' + expressionArguments: + type: array + items: + $ref: '#/components/schemas/VariableData' + required: + - expressionFunction + - expressionArguments + ExpressionFunction: + type: string + description: Defines the list of operators available to use in an Expression. + enum: + - ADDITION + - SUBTRACTION + - MULTIPLICATION + - DIVISION + - EQUALS + - NOT_EQUAL + - LESS_THAN + - LESS_THAN_OR_EQUAL + - GREATER_THAN + - GREATER_THAN_OR_EQUAL + - AND + - OR + - VAR_MODE_LOOKUP + - NEGATE + - NOT + FlowStartingPoint: + type: object + description: A flow starting point used when launching a prototype to enter Presentation view. + properties: + nodeId: + type: string + description: Unique identifier specifying the frame. + name: + type: string + description: Name of flow. + required: + - nodeId + - name + Folder: + type: object + description: A Folder (previously called a "Project") is a collection of files and subfolders. Folders + can be nested; a folder's parent is identified by `parent_folder_id`. + properties: + id: + type: string + description: The ID of the folder. + name: + type: string + description: The name of the folder. + parent_folder_id: + type: + - string + - 'null' + description: The ID of the folder's parent folder, or null if the folder is a top-level folder + in the team. + sharing_audience_control: + type: string + description: The audience that can access the folder. + enum: + - plan_view + - plan_edit + - invite_only + - workspace_view + - workspace_edit + created_at: + type: string + format: date-time + description: The time at which the folder was created. + required: + - id + - name + - parent_folder_id + FrameNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - FRAME + description: The type of this node, represented by the string literal "FRAME" + required: + - type + - $ref: '#/components/schemas/FrameTraits' + FrameOffset: + type: object + description: Position of a comment relative to the frame to which it is attached. + properties: + node_id: + type: string + description: Unique id specifying the frame. + node_offset: + $ref: '#/components/schemas/Vector' + description: 2D vector offset within the frame from the top-left corner. + required: + - node_id + - node_offset + FrameOffsetRegion: + type: object + description: Position of a region comment relative to the frame to which it is attached. + properties: + node_id: + type: string + description: Unique id specifying the frame. + node_offset: + $ref: '#/components/schemas/Vector' + description: 2D vector offset within the frame from the top-left corner. + region_height: + type: number + description: The height of the comment region. Must be greater than 0. + region_width: + type: number + description: The width of the comment region. Must be greater than 0. + comment_pin_corner: + type: string + description: The corner of the comment region to pin to the node's corner as a string enum. + enum: + - top-left + - top-right + - bottom-left + - bottom-right + default: bottom-right + required: + - node_id + - node_offset + - region_height + - region_width + FrameTraits: + allOf: + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/HasChildrenTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasFramePropertiesTrait' + - $ref: '#/components/schemas/CornerTrait' + - $ref: '#/components/schemas/HasGeometryTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasMaskTrait' + - $ref: '#/components/schemas/TransitionSourceTrait' + - $ref: '#/components/schemas/IndividualStrokesTrait' + - $ref: '#/components/schemas/VariableWidthStrokesTrait' + - $ref: '#/components/schemas/ComplexStrokesTrait' + - $ref: '#/components/schemas/DevStatusTrait' + - $ref: '#/components/schemas/AnnotationsTrait' + GradientPaint: + allOf: + - type: object + description: A gradient + properties: + type: + type: string + description: The string literal representing the paint's type. Always check the `type` before + reading other properties. + enum: + - GRADIENT_LINEAR + - GRADIENT_RADIAL + - GRADIENT_ANGULAR + - GRADIENT_DIAMOND + gradientHandlePositions: + type: array + items: + $ref: '#/components/schemas/Vector' + description: This field contains three vectors, each of which are a position in normalized + object space (normalized object space is if the top left corner of the bounding box of the + object is (0, 0) and the bottom right is (1,1)). The first position corresponds to the start + of the gradient (value 0 for the purposes of calculating gradient stops), the second position + is the end of the gradient (value 1), and the third handle position determines the width + of the gradient. + gradientStops: + type: array + items: + $ref: '#/components/schemas/ColorStop' + description: Positions of key points along the gradient axis with the colors anchored there. + Colors along the gradient are interpolated smoothly between neighboring gradient stops. + required: + - type + - gradientHandlePositions + - gradientStops + - $ref: '#/components/schemas/BasePaint' + GroupNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - GROUP + description: The type of this node, represented by the string literal "GROUP" + required: + - type + - $ref: '#/components/schemas/FrameTraits' + HasBlendModeAndOpacityTrait: + type: object + properties: + blendMode: + $ref: '#/components/schemas/BlendMode' + description: How this node blends with nodes behind it in the scene (see blend mode section + for more details) + opacity: + type: number + description: Opacity of the node + default: 1 + minimum: 0 + maximum: 1 + required: + - blendMode + HasChildrenTrait: + type: object + properties: + children: + type: array + description: An array of nodes that are direct children of this node + items: + $ref: '#/components/schemas/SubcanvasNode' + required: + - children + HasEffectsTrait: + type: object + properties: + effects: + type: array + items: + $ref: '#/components/schemas/Effect' + description: An array of effects attached to this node (see effects section for more details) + required: + - effects + HasExportSettingsTrait: + type: object + properties: + exportSettings: + type: array + items: + $ref: '#/components/schemas/ExportSetting' + description: An array of export settings representing images to export from the node. + HasFramePropertiesTrait: + type: object + properties: + clipsContent: + type: boolean + description: Whether or not this node clip content outside of its bounds + background: + type: array + items: + $ref: '#/components/schemas/Paint' + description: Background of the node. This is deprecated, as backgrounds for frames are now in + the `fills` field. + deprecated: true + backgroundColor: + $ref: '#/components/schemas/RGBA' + description: Background color of the node. This is deprecated, as frames now support more than + a solid color as a background. Please use the `fills` field instead. + deprecated: true + layoutGrids: + type: array + description: An array of layout grids attached to this node (see layout grids section for more + details). GROUP nodes do not have this attribute + items: + $ref: '#/components/schemas/LayoutGrid' + overflowDirection: + type: string + description: Whether a node has primary axis scrolling, horizontal or vertical. + enum: + - HORIZONTAL_SCROLLING + - VERTICAL_SCROLLING + - HORIZONTAL_AND_VERTICAL_SCROLLING + - NONE + default: NONE + layoutMode: + type: string + description: Whether this layer uses auto-layout to position its children. + enum: + - NONE + - HORIZONTAL + - VERTICAL + - GRID + default: NONE + primaryAxisSizingMode: + type: string + description: Whether the primary axis has a fixed length (determined by the user) or an automatic + length (determined by the layout engine). This property is only applicable for auto-layout + frames. + enum: + - FIXED + - AUTO + default: AUTO + counterAxisSizingMode: + type: string + description: Whether the counter axis has a fixed length (determined by the user) or an automatic + length (determined by the layout engine). This property is only applicable for auto-layout + frames. + enum: + - FIXED + - AUTO + default: AUTO + primaryAxisAlignItems: + type: string + description: Determines how the auto-layout frame's children should be aligned in the primary + axis direction. This property is only applicable for auto-layout frames. + enum: + - MIN + - CENTER + - MAX + - SPACE_BETWEEN + default: MIN + counterAxisAlignItems: + type: string + description: Determines how the auto-layout frame's children should be aligned in the counter + axis direction. This property is only applicable for auto-layout frames. + enum: + - MIN + - CENTER + - MAX + - BASELINE + default: MIN + paddingLeft: + type: number + description: The padding between the left border of the frame and its children. This property + is only applicable for auto-layout frames. + default: 0 + paddingRight: + type: number + description: The padding between the right border of the frame and its children. This property + is only applicable for auto-layout frames. + default: 0 + paddingTop: + type: number + description: The padding between the top border of the frame and its children. This property + is only applicable for auto-layout frames. + default: 0 + paddingBottom: + type: number + description: The padding between the bottom border of the frame and its children. This property + is only applicable for auto-layout frames. + default: 0 + itemSpacing: + type: number + description: The distance between children of the frame. Can be negative. This property is only + applicable for auto-layout frames. + default: 0 + itemReverseZIndex: + type: boolean + description: Determines the canvas stacking order of layers in this frame. When true, the first + layer will be draw on top. This property is only applicable for auto-layout frames. + default: false + strokesIncludedInLayout: + type: boolean + description: 'Determines whether strokes are included in layout calculations. When true, auto-layout + frames behave like css "box-sizing: border-box". This property is only applicable for auto-layout + frames.' + default: false + layoutWrap: + type: string + description: Whether this auto-layout frame has wrapping enabled. + enum: + - NO_WRAP + - WRAP + counterAxisSpacing: + type: number + description: 'The distance between wrapped tracks of an auto-layout frame. This property is + only applicable for auto-layout frames with `layoutWrap: "WRAP"`' + counterAxisAlignContent: + type: string + description: 'Determines how the auto-layout frame’s wrapped tracks should be aligned in the + counter axis direction. This property is only applicable for auto-layout frames with `layoutWrap: + "WRAP"`.' + enum: + - AUTO + - SPACE_BETWEEN + default: AUTO + required: + - clipsContent + HasGeometryTrait: + allOf: + - $ref: '#/components/schemas/MinimalFillsTrait' + - $ref: '#/components/schemas/MinimalStrokesTrait' + - type: object + properties: + fillOverrideTable: + type: object + additionalProperties: + oneOf: + - $ref: '#/components/schemas/PaintOverride' + - type: 'null' + description: Map from ID to PaintOverride for looking up fill overrides. To see which regions + are overridden, you must use the `geometry=paths` option. Each path returned may have an + `overrideID` which maps to this table. + fillGeometry: + type: array + items: + $ref: '#/components/schemas/Path' + description: Only specified if parameter `geometry=paths` is used. An array of paths representing + the object fill. + strokeGeometry: + type: array + items: + $ref: '#/components/schemas/Path' + description: Only specified if parameter `geometry=paths` is used. An array of paths representing + the object stroke. + strokeCap: + type: string + description: A string enum describing the end caps of vector paths. + enum: + - NONE + - ROUND + - SQUARE + - LINE_ARROW + - TRIANGLE_ARROW + - DIAMOND_FILLED + - CIRCLE_FILLED + - TRIANGLE_FILLED + - WASHI_TAPE_1 + - WASHI_TAPE_2 + - WASHI_TAPE_3 + - WASHI_TAPE_4 + - WASHI_TAPE_5 + - WASHI_TAPE_6 + default: NONE + strokeMiterAngle: + type: number + description: Only valid if `strokeJoin` is "MITER". The corner angle, in degrees, below which + `strokeJoin` will be set to "BEVEL" to avoid super sharp corners. By default this is 28.96 + degrees. + default: 28.96 + HasLayoutTrait: + type: object + properties: + absoluteBoundingBox: + description: Bounding box of the node in absolute space coordinates. + oneOf: + - $ref: '#/components/schemas/Rectangle' + - type: 'null' + absoluteRenderBounds: + description: The actual bounds of a node accounting for drop shadows, thick strokes, and anything + else that may fall outside the node's regular bounding box defined in `x`, `y`, `width`, and + `height`. The `x` and `y` inside this property represent the absolute position of the node + on the page. This value will be `null` if the node is invisible. + oneOf: + - $ref: '#/components/schemas/Rectangle' + - type: 'null' + preserveRatio: + type: boolean + description: Keep height and width constrained to same ratio. + default: false + constraints: + $ref: '#/components/schemas/LayoutConstraint' + description: Horizontal and vertical layout constraints for node. + relativeTransform: + $ref: '#/components/schemas/Transform' + description: The top two rows of a matrix that represents the 2D transform of this node relative + to its parent. The bottom row of the matrix is implicitly always (0, 0, 1). Use to transform + coordinates in geometry. Only present if `geometry=paths` is passed. + size: + $ref: '#/components/schemas/Vector' + description: Width and height of element. This is different from the width and height of the + bounding box in that the absolute bounding box represents the element after scaling and rotation. + Only present if `geometry=paths` is passed. + layoutAlign: + type: string + description: ' + + Determines if the layer should stretch along the parent''s counter axis. This property is + only provided for direct children of auto-layout frames. + + + - `INHERIT` + + - `STRETCH` + + + In previous versions of auto layout, determined how the layer is aligned inside an auto-layout + frame. This property is only provided for direct children of auto-layout frames. + + + - `MIN` + + - `CENTER` + + - `MAX` + + - `STRETCH` + + + In horizontal auto-layout frames, "MIN" and "MAX" correspond to "TOP" and "BOTTOM". In vertical + auto-layout frames, "MIN" and "MAX" correspond to "LEFT" and "RIGHT".' + enum: + - INHERIT + - STRETCH + - MIN + - CENTER + - MAX + layoutGrow: + type: number + description: This property is applicable only for direct children of auto-layout frames, ignored + otherwise. Determines whether a layer should stretch along the parent's primary axis. A `0` + corresponds to a fixed size and `1` corresponds to stretch. + enum: + - 0 + - 1 + default: 0 + layoutPositioning: + type: string + description: Determines whether a layer's size and position should be determined by auto-layout + settings or manually adjustable. + enum: + - AUTO + - ABSOLUTE + default: AUTO + minWidth: + type: number + description: The minimum width of the frame. This property is only applicable for auto-layout + frames or direct children of auto-layout frames. + default: 0 + maxWidth: + type: number + description: The maximum width of the frame. This property is only applicable for auto-layout + frames or direct children of auto-layout frames. + default: 0 + minHeight: + type: number + description: The minimum height of the frame. This property is only applicable for auto-layout + frames or direct children of auto-layout frames. + default: 0 + maxHeight: + type: number + description: The maximum height of the frame. This property is only applicable for auto-layout + frames or direct children of auto-layout frames. + default: 0 + layoutSizingHorizontal: + type: string + description: 'The horizontal sizing setting on this auto-layout frame or frame child. + + - `FIXED` + + - `HUG`: only valid on auto-layout frames and text nodes + + - `FILL`: only valid on auto-layout frame children' + enum: + - FIXED + - HUG + - FILL + layoutSizingVertical: + type: string + description: 'The vertical sizing setting on this auto-layout frame or frame child. + + - `FIXED` + + - `HUG`: only valid on auto-layout frames and text nodes + + - `FILL`: only valid on auto-layout frame children' + enum: + - FIXED + - HUG + - FILL + gridRowCount: + type: number + description: 'The number of rows in the grid layout. This property is only applicable for auto-layout + frames with `layoutMode: "GRID"`.' + gridColumnCount: + type: number + description: 'The number of columns in the grid layout. This property is only applicable for + auto-layout frames with `layoutMode: "GRID"`.' + gridRowGap: + type: number + description: 'The distance between rows in the grid layout. This property is only applicable + for auto-layout frames with `layoutMode: "GRID"`.' + default: 0 + gridColumnGap: + type: number + description: 'The distance between columns in the grid layout. This property is only applicable + for auto-layout frames with `layoutMode: "GRID"`.' + default: 0 + gridColumnsSizing: + type: string + description: 'The string for the CSS grid-template-columns property. This property is only applicable + for auto-layout frames with `layoutMode: "GRID"`.' + gridRowsSizing: + type: string + description: 'The string for the CSS grid-template-rows property. This property is only applicable + for auto-layout frames with `layoutMode: "GRID"`.' + gridChildHorizontalAlign: + type: string + description: 'Determines how a GRID frame''s child should be aligned in the horizontal direction + within its grid area. This property is only applicable for direct children of frames with + `layoutMode: "GRID"`.' + enum: + - AUTO + - MIN + - CENTER + - MAX + gridChildVerticalAlign: + type: string + description: 'Determines how a GRID frame''s child should be aligned in the vertical direction + within its grid area. This property is only applicable for direct children of frames with + `layoutMode: "GRID"`.' + enum: + - AUTO + - MIN + - CENTER + - MAX + gridRowSpan: + type: number + description: 'The number of rows that a GRID frame''s child should span. This property is only + applicable for direct children of frames with `layoutMode: "GRID"`.' + default: 1 + gridColumnSpan: + type: number + description: 'The number of columns that a GRID frame''s child should span. This property is + only applicable for direct children of frames with `layoutMode: "GRID"`.' + default: 1 + gridRowAnchorIndex: + type: number + description: 'The index of the row that a GRID frame''s child should be anchored to. This property + is only applicable for direct children of frames with `layoutMode: "GRID"`.' + default: 0 + gridColumnAnchorIndex: + type: number + description: 'The index of the column that a GRID frame''s child should be anchored to. This + property is only applicable for direct children of frames with `layoutMode: "GRID"`.' + default: 0 + required: + - absoluteBoundingBox + - absoluteRenderBounds + HasMaskTrait: + type: object + properties: + isMask: + type: boolean + description: Does this node mask sibling nodes in front of it? + default: false + maskType: + type: string + description: 'If this layer is a mask, this property describes the operation used to mask the + layer''s siblings. The value may be one of the following: + + + - ALPHA: the mask node''s alpha channel will be used to determine the opacity of each pixel + in the masked result. + + - VECTOR: if the mask node has visible fill paints, every pixel inside the node''s fill regions + will be fully visible in the masked result. If the mask has visible stroke paints, every pixel + inside the node''s stroke regions will be fully visible in the masked result. + + - LUMINANCE: the luminance value of each pixel of the mask node will be used to determine + the opacity of that pixel in the masked result.' + enum: + - ALPHA + - VECTOR + - LUMINANCE + isMaskOutline: + type: boolean + description: True if maskType is VECTOR. This field is deprecated; use maskType instead. + default: false + deprecated: true + HasTextSublayerTrait: + type: object + properties: + characters: + type: string + description: Text contained within a text box. + required: + - characters + Hyperlink: + type: object + description: A link to either a URL or another frame (node) in the document. + properties: + type: + type: string + description: The type of hyperlink. Can be either `URL` or `NODE`. + enum: + - URL + - NODE + url: + type: string + description: The URL that the hyperlink points to, if `type` is `URL`. + nodeID: + type: string + description: The ID of the node that the hyperlink points to, if `type` is `NODE`. + required: + - type + ImageFilters: + type: object + description: Image filters to apply to the node. + properties: + exposure: + type: number + default: 0 + contrast: + type: number + default: 0 + saturation: + type: number + default: 0 + temperature: + type: number + default: 0 + tint: + type: number + default: 0 + highlights: + type: number + default: 0 + shadows: + type: number + default: 0 + ImagePaint: + allOf: + - type: object + description: An image + properties: + type: + type: string + description: The string literal "IMAGE" representing the paint's type. Always check the `type` + before reading other properties. + enum: + - IMAGE + scaleMode: + type: string + description: Image scaling mode. + enum: + - FILL + - FIT + - TILE + - STRETCH + imageRef: + type: string + description: A reference to an image embedded in this node. To download the image using this + reference, use the `GET file images` endpoint to retrieve the mapping from image references + to image URLs. + imageTransform: + $ref: '#/components/schemas/Transform' + description: Affine transform applied to the image, only present if `scaleMode` is `STRETCH` + scalingFactor: + type: number + description: Amount image is scaled by in tiling, only present if scaleMode is `TILE`. + filters: + $ref: '#/components/schemas/ImageFilters' + description: Defines what image filters have been applied to this paint, if any. If this property + is not defined, no filters have been applied. + rotation: + type: number + description: Image rotation, in degrees. + default: 0 + gifRef: + type: string + description: A reference to an animated GIF embedded in this node. To download the image using + this reference, use the `GET file images` endpoint to retrieve the mapping from image references + to image URLs. + required: + - type + - scaleMode + - imageRef + - $ref: '#/components/schemas/BasePaint' + IndividualStrokesTrait: + type: object + properties: + individualStrokeWeights: + $ref: '#/components/schemas/StrokeWeights' + description: An object including the top, bottom, left, and right stroke weights. Only returned + if individual stroke weights are used. + InnerShadowEffect: + allOf: + - type: object + description: An inner shadow effect + properties: + type: + type: string + description: A string literal representing the effect's type. Always check the type before + reading other properties. + enum: + - INNER_SHADOW + - $ref: '#/components/schemas/BaseShadowEffect' + InstanceNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - INSTANCE + description: The type of this node, represented by the string literal "INSTANCE" + componentId: + type: string + description: ID of component that this instance came from. + isExposedInstance: + type: boolean + description: If true, this node has been marked as exposed to its containing component or + component set. + default: false + exposedInstances: + type: array + items: + type: string + description: IDs of instances that have been exposed to this node's level. + componentProperties: + type: object + additionalProperties: + $ref: '#/components/schemas/ComponentProperty' + description: A mapping of name to `ComponentProperty` for all component properties on this + instance. Each property has a type, value, and other optional values. + overrides: + type: array + items: + $ref: '#/components/schemas/Overrides' + description: An array of all of the fields directly overridden on this instance. Inherited + overrides are not included. + required: + - type + - componentId + - overrides + - $ref: '#/components/schemas/FrameTraits' + InstanceSwapPreferredValue: + type: object + description: Instance swap preferred value. + properties: + type: + type: string + description: Type of node for this preferred value. + enum: + - COMPONENT + - COMPONENT_SET + key: + type: string + description: Key of this component or component set. + required: + - type + - key + Interaction: + type: object + description: An interaction in the Figma viewer, containing a trigger and one or more actions. + properties: + trigger: + oneOf: + - $ref: '#/components/schemas/Trigger' + - type: 'null' + description: The user event that initiates the interaction. + actions: + type: array + items: + $ref: '#/components/schemas/Action' + description: The actions that are performed when the trigger is activated. + required: + - trigger + IsLayerTrait: + type: object + properties: + id: + type: string + description: A string uniquely identifying this node within the document. + name: + type: string + description: The name given to the node by the user in the tool. + type: + type: string + description: The type of the node + visible: + type: boolean + description: Whether or not the node is visible on the canvas. + default: true + locked: + type: boolean + description: If true, layer is locked and cannot be edited + default: false + isFixed: + type: boolean + description: Whether the layer is fixed while the parent is scrolling + deprecated: true + default: false + scrollBehavior: + type: string + description: How layer should be treated when the frame is resized + enum: + - SCROLLS + - FIXED + - STICKY_SCROLLS + default: SCROLLS + rotation: + type: number + description: The rotation of the node, if not 0. + default: 0 + componentPropertyReferences: + type: object + additionalProperties: + type: string + description: A mapping of a layer's property to component property name of component properties + attached to this node. The component property name can be used to look up more information + on the corresponding component's or component set's componentPropertyDefinitions. + pluginData: + description: Data written by plugins that is visible only to the plugin that wrote it. Requires + the `pluginData` to include the ID of the plugin. + sharedPluginData: + description: Data written by plugins that is visible to all plugins. Requires the `pluginData` + parameter to include the string "shared". + boundVariables: + type: object + description: A mapping of field to the variables applied to this field. Most fields will only + map to a single `VariableAlias`. However, for properties like `fills`, `strokes`, `size`, + `componentProperties`, and `textRangeFills`, it is possible to have multiple variables bound + to the field. + properties: + size: + type: object + properties: + x: + $ref: '#/components/schemas/VariableAlias' + y: + $ref: '#/components/schemas/VariableAlias' + individualStrokeWeights: + type: object + properties: + top: + $ref: '#/components/schemas/VariableAlias' + bottom: + $ref: '#/components/schemas/VariableAlias' + left: + $ref: '#/components/schemas/VariableAlias' + right: + $ref: '#/components/schemas/VariableAlias' + characters: + $ref: '#/components/schemas/VariableAlias' + itemSpacing: + $ref: '#/components/schemas/VariableAlias' + paddingLeft: + $ref: '#/components/schemas/VariableAlias' + paddingRight: + $ref: '#/components/schemas/VariableAlias' + paddingTop: + $ref: '#/components/schemas/VariableAlias' + paddingBottom: + $ref: '#/components/schemas/VariableAlias' + visible: + $ref: '#/components/schemas/VariableAlias' + topLeftRadius: + $ref: '#/components/schemas/VariableAlias' + topRightRadius: + $ref: '#/components/schemas/VariableAlias' + bottomLeftRadius: + $ref: '#/components/schemas/VariableAlias' + bottomRightRadius: + $ref: '#/components/schemas/VariableAlias' + minWidth: + $ref: '#/components/schemas/VariableAlias' + maxWidth: + $ref: '#/components/schemas/VariableAlias' + minHeight: + $ref: '#/components/schemas/VariableAlias' + maxHeight: + $ref: '#/components/schemas/VariableAlias' + counterAxisSpacing: + $ref: '#/components/schemas/VariableAlias' + opacity: + $ref: '#/components/schemas/VariableAlias' + fontFamily: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + fontSize: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + fontStyle: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + fontWeight: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + letterSpacing: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + lineHeight: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + paragraphSpacing: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + paragraphIndent: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + fills: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + strokes: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + componentProperties: + type: object + additionalProperties: + $ref: '#/components/schemas/VariableAlias' + textRangeFills: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + effects: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + layoutGrids: + type: array + items: + $ref: '#/components/schemas/VariableAlias' + rectangleCornerRadii: + type: object + properties: + RECTANGLE_TOP_LEFT_CORNER_RADIUS: + $ref: '#/components/schemas/VariableAlias' + RECTANGLE_TOP_RIGHT_CORNER_RADIUS: + $ref: '#/components/schemas/VariableAlias' + RECTANGLE_BOTTOM_LEFT_CORNER_RADIUS: + $ref: '#/components/schemas/VariableAlias' + RECTANGLE_BOTTOM_RIGHT_CORNER_RADIUS: + $ref: '#/components/schemas/VariableAlias' + explicitVariableModes: + type: object + description: A mapping of variable collection ID to mode ID representing the explicitly set + modes for this node. + additionalProperties: + type: string + required: + - id + - name + - type + - scrollBehavior + LayoutConstraint: + type: object + description: Layout constraint relative to containing Frame + properties: + vertical: + type: string + description: 'Vertical constraint (relative to containing frame) as an enum: + + + - `TOP`: Node is laid out relative to top of the containing frame + + - `BOTTOM`: Node is laid out relative to bottom of the containing frame + + - `CENTER`: Node is vertically centered relative to containing frame + + - `TOP_BOTTOM`: Both top and bottom of node are constrained relative to containing frame (node + stretches with frame) + + - `SCALE`: Node scales vertically with containing frame' + enum: + - TOP + - BOTTOM + - CENTER + - TOP_BOTTOM + - SCALE + horizontal: + type: string + description: 'Horizontal constraint (relative to containing frame) as an enum: + + + - `LEFT`: Node is laid out relative to left of the containing frame + + - `RIGHT`: Node is laid out relative to right of the containing frame + + - `CENTER`: Node is horizontally centered relative to containing frame + + - `LEFT_RIGHT`: Both left and right of node are constrained relative to containing frame (node + stretches with frame) + + - `SCALE`: Node scales horizontally with containing frame' + enum: + - LEFT + - RIGHT + - CENTER + - LEFT_RIGHT + - SCALE + required: + - vertical + - horizontal + LayoutGrid: + type: object + description: Guides to align and place objects within a frames. + properties: + pattern: + type: string + description: 'Orientation of the grid as a string enum + + + - `COLUMNS`: Vertical grid + + - `ROWS`: Horizontal grid + + - `GRID`: Square grid' + enum: + - COLUMNS + - ROWS + - GRID + sectionSize: + type: number + description: Width of column grid or height of row grid or square grid spacing. + visible: + type: boolean + description: Is the grid currently visible? + color: + $ref: '#/components/schemas/RGBA' + description: Color of the grid + alignment: + type: string + description: 'Positioning of grid as a string enum + + + - `MIN`: Grid starts at the left or top of the frame + + - `MAX`: Grid starts at the right or bottom of the frame + + - `STRETCH`: Grid is stretched to fit the frame + + - `CENTER`: Grid is center aligned' + enum: + - MIN + - MAX + - STRETCH + - CENTER + gutterSize: + type: number + description: Spacing in between columns and rows + offset: + type: number + description: Spacing before the first column or row + count: + type: number + description: Number of columns or rows + boundVariables: + type: object + description: The variables bound to a particular field on this layout grid + properties: + gutterSize: + $ref: '#/components/schemas/VariableAlias' + numSections: + $ref: '#/components/schemas/VariableAlias' + sectionSize: + $ref: '#/components/schemas/VariableAlias' + offset: + $ref: '#/components/schemas/VariableAlias' + required: + - pattern + - sectionSize + - visible + - color + - alignment + - gutterSize + - offset + - count + LineNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - LINE + description: The type of this node, represented by the string literal "LINE" + required: + - type + - $ref: '#/components/schemas/DefaultShapeTraits' + - $ref: '#/components/schemas/AnnotationsTrait' + LinkAccess: + type: string + description: Access policy for users who have the link to the resource. + enum: + - view + - edit + - org_view + - org_edit + - inherit + LinkUnfurlNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - LINK_UNFURL + description: The type of this node, represented by the string literal "LINK_UNFURL" + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + Measurement: + type: object + description: A pinned distance between two nodes in Dev Mode + properties: + id: + type: string + start: + $ref: '#/components/schemas/MeasurementStartEnd' + end: + $ref: '#/components/schemas/MeasurementStartEnd' + offset: + oneOf: + - $ref: '#/components/schemas/MeasurementOffsetInner' + - $ref: '#/components/schemas/MeasurementOffsetOuter' + freeText: + type: string + description: When manually overridden, the displayed value of the measurement + required: + - id + - start + - end + - offset + MeasurementOffsetInner: + type: object + description: Measurement offset relative to the inside of the start node + properties: + type: + type: string + enum: + - INNER + relative: + type: number + required: + - type + - relative + MeasurementOffsetOuter: + type: object + description: Measurement offset relative to the outside of the start node + properties: + type: + type: string + enum: + - OUTER + fixed: + type: number + required: + - type + - fixed + MeasurementStartEnd: + type: object + description: The node and side a measurement is pinned to + properties: + nodeId: + type: string + side: + type: string + enum: + - TOP + - RIGHT + - BOTTOM + - LEFT + required: + - nodeId + - side + MinimalFillsTrait: + type: object + properties: + fills: + type: array + items: + $ref: '#/components/schemas/Paint' + description: An array of fill paints applied to the node. + styles: + type: object + additionalProperties: + type: string + description: A mapping of a StyleType to style ID (see Style) of styles present on this node. + The style ID can be used to look up more information about the style in the top-level styles + field. + required: + - fills + MinimalStrokesTrait: + type: object + properties: + strokes: + type: array + items: + $ref: '#/components/schemas/Paint' + description: An array of stroke paints applied to the node. + strokeWeight: + type: number + description: The weight of strokes on the node. + default: 1 + strokeAlign: + type: string + description: 'Position of stroke relative to vector outline, as a string enum + + + - `INSIDE`: stroke drawn inside the shape boundary + + - `OUTSIDE`: stroke drawn outside the shape boundary + + - `CENTER`: stroke drawn centered along the shape boundary' + enum: + - INSIDE + - OUTSIDE + - CENTER + strokeJoin: + type: string + description: A string enum with value of "MITER", "BEVEL", or "ROUND", describing how corners + in vector paths are rendered. + enum: + - MITER + - BEVEL + - ROUND + default: MITER + strokeDashes: + type: array + items: + type: number + description: 'An array of floating point numbers describing the pattern of dash length and gap + lengths that the vector stroke will use when drawn. + + + For example a value of [1, 2] indicates that the stroke will be drawn with a dash of length + 1 followed by a gap of length 2, repeated.' + MonotoneNoiseEffect: + allOf: + - type: object + description: A monotone noise effect + properties: + noiseType: + type: string + description: The string literal 'MONOTONE' representing the noise type. + enum: + - MONOTONE + required: + - noiseType + - $ref: '#/components/schemas/BaseNoiseEffect' + MultitoneNoiseEffect: + allOf: + - type: object + description: A multitone noise effect + properties: + noiseType: + type: string + description: The string literal 'MULTITONE' representing the noise type. + enum: + - MULTITONE + opacity: + type: number + description: The opacity of the noise effect + required: + - noiseType + - opacity + - $ref: '#/components/schemas/BaseNoiseEffect' + Navigation: + type: string + description: "The method of navigation. The possible values are:\n- `\"NAVIGATE\"`: Replaces the\ + \ current screen with the destination, also closing all overlays.\n- `\"OVERLAY\"`: Opens the\ + \ destination as an overlay on the current screen.\n- `\"SWAP\"`: On an overlay, replaces the\ + \ current (topmost) overlay with the destination. On a top-level frame, \n behaves the same as\ + \ `\"NAVIGATE\"` except that no entry is added to the navigation history.\n- `\"SCROLL_TO\"`:\ + \ Scrolls to the destination on the current screen.\n- `\"CHANGE_TO\"`: Changes the closest ancestor\ + \ instance of source node to the specified variant." + enum: + - NAVIGATE + - SWAP + - OVERLAY + - SCROLL_TO + - CHANGE_TO + NodeAction: + type: object + description: An action that navigates to a specific node in the Figma viewer. + properties: + type: + type: string + enum: + - NODE + destinationId: + type: + - string + - 'null' + navigation: + $ref: '#/components/schemas/Navigation' + transition: + oneOf: + - $ref: '#/components/schemas/Transition' + - type: 'null' + preserveScrollPosition: + type: boolean + description: Whether the scroll offsets of any scrollable elements in the current screen or + overlay are preserved when navigating to the destination. This is applicable only if the layout + of both the current frame and its destination are the same. + overlayRelativePosition: + $ref: '#/components/schemas/Vector' + description: Applicable only when `navigation` is `"OVERLAY"` and the destination is a frame + with `overlayPosition` equal to `"MANUAL"`. This value represents the offset by which the + overlay is opened relative to this node. + resetVideoPosition: + type: boolean + description: When true, all videos within the destination frame will reset their memorized playback + position to 00:00 before starting to play. + resetScrollPosition: + type: boolean + description: Whether the scroll offsets of any scrollable elements in the current screen or + overlay reset when navigating to the destination. This is applicable only if the layout of + both the current frame and its destination are the same. + resetInteractiveComponents: + type: boolean + description: Whether the state of any interactive components in the current screen or overlay + reset when navigating to the destination. This is applicable if there are interactive components + in the destination frame. + required: + - type + - destinationId + - navigation + - transition + NoiseEffect: + oneOf: + - $ref: '#/components/schemas/MonotoneNoiseEffect' + - $ref: '#/components/schemas/MultitoneNoiseEffect' + - $ref: '#/components/schemas/DuotoneNoiseEffect' + NormalBlurEffect: + allOf: + - type: object + description: A normal blur effect + properties: + blurType: + type: string + description: The string literal 'NORMAL' representing the blur type. Always check the blurType + before reading other properties. + enum: + - NORMAL + - $ref: '#/components/schemas/BaseBlurEffect' + OnKeyDownTrigger: + type: object + properties: + type: + type: string + enum: + - ON_KEY_DOWN + device: + type: string + enum: + - KEYBOARD + - XBOX_ONE + - PS4 + - SWITCH_PRO + - UNKNOWN_CONTROLLER + keyCodes: + type: array + items: + type: number + required: + - type + - device + - keyCodes + OnMediaHitTrigger: + type: object + properties: + type: + type: string + enum: + - ON_MEDIA_HIT + mediaHitTime: + type: number + required: + - type + - mediaHitTime + OpenURLAction: + type: object + description: An action that opens a URL. + properties: + type: + type: string + enum: + - URL + url: + type: string + required: + - type + - url + Overrides: + type: object + description: Fields directly overridden on an instance. Inherited overrides are not included. + properties: + id: + type: string + description: A unique ID for a node. + overriddenFields: + type: array + items: + type: string + description: An array of properties. + required: + - id + - overriddenFields + Paint: + oneOf: + - $ref: '#/components/schemas/SolidPaint' + - $ref: '#/components/schemas/GradientPaint' + - $ref: '#/components/schemas/ImagePaint' + - $ref: '#/components/schemas/PatternPaint' + PaintOverride: + type: object + description: Paint metadata to override default paints. + properties: + fills: + type: array + items: + $ref: '#/components/schemas/Paint' + description: Paints applied to characters. + inheritFillStyleId: + type: string + description: ID of style node, if any, that this inherits fill data from. + Path: + type: object + description: Defines a single path + properties: + path: + type: string + description: A series of path commands that encodes how to draw the path. + windingRule: + type: string + description: The winding rule for the path (same as in SVGs). This determines whether a given + point in space is inside or outside the path. + enum: + - NONZERO + - EVENODD + overrideID: + type: number + description: If there is a per-region fill, this refers to an ID in the `fillOverrideTable`. + required: + - path + - windingRule + PatternPaint: + allOf: + - type: object + description: A pattern + properties: + type: + type: string + description: The string literal "PATTERN" representing the paint's type. Always check the + `type` before reading other properties. + enum: + - PATTERN + sourceNodeId: + type: string + description: The node id of the source node for the pattern + tileType: + type: string + description: The tile type for the pattern + enum: + - RECTANGULAR + - HORIZONTAL_HEXAGONAL + - VERTICAL_HEXAGONAL + scalingFactor: + type: number + description: The scaling factor for the pattern + spacing: + $ref: '#/components/schemas/Vector' + description: The spacing for the pattern + horizontalAlignment: + type: string + description: The horizontal alignment for the pattern + enum: + - START + - CENTER + - END + verticalAlignment: + type: string + description: The vertical alignment for the pattern + enum: + - START + - CENTER + - END + required: + - type + - sourceNodeId + - tileType + - scalingFactor + - spacing + - horizontalAlignment + - verticalAlignment + - $ref: '#/components/schemas/BasePaint' + ProgressiveBlurEffect: + allOf: + - type: object + description: A progressive blur effect + properties: + blurType: + type: string + description: The string literal 'PROGRESSIVE' representing the blur type. Always check the + blurType before reading other properties. + enum: + - PROGRESSIVE + startRadius: + type: number + description: The starting radius of the progressive blur + startOffset: + $ref: '#/components/schemas/Vector' + description: The starting offset of the progressive blur + endOffset: + $ref: '#/components/schemas/Vector' + description: The ending offset of the progressive blur + required: + - blurType + - startRadius + - startOffset + - endOffset + - $ref: '#/components/schemas/BaseBlurEffect' + Project: + type: object + description: A Project can be identified by both the Project name, and the Project ID. + properties: + id: + type: string + description: The ID of the project. + name: + type: string + description: The name of the project. + required: + - id + - name + PrototypeDevice: + type: object + description: The device used to view a prototype. + properties: + type: + type: string + enum: + - NONE + - PRESET + - CUSTOM + - PRESENTATION + size: + $ref: '#/components/schemas/Size' + presetIdentifier: + type: string + rotation: + type: string + enum: + - NONE + - CCW_90 + required: + - type + - rotation + RGB: + type: object + description: An RGB color + properties: + r: + type: number + description: Red channel value, between 0 and 1. + minimum: 0 + maximum: 1 + g: + type: number + description: Green channel value, between 0 and 1. + minimum: 0 + maximum: 1 + b: + type: number + description: Blue channel value, between 0 and 1. + minimum: 0 + maximum: 1 + required: + - r + - g + - b + RGBA: + type: object + description: An RGBA color + properties: + r: + type: number + description: Red channel value, between 0 and 1. + minimum: 0 + maximum: 1 + g: + type: number + description: Green channel value, between 0 and 1. + minimum: 0 + maximum: 1 + b: + type: number + description: Blue channel value, between 0 and 1. + minimum: 0 + maximum: 1 + a: + type: number + description: Alpha channel value, between 0 and 1. + minimum: 0 + maximum: 1 + required: + - r + - g + - b + - a + Reaction: + type: object + description: A reaction left by a user. + properties: + user: + $ref: '#/components/schemas/User' + description: The user who left the reaction. + emoji: + $ref: '#/components/schemas/Emoji' + created_at: + type: string + format: date-time + description: The UTC ISO 8601 time at which the reaction was left. + required: + - user + - emoji + - created_at + Rectangle: + type: object + description: A rectangle that expresses a bounding box in absolute coordinates. + properties: + x: + type: number + description: X coordinate of top left corner of the rectangle. + y: + type: number + description: Y coordinate of top left corner of the rectangle. + width: + type: number + description: Width of the rectangle. + height: + type: number + description: Height of the rectangle. + required: + - x + - y + - width + - height + RectangleNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - RECTANGLE + description: The type of this node, represented by the string literal "RECTANGLE" + required: + - type + - $ref: '#/components/schemas/RectangularShapeTraits' + RectangularShapeTraits: + allOf: + - $ref: '#/components/schemas/DefaultShapeTraits' + - $ref: '#/components/schemas/CornerTrait' + - $ref: '#/components/schemas/IndividualStrokesTrait' + - $ref: '#/components/schemas/AnnotationsTrait' + Region: + type: object + description: Position of a region comment on the canvas. + properties: + x: + type: number + description: X coordinate of the position. + y: + type: number + description: Y coordinate of the position. + region_height: + type: number + description: The height of the comment region. Must be greater than 0. + region_width: + type: number + description: The width of the comment region. Must be greater than 0. + comment_pin_corner: + type: string + description: The corner of the comment region to pin to the node's corner as a string enum. + enum: + - top-left + - top-right + - bottom-left + - bottom-right + default: bottom-right + required: + - x + - y + - region_height + - region_width + RegularPolygonNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - REGULAR_POLYGON + description: The type of this node, represented by the string literal "REGULAR_POLYGON" + required: + - type + - $ref: '#/components/schemas/CornerRadiusShapeTraits' + - $ref: '#/components/schemas/AnnotationsTrait' + ResponsePagination: + type: object + description: If pagination is needed due to the length of the response, identifies the next and + previous pages. + properties: + prev_page: + type: string + description: A URL that calls the previous page of the response. + next_page: + type: string + description: A URL that calls the next page of the response. + Role: + type: string + description: The role of the user making the API request in relation to the resource. + enum: + - owner + - editor + - viewer + SectionNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - SECTION + description: The type of this node, represented by the string literal "SECTION" + sectionContentsHidden: + type: boolean + description: Whether the contents of the section are visible + default: false + required: + - type + - sectionContentsHidden + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasGeometryTrait' + - $ref: '#/components/schemas/HasChildrenTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/DevStatusTrait' + SetVariableAction: + type: object + description: Sets a variable to a specific value. + properties: + type: + type: string + enum: + - SET_VARIABLE + variableId: + type: + - string + - 'null' + variableValue: + $ref: '#/components/schemas/VariableData' + required: + - type + - variableId + SetVariableModeAction: + type: object + description: Sets a variable to a specific mode. + properties: + type: + type: string + enum: + - SET_VARIABLE_MODE + variableCollectionId: + type: + - string + - 'null' + variableModeId: + type: + - string + - 'null' + required: + - type + - variableId + - variableMode + ShapeType: + type: string + description: Geometric shape type. + enum: + - SQUARE + - ELLIPSE + - ROUNDED_RECTANGLE + - DIAMOND + - TRIANGLE_UP + - TRIANGLE_DOWN + - PARALLELOGRAM_RIGHT + - PARALLELOGRAM_LEFT + - ENG_DATABASE + - ENG_QUEUE + - ENG_FILE + - ENG_FOLDER + - TRAPEZOID + - PREDEFINED_PROCESS + - SHIELD + - DOCUMENT_SINGLE + - DOCUMENT_MULTIPLE + - MANUAL_INPUT + - HEXAGON + - CHEVRON + - PENTAGON + - OCTAGON + - STAR + - PLUS + - ARROW_LEFT + - ARROW_RIGHT + - SUMMING_JUNCTION + - OR + - SPEECH_BUBBLE + - INTERNAL_STORAGE + ShapeWithTextNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - SHAPE_WITH_TEXT + description: The type of this node, represented by the string literal "SHAPE_WITH_TEXT" + shapeType: + $ref: '#/components/schemas/ShapeType' + description: 'Geometric shape type. Most shape types have the same name as their tooltip but + there are a few exceptions. ENG_DATABASE: Cylinder, ENG_QUEUE: Horizontal cylinder, ENG_FILE: + File, ENG_FOLDER: Folder.' + required: + - type + - shapeType + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/MinimalFillsTrait' + - $ref: '#/components/schemas/HasMaskTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasTextSublayerTrait' + - $ref: '#/components/schemas/CornerTrait' + - $ref: '#/components/schemas/MinimalStrokesTrait' + SimpleTransition: + type: object + description: Describes an animation used when navigating in a prototype. + properties: + type: + type: string + enum: + - DISSOLVE + - SMART_ANIMATE + - SCROLL_ANIMATE + duration: + type: number + description: The duration of the transition in milliseconds. + easing: + $ref: '#/components/schemas/Easing' + description: The easing curve of the transition. + required: + - type + - duration + - easing + Size: + type: object + description: A width and a height. + properties: + width: + type: number + description: The width of a size. + height: + type: number + description: the height of a size. + required: + - width + - height + SliceNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - SLICE + description: The type of this node, represented by the string literal "SLICE" + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + SlotContentId: + type: object + description: Identifier for the content of a slot component property. + properties: + guid: + type: object + description: Identifier for the slot content. + properties: + sessionID: + type: integer + localID: + type: integer + required: + - sessionID + - localID + required: + - guid + SlotSettings: + type: object + description: Slot-specific configuration for a SLOT component property. + properties: + stretchChildOnInsert: + type: boolean + description: Whether layers inserted into the slot fill its counter axis. + displayEmptyByDefault: + type: boolean + description: Whether an empty slot is shown by default. + minChildren: + type: + - integer + - 'null' + minimum: 0 + description: Minimum number of children allowed in the slot. null means no minimum. + maxChildren: + type: + - integer + - 'null' + minimum: 0 + description: Maximum number of children allowed in the slot. null means no maximum. + allowPreferredValuesOnly: + type: boolean + description: Whether slot contents are limited to preferred values. + SolidPaint: + allOf: + - type: object + description: A solid color + properties: + type: + type: string + description: The string literal "SOLID" representing the paint's type. Always check the `type` + before reading other properties. + enum: + - SOLID + color: + $ref: '#/components/schemas/RGBA' + description: Solid color of the paint + boundVariables: + type: object + description: The variables bound to a particular field on this paint + properties: + color: + $ref: '#/components/schemas/VariableAlias' + required: + - type + - color + - $ref: '#/components/schemas/BasePaint' + StarNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - STAR + description: The type of this node, represented by the string literal "STAR" + required: + - type + - $ref: '#/components/schemas/CornerRadiusShapeTraits' + - $ref: '#/components/schemas/AnnotationsTrait' + StickyNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - STICKY + description: The type of this node, represented by the string literal "STICKY" + authorVisible: + type: boolean + description: If true, author name is visible. + default: false + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/MinimalFillsTrait' + - $ref: '#/components/schemas/HasMaskTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasTextSublayerTrait' + StrokeWeights: + type: object + description: Individual stroke weights + properties: + top: + type: number + description: The top stroke weight. + right: + type: number + description: The right stroke weight. + bottom: + type: number + description: The bottom stroke weight. + left: + type: number + description: The left stroke weight. + required: + - top + - right + - bottom + - left + Style: + type: object + description: A set of properties that can be applied to nodes and published. Styles for a property + can be created in the corresponding property's panel while editing a file. + properties: + key: + type: string + description: The key of the style + name: + type: string + description: Name of the style + description: + type: string + description: Description of the style + remote: + type: boolean + description: Whether this style is a remote style that doesn't live in this file + styleType: + $ref: '#/components/schemas/StyleType' + required: + - key + - name + - description + - remote + - styleType + StyleType: + type: string + description: The type of style + enum: + - FILL + - TEXT + - EFFECT + - GRID + SubcanvasNode: + oneOf: + - $ref: '#/components/schemas/BooleanOperationNode' + - $ref: '#/components/schemas/ComponentNode' + - $ref: '#/components/schemas/ComponentSetNode' + - $ref: '#/components/schemas/ConnectorNode' + - $ref: '#/components/schemas/EllipseNode' + - $ref: '#/components/schemas/EmbedNode' + - $ref: '#/components/schemas/FrameNode' + - $ref: '#/components/schemas/GroupNode' + - $ref: '#/components/schemas/InstanceNode' + - $ref: '#/components/schemas/LineNode' + - $ref: '#/components/schemas/LinkUnfurlNode' + - $ref: '#/components/schemas/RectangleNode' + - $ref: '#/components/schemas/RegularPolygonNode' + - $ref: '#/components/schemas/SectionNode' + - $ref: '#/components/schemas/ShapeWithTextNode' + - $ref: '#/components/schemas/SliceNode' + - $ref: '#/components/schemas/StarNode' + - $ref: '#/components/schemas/StickyNode' + - $ref: '#/components/schemas/TableNode' + - $ref: '#/components/schemas/TableCellNode' + - $ref: '#/components/schemas/TextNode' + - $ref: '#/components/schemas/TextPathNode' + - $ref: '#/components/schemas/TransformGroupNode' + - $ref: '#/components/schemas/VectorNode' + - $ref: '#/components/schemas/WashiTapeNode' + - $ref: '#/components/schemas/WidgetNode' + discriminator: + propertyName: type + mapping: + BOOLEAN_OPERATION: '#/components/schemas/BooleanOperationNode' + COMPONENT: '#/components/schemas/ComponentNode' + COMPONENT_SET: '#/components/schemas/ComponentSetNode' + CONNECTOR: '#/components/schemas/ConnectorNode' + ELLIPSE: '#/components/schemas/EllipseNode' + EMBED: '#/components/schemas/EmbedNode' + FRAME: '#/components/schemas/FrameNode' + GROUP: '#/components/schemas/GroupNode' + INSTANCE: '#/components/schemas/InstanceNode' + LINE: '#/components/schemas/LineNode' + LINK_UNFURL: '#/components/schemas/LinkUnfurlNode' + RECTANGLE: '#/components/schemas/RectangleNode' + REGULAR_POLYGON: '#/components/schemas/RegularPolygonNode' + SECTION: '#/components/schemas/SectionNode' + SHAPE_WITH_TEXT: '#/components/schemas/ShapeWithTextNode' + SLICE: '#/components/schemas/SliceNode' + STAR: '#/components/schemas/StarNode' + STICKY: '#/components/schemas/StickyNode' + TABLE: '#/components/schemas/TableNode' + TABLE_CELL: '#/components/schemas/TableCellNode' + TEXT: '#/components/schemas/TextNode' + TEXT_PATH: '#/components/schemas/TextPathNode' + TRANSFORM_GROUP: '#/components/schemas/TransformGroupNode' + VECTOR: '#/components/schemas/VectorNode' + WASHI_TAPE: '#/components/schemas/WashiTapeNode' + WIDGET: '#/components/schemas/WidgetNode' + TableCellNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - TABLE_CELL + description: The type of this node, represented by the string literal "TABLE_CELL" + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/MinimalFillsTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/HasTextSublayerTrait' + TableNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - TABLE + description: The type of this node, represented by the string literal "TABLE" + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasChildrenTrait' + - $ref: '#/components/schemas/HasLayoutTrait' + - $ref: '#/components/schemas/MinimalStrokesTrait' + - $ref: '#/components/schemas/HasEffectsTrait' + - $ref: '#/components/schemas/HasBlendModeAndOpacityTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + TextNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - TEXT + description: The type of this node, represented by the string literal "TEXT" + required: + - type + - $ref: '#/components/schemas/DefaultShapeTraits' + - $ref: '#/components/schemas/TypePropertiesTrait' + - $ref: '#/components/schemas/AnnotationsTrait' + TextPathNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - TEXT_PATH + description: The type of this node, represented by the string literal "TEXT_PATH" + required: + - type + - $ref: '#/components/schemas/DefaultShapeTraits' + - $ref: '#/components/schemas/TextPathPropertiesTrait' + TextPathPropertiesTrait: + type: object + properties: + characters: + type: string + description: The raw characters in the text path node. + style: + $ref: '#/components/schemas/TextPathTypeStyle' + description: Style of text including font family and weight. + characterStyleOverrides: + type: array + items: + type: number + description: The array corresponds to characters in the text box, where each element references + the 'styleOverrideTable' to apply specific styles to each character. The array's length can + be less than or equal to the number of characters due to the removal of trailing zeros. Elements + with a value of 0 indicate characters that use the default type style. If the array is shorter + than the total number of characters, the characters beyond the array's length also use the + default style. + layoutVersion: + type: number + description: Internal property, preserved for backward compatibility. Avoid using this value. + styleOverrideTable: + type: object + additionalProperties: + $ref: '#/components/schemas/TextPathTypeStyle' + description: Map from ID to TextPathTypeStyle for looking up style overrides. + required: + - characters + - style + - characterStyleOverrides + - styleOverrideTable + - textPathStartData + TextPathTypeStyle: + allOf: + - type: object + description: Metadata for character formatting for styles used in text paths. + properties: + isOverrideOverTextStyle: + type: boolean + description: Whether or not this style has overrides over a text style. The possible fields + to override are semanticWeight, semanticItalic, and hyperlink. If this is true, then those + fields are overrides if present. + boundVariables: + type: object + description: The variables bound to a particular field on this style + properties: + fontFamily: + $ref: '#/components/schemas/VariableAlias' + fontSize: + $ref: '#/components/schemas/VariableAlias' + fontStyle: + $ref: '#/components/schemas/VariableAlias' + fontWeight: + $ref: '#/components/schemas/VariableAlias' + letterSpacing: + $ref: '#/components/schemas/VariableAlias' + - $ref: '#/components/schemas/BaseTypeStyle' + TextureEffect: + type: object + description: A texture effect + properties: + type: + type: string + description: The string literal 'TEXTURE' representing the effect's type. Always check the type + before reading other properties. + enum: + - TEXTURE + visible: + type: boolean + description: Whether the texture effect is visible. + noiseSize: + type: number + description: The size of the texture effect + radius: + type: number + description: The radius of the texture effect + clipToShape: + type: boolean + description: Whether the texture is clipped to the shape + required: + - type + - visible + - noiseSize + - radius + - clipToShape + Transform: + type: array + items: + type: array + items: + type: number + maxItems: 3 + minItems: 3 + maxItems: 2 + minItems: 2 + description: "A transformation matrix is standard way in computer graphics to represent translation\ + \ and rotation. These are the top two rows of a 3x3 matrix. The bottom row of the matrix is assumed\ + \ to be [0, 0, 1]. This is known as an affine transform and is enough to represent translation,\ + \ rotation, and skew.\n\nThe identity transform is [[1, 0, 0], [0, 1, 0]].\n\nA translation matrix\ + \ will typically look like:\n\n```\n[[1, 0, tx],\n [0, 1, ty]]\n```\n\nand a rotation matrix\ + \ will typically look like:\n\n```\n[[cos(angle), sin(angle), 0],\n [-sin(angle), cos(angle),\ + \ 0]]\n```\n\nAnother way to think about this transform is as three vectors:\n\n- The x axis (t[0][0],\ + \ t[1][0])\n- The y axis (t[0][1], t[1][1])\n- The translation offset (t[0][2], t[1][2])\n\nThe\ + \ most common usage of the Transform matrix is the `relativeTransform property`. This particular\ + \ usage of the matrix has a few additional restrictions. The translation offset can take on any\ + \ value but we do enforce that the axis vectors are unit vectors (i.e. have length 1). The axes\ + \ are not required to be at 90° angles to each other." + TransformGroupNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - TRANSFORM_GROUP + description: The type of this node, represented by the string literal "TRANSFORM_GROUP" + required: + - type + - $ref: '#/components/schemas/FrameTraits' + - $ref: '#/components/schemas/TransformModifiersTrait' + TransformModifiersTrait: + type: object + properties: {} + Transition: + oneOf: + - $ref: '#/components/schemas/SimpleTransition' + - $ref: '#/components/schemas/DirectionalTransition' + TransitionSourceTrait: + type: object + properties: + transitionNodeID: + type: string + description: Node ID of node to transition to in prototyping + transitionDuration: + type: number + description: The duration of the prototyping transition on this node (in milliseconds). This + will override the default transition duration on the prototype, for this node. + transitionEasing: + $ref: '#/components/schemas/EasingType' + description: The easing curve used in the prototyping transition on this node. + interactions: + type: array + items: + $ref: '#/components/schemas/Interaction' + description: An array of the interactions on this node, each containing a trigger and one + or more actions. + Trigger: + type: object + description: "The `\"ON_HOVER\"` and `\"ON_PRESS\"` trigger types revert the navigation when the\ + \ trigger is finished (the result is temporary). \n`\"MOUSE_ENTER\"`, `\"MOUSE_LEAVE\"`, `\"MOUSE_UP\"\ + ` and `\"MOUSE_DOWN\"` are permanent, one-way navigation.\nThe `delay` parameter requires the\ + \ trigger to be held for a certain duration of time before the action occurs.\nBoth `timeout`\ + \ and `delay` values are in milliseconds.\nThe `\"ON_MEDIA_HIT\"` and `\"ON_MEDIA_END\"` trigger\ + \ types can only trigger from a video. \nThey fire when a video reaches a certain time or ends.\ + \ The `timestamp` value is in seconds." + oneOf: + - type: object + properties: + type: + type: string + enum: + - ON_CLICK + - ON_HOVER + - ON_PRESS + - ON_DRAG + required: + - type + - $ref: '#/components/schemas/AfterTimeoutTrigger' + - type: object + properties: + type: + type: string + enum: + - MOUSE_ENTER + - MOUSE_LEAVE + - MOUSE_UP + - MOUSE_DOWN + delay: + type: number + deprecatedVersion: + description: 'Whether this is a [deprecated version](https://help.figma.com/hc/en-us/articles/360040035834-Prototype-triggers#h_01HHN04REHJNP168R26P1CMP0A) + of the trigger that was left unchanged for backwards compatibility. + + If not present, the trigger is the latest version.' + type: boolean + required: + - type + - delay + - $ref: '#/components/schemas/OnKeyDownTrigger' + - $ref: '#/components/schemas/OnMediaHitTrigger' + - type: object + properties: + type: + type: string + enum: + - ON_MEDIA_END + required: + - type + TypePropertiesTrait: + type: object + properties: + characters: + type: string + description: The raw characters in the text node. + style: + $ref: '#/components/schemas/TypeStyle' + description: Style of text including font family and weight. + characterStyleOverrides: + type: array + items: + type: number + description: The array corresponds to characters in the text box, where each element references + the 'styleOverrideTable' to apply specific styles to each character. The array's length can + be less than or equal to the number of characters due to the removal of trailing zeros. Elements + with a value of 0 indicate characters that use the default type style. If the array is shorter + than the total number of characters, the characters beyond the array's length also use the + default style. + layoutVersion: + type: number + description: Internal property, preserved for backward compatibility. Avoid using this value. + styleOverrideTable: + type: object + additionalProperties: + $ref: '#/components/schemas/TypeStyle' + description: Map from ID to TypeStyle for looking up style overrides. + lineTypes: + type: array + items: + type: string + enum: + - NONE + - ORDERED + - UNORDERED + description: 'An array with the same number of elements as lines in the text node, where lines + are delimited by newline or paragraph separator characters. Each element in the array corresponds + to the list type of a specific line. List types are represented as string enums with one of + these possible values: + + + - `NONE`: Not a list item. + + - `ORDERED`: Text is an ordered list (numbered). + + - `UNORDERED`: Text is an unordered list (bulleted).' + lineIndentations: + type: array + items: + type: number + description: An array with the same number of elements as lines in the text node, where lines + are delimited by newline or paragraph separator characters. Each element in the array corresponds + to the indentation level of a specific line. + required: + - characters + - style + - characterStyleOverrides + - styleOverrideTable + - lineTypes + - lineIndentations + TypeStyle: + allOf: + - type: object + description: Metadata for character formatting. + properties: + paragraphSpacing: + type: number + description: Space between paragraphs in px, 0 if not present. + default: 0 + paragraphIndent: + type: number + description: Paragraph indentation in px, 0 if not present. + default: 0 + listSpacing: + type: number + description: Space between list items in px, 0 if not present. + default: 0 + textDecoration: + type: string + description: Text decoration applied to the node, default is none. + enum: + - NONE + - STRIKETHROUGH + - UNDERLINE + default: NONE + textAutoResize: + type: string + description: Dimensions along which text will auto resize, default is that the text does not + auto-resize. TRUNCATE means that the text will be shortened and trailing text will be replaced + with "…" if the text contents is larger than the bounds. `TRUNCATE` as a return value is + deprecated and will be removed in a future version. Read from `textTruncation` instead. + enum: + - NONE + - WIDTH_AND_HEIGHT + - HEIGHT + - TRUNCATE + default: NONE + textTruncation: + type: string + description: Whether this text node will truncate with an ellipsis when the text contents + is larger than the text node. + enum: + - DISABLED + - ENDING + default: DISABLED + maxLines: + type: number + description: 'When `textTruncation: "ENDING"` is set, `maxLines` determines how many lines + a text node can grow to before it truncates.' + lineHeightPx: + type: number + description: Line height in px. + lineHeightPercent: + type: number + default: 100 + description: Line height as a percentage of normal line height. This is deprecated; in a future + version of the API only lineHeightPx and lineHeightPercentFontSize will be returned. + lineHeightPercentFontSize: + type: number + description: Line height as a percentage of the font size. Only returned when `lineHeightPercent` + (deprecated) is not 100. + lineHeightUnit: + type: string + description: The unit of the line height value specified by the user. + enum: + - PIXELS + - FONT_SIZE_% + - INTRINSIC_% + isOverrideOverTextStyle: + type: boolean + description: Whether or not this style has overrides over a text style. The possible fields + to override are semanticWeight, semanticItalic, hyperlink, and textDecoration. If this is + true, then those fields are overrides if present. + boundVariables: + type: object + description: The variables bound to a particular field on this style + properties: + fontFamily: + $ref: '#/components/schemas/VariableAlias' + fontSize: + $ref: '#/components/schemas/VariableAlias' + fontStyle: + $ref: '#/components/schemas/VariableAlias' + fontWeight: + $ref: '#/components/schemas/VariableAlias' + letterSpacing: + $ref: '#/components/schemas/VariableAlias' + lineHeight: + $ref: '#/components/schemas/VariableAlias' + paragraphSpacing: + $ref: '#/components/schemas/VariableAlias' + paragraphIndent: + $ref: '#/components/schemas/VariableAlias' + - $ref: '#/components/schemas/BaseTypeStyle' + UpdateMediaRuntimeAction: + type: object + description: An action that affects a video node in the Figma viewer. For example, to play, pause, + or skip. + oneOf: + - type: object + description: "An action that updates the runtime of a media node by playing, pausing, toggling\ + \ play/pause, \nmuting, unmuting, or toggling mute/unmute.\n\nThe `destinationId` is the node\ + \ ID of the media node to update. If `destinationId` is `null`, the action will \nupdate the\ + \ media node that contains the action.\n\nThe `mediaAction` is the action to perform on the\ + \ media node." + properties: + type: + type: string + enum: + - UPDATE_MEDIA_RUNTIME + destinationId: + type: + - string + - 'null' + mediaAction: + type: string + enum: + - PLAY + - PAUSE + - TOGGLE_PLAY_PAUSE + - MUTE + - UNMUTE + - TOGGLE_MUTE_UNMUTE + required: + - type + - destinationId + - mediaAction + - type: object + description: "An action that updates the runtime of a media node by skipping forward or backward.\n\ + \nThe `destinationId` is the node ID of the media node to update. If `destinationId` is `null`,\ + \ the action will \nupdate the media node that contains the action.\n\nThe `mediaAction` is\ + \ the action to perform on the media node.\n\nThe `amountToSkip` is the amount of time to skip\ + \ in seconds." + properties: + type: + type: string + enum: + - UPDATE_MEDIA_RUNTIME + destinationId: + type: + - string + - 'null' + mediaAction: + type: string + enum: + - SKIP_FORWARD + - SKIP_BACKWARD + amountToSkip: + type: number + required: + - type + - mediaAction + - amountToSkip + - type: object + description: "An action that updates the runtime of a media node by skipping to a specific time.\n\ + \nThe `destinationId` is the node ID of the media node to update. If `destinationId` is `null`,\ + \ the action will \nupdate the media node that contains the action.\n\nThe `mediaAction` is\ + \ the action to perform on the media node.\n\nThe `newTimestamp` is the new time to skip to\ + \ in seconds." + properties: + type: + type: string + enum: + - UPDATE_MEDIA_RUNTIME + destinationId: + type: + - string + - 'null' + mediaAction: + type: string + enum: + - SKIP_TO + newTimestamp: + type: number + required: + - type + - mediaAction + - newTimestamp + User: + type: object + description: A description of a user. + properties: + id: + type: string + description: Unique stable id of the user. + handle: + type: string + description: Name of the user. + img_url: + type: string + description: URL link to the user's profile image. + required: + - id + - handle + - img_url + VariableAlias: + type: object + description: Contains a variable alias + properties: + type: + type: string + enum: + - VARIABLE_ALIAS + id: + type: string + description: The id of the variable that the current variable is aliased to. This variable can + be a local or remote variable, and both can be retrieved via the GET /v1/files/:file_key/variables/local + endpoint. + required: + - type + - id + VariableData: + type: object + description: A value to set a variable to during prototyping. + properties: + type: + $ref: '#/components/schemas/VariableDataType' + resolvedType: + $ref: '#/components/schemas/VariableResolvedDataType' + value: + oneOf: + - type: boolean + - type: number + - type: string + - $ref: '#/components/schemas/RGB' + - $ref: '#/components/schemas/RGBA' + - $ref: '#/components/schemas/VariableAlias' + - $ref: '#/components/schemas/Expression' + VariableDataType: + type: string + description: Defines the types of data a VariableData object can hold + enum: + - BOOLEAN + - FLOAT + - STRING + - COLOR + - VARIABLE_ALIAS + - EXPRESSION + VariableResolvedDataType: + type: string + description: Defines the types of data a VariableData object can eventually equal + enum: + - BOOLEAN + - FLOAT + - STRING + - COLOR + VariableWidthStrokesTrait: + type: object + properties: {} + Vector: + type: object + description: A 2d vector. + properties: + x: + type: number + description: X coordinate of the vector. + y: + type: number + description: Y coordinate of the vector. + required: + - x + - y + VectorNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - VECTOR + description: The type of this node, represented by the string literal "VECTOR" + required: + - type + - $ref: '#/components/schemas/CornerRadiusShapeTraits' + - $ref: '#/components/schemas/AnnotationsTrait' + WashiTapeNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - WASHI_TAPE + description: The type of this node, represented by the string literal "WASHI_TAPE" + required: + - type + - $ref: '#/components/schemas/DefaultShapeTraits' + WebhookV2: + type: object + description: A description of an HTTP webhook (from Figma back to your application) + properties: + id: + type: string + description: The ID of the webhook + event_type: + $ref: '#/components/schemas/WebhookV2Event' + description: The event this webhook triggers on + team_id: + type: string + description: The team id you are subscribed to for updates. This is deprecated, use context + and context_id instead + deprecated: true + context: + type: string + description: The type of context this webhook is attached to. The value will be "PROJECT", "TEAM", + or "FILE" + context_id: + type: string + description: The ID of the context this webhook is attached to + plan_api_id: + type: string + description: The plan API ID of the team or organization where this webhook was created + status: + $ref: '#/components/schemas/WebhookV2Status' + description: The current status of the webhook + client_id: + type: + - string + - 'null' + description: The client ID of the OAuth application that registered this webhook, if any + passcode: + type: string + description: The passcode that will be passed back to the webhook endpoint. For security, when + using the GET endpoints, the value is an empty string + endpoint: + type: string + description: The endpoint that will be hit when the webhook is triggered + description: + type: + - string + - 'null' + description: Optional user-provided description or name for the webhook. This is provided to + help make maintaining a number of webhooks more convenient. Max length 140 characters. + required: + - id + - event_type + - team_id + - context + - context_id + - plan_api_id + - status + - client_id + - passcode + - endpoint + - description + WebhookV2Event: + type: string + description: An enum representing the possible events that a webhook can subscribe to + enum: + - PING + - FILE_UPDATE + - FILE_VERSION_UPDATE + - FILE_DELETE + - LIBRARY_PUBLISH + - FILE_COMMENT + - DEV_MODE_STATUS_UPDATE + WebhookV2Request: + type: object + description: Information regarding the most recent interactions sent to a webhook endpoint + properties: + webhook_id: + type: string + description: The ID of the webhook the requests were sent to + request_info: + $ref: '#/components/schemas/WebhookV2RequestInfo' + response_info: + $ref: '#/components/schemas/WebhookV2ResponseInfo' + error_msg: + type: + - string + - 'null' + description: Error message for this request. NULL if no error occurred + required: + - webhook_id + - request_info + - response_info + - error_msg + WebhookV2RequestInfo: + type: object + description: Information regarding the request sent to a webhook endpoint + properties: + id: + type: string + description: The ID of the webhook + endpoint: + type: string + description: The actual endpoint the request was sent to + payload: + type: object + description: The contents of the request that was sent to the endpoint + sent_at: + type: string + format: date-time + description: UTC ISO 8601 timestamp of when the request was sent + required: + - id + - endpoint + - payload + - sent_at + WebhookV2ResponseInfo: + type: + - object + - 'null' + description: Information regarding the reply sent back from a webhook endpoint + properties: + status: + type: string + description: HTTP status code of the response + received_at: + type: string + format: date-time + description: UTC ISO 8601 timestamp of when the response was received + required: + - status + - received_at + WebhookV2Status: + type: string + description: 'An enum representing the possible statuses you can set a webhook to: + + - `ACTIVE`: The webhook is healthy and receive all events + + - `PAUSED`: The webhook is paused and will not receive any events' + enum: + - ACTIVE + - PAUSED + WidgetNode: + allOf: + - type: object + properties: + type: + type: string + enum: + - WIDGET + description: The type of this node, represented by the string literal "WIDGET" + required: + - type + - $ref: '#/components/schemas/IsLayerTrait' + - $ref: '#/components/schemas/HasExportSettingsTrait' + - $ref: '#/components/schemas/HasChildrenTrait' securitySchemes: OAuth2: type: oauth2 @@ -859,4 +5591,3 @@ components: type: apiKey name: X-Figma-Token in: header - schemas: {} diff --git a/packages/v1-ready/figma/tests/api.test.js b/packages/v1-ready/figma/tests/api.test.js index 496aebda..7b73ae8f 100644 --- a/packages/v1-ready/figma/tests/api.test.js +++ b/packages/v1-ready/figma/tests/api.test.js @@ -132,7 +132,7 @@ describe('Figma Api', () => { it('lists team folders and folder files/meta', async () => { const api = makeApi(); await api.getTeamFolders('t1'); - await api.getFolderFiles('fold1'); + await api.getFolderFiles('fold1', { branch_data: true }); await api.getFolderMeta('fold1'); expect(api._captured[0].url).toBe( 'https://api.figma.com/v2/teams/t1/folders' @@ -140,6 +140,7 @@ describe('Figma Api', () => { expect(api._captured[1].url).toBe( 'https://api.figma.com/v2/folders/fold1/files' ); + expect(api._captured[1].query).toEqual({ branch_data: true }); expect(api._captured[2].url).toBe( 'https://api.figma.com/v2/folders/fold1/meta' ); @@ -159,7 +160,13 @@ describe('Figma Api', () => { await api.getWebhooks({ context: 'team', context_id: 't1' }); await api.postWebhook(body); await api.getWebhook('99'); - await api.putWebhook('99', { ...body, status: 'PAUSED' }); + await api.putWebhook('99', { + event_type: 'FILE_UPDATE', + team_id: 't1', + endpoint: body.endpoint, + passcode: body.passcode, + status: 'PAUSED', + }); await api.deleteWebhook('99'); await api.getTeamWebhooks('t1'); await api.getWebhookRequests('99'); @@ -173,6 +180,7 @@ describe('Figma Api', () => { 'https://api.figma.com/v2/webhooks/99' ); expect(api._captured[3].method).toBe('PUT'); + expect(api._captured[3].body.team_id).toBe('t1'); expect(api._captured[4].method).toBe('DELETE'); expect(api._captured[5].url).toBe( 'https://api.figma.com/v2/teams/t1/webhooks' diff --git a/packages/v1-ready/figma/tests/definition.test.js b/packages/v1-ready/figma/tests/definition.test.js index e74db211..0eeaf327 100644 --- a/packages/v1-ready/figma/tests/definition.test.js +++ b/packages/v1-ready/figma/tests/definition.test.js @@ -158,5 +158,7 @@ describe('Figma Definition', () => { } expect(declared.has('getMe')).toBe(true); expect(declared.has('postWebhook')).toBe(true); + expect(manifest.auth.scopes).toContain('project_metadata:read'); + expect(manifest.auth.scopes).toContain('folder_metadata:read'); }); }); diff --git a/packages/v1-ready/figma/tests/spec-sync.test.js b/packages/v1-ready/figma/tests/spec-sync.test.js index c7714a2a..5ca61e35 100644 --- a/packages/v1-ready/figma/tests/spec-sync.test.js +++ b/packages/v1-ready/figma/tests/spec-sync.test.js @@ -7,9 +7,11 @@ const spec = yaml.load( fs.readFileSync(path.join(__dirname, '..', 'figma.openapi.yaml'), 'utf8') ); +const HTTP = ['get', 'post', 'patch', 'put', 'delete']; + const specOperationIds = Object.values(spec.paths).flatMap((item) => Object.entries(item) - .filter(([m]) => ['get', 'post', 'patch', 'put', 'delete'].includes(m)) + .filter(([m]) => HTTP.includes(m)) .map(([, op]) => op.operationId) ); @@ -33,6 +35,7 @@ describe('OpenAPI spec ↔ client sync', () => { scope: 'current_user:read', }); expect(spec.servers[0].url).toBe(api.baseUrl); + expect(spec.servers[0].url).toBe('https://api.figma.com'); }); it('declares OAuth2 security with Figma authorize/token/refresh URLs', () => { @@ -43,4 +46,53 @@ describe('OpenAPI spec ↔ client sync', () => { expect(flow.tokenUrl).toBe('https://api.figma.com/v1/oauth/token'); expect(flow.refreshUrl).toBe('https://api.figma.com/v1/oauth/refresh'); }); + + it('matches the vendor OpenAPI shape (no global security; per-op scopes)', () => { + // Official figma/rest-api-spec leaves security on each operation. + expect(spec.security).toBeUndefined(); + const oauthScopes = new Set( + Object.keys( + spec.components.securitySchemes.OAuth2.flows.authorizationCode + .scopes + ) + ); + for (const item of Object.values(spec.paths)) { + for (const [method, op] of Object.entries(item)) { + if (!HTTP.includes(method)) continue; + expect(op.operationId).toBeTruthy(); + expect(op.responses).toBeTruthy(); + for (const sec of op.security || []) { + if (sec.OAuth2) { + for (const scope of sec.OAuth2) { + expect(oauthScopes.has(scope)).toBe(true); + } + } + } + } + } + }); + + it('preserves official putWebhook required team_id quirk', () => { + const schema = + spec.paths['/v2/webhooks/{webhook_id}'].put.requestBody.content[ + 'application/json' + ].schema; + expect(schema.required).toEqual( + expect.arrayContaining([ + 'event_type', + 'team_id', + 'endpoint', + 'passcode', + ]) + ); + // Vendor schema lists team_id as required but omits it from properties. + expect(schema.properties.team_id).toBeUndefined(); + }); + + it('documents getFolderFiles branch_data query param', () => { + const names = ( + spec.paths['/v2/folders/{folder_id}/files'].get.parameters || [] + ).map((p) => p.name); + expect(names).toContain('branch_data'); + }); });