Skip to content

Connect Figma (Authorize)

Method

This API follows the REST methodology.

HTTP Method

POST: Mint the Figma consent URL an organization admin opens to connect the Figma OAuth app โ€” the credential the wf2des scheduler runs on.

This is a deliberate one-time setup action, not part of plugin login. See Why this exists.

Naming Convention

To ensure consistency and readability, JSON nodes in requests and responses use camelCase.

Request and Response

Headers

Request Headers

  • Authorization
  • Accept
  • Accept-language

Response Headers

  • Content-Type

Authorize

URI

POST /api/v1/organizations/{organization_id}/figma-oauth/authorize

Path Parameters

Name Type Required Description
organization_id integer Required Organization ID

Request Body

None.

Response

The response is JSON (HTTP status: 200 OK).

{
  "authorizeUrl": "https://www.figma.com/oauth?client_id=โ€ฆ&redirect_uri=โ€ฆ&scope=file_content%3Aread&state=โ€ฆ&response_type=code",
  "expiresInSeconds": 600
}

Response Fields

Name Type Description
authorizeUrl string The Figma consent URL. Carries a signed, expiring, organization-bound state.
expiresInSeconds integer How long the link stays usable (600). After that, request a new one.

Open it signed in as the SERVICE ACCOUNT

Open authorizeUrl in a private/incognito window signed in as the dedicated Figma service account โ€” not your own Figma user. A browser already signed in as the admin binds the grant to that person, which looks like success and reintroduces exactly the dependency this replaces: the scheduler then breaks when they rotate access or leave.

The service account must have access to the Figma files

An OAuth grant carries only what the authorizing account can already see. If the service account has not been invited to the team or files the project uses, every Figma read returns 403 Request denied โ€” the grant itself is valid, so this looks like a broken integration rather than a permissions gap.

Verified in practice: on the same file and the same request, a PAT belonging to an account with access returned 200, while a freshly-issued grant for an account without access returned 403. Invite the service account to the files (or their team) before connecting, and confirm afterwards with a file read.

Authentication

JWT issued by the Admin Cognito pool, plus organization-level read/write access. This establishes an organization-wide credential, so a regular application user or project owner cannot replace it.

Error Handling

Description Status Code Status Name
Missing authentication credentials 401 Unauthorized
Admin role lacks organization read/write access 403 Forbidden
Organization does not exist 404 Not Found
Figma OAuth is not configured on this deployment 500 Internal Server Error

The 500 names which of FIGMA_OAUTH_CLIENT_ID / FIGMA_OAUTH_CLIENT_SECRET / FIGMA_OAUTH_REDIRECT_URI / FIGMA_OAUTH_STATE_SECRET is missing โ€” all four are required together.

Processing Flow

  1. Extract the organization ID from path parameters.
  2. Verify the Admin Cognito session and organization read/write access.
  3. Mint state โ€” a signed, org-bound, 10-minute token (see callback for why it matters).
  4. Build the Figma consent URL with client_id, the registered redirect_uri, scope=file_content:read, state, response_type=code.
  5. Return the URL. Nothing is stored yet.

Why this exists

The scheduled component_sweep is a bodyless EventBridge invoke โ€” no user, no request, no tenancy beyond the default organization. A personal access token cannot serve it: it is one human's standing authority with no expiry we control, and the worker holds no PostgreSQL client to reach the platform figma_token table at all.

Figma supports only the authorization-code flow โ€” there is no client-credentials / machine-to-machine grant โ€” so a headless run can never mint its own credential. Exactly one human step is unavoidable, and this endpoint is it. Everything afterwards is an automatic refresh in the Admin API. The worker requests only a valid access token from the Admin API.

The client secret alone is not enough: it identifies the app, not the authority to read anyone's files.