Generate Design from Wireframe (Trigger)
Method
This API follows the REST methodology. It is the trigger for a wireframe-to-design generation run: it creates the client-facing wf2des row, captures the trigger-time wireframe snapshot, and enqueues the parse message. Generation itself runs asynchronously; this endpoint returns immediately with a poll target.
HTTP Method
POST: Generate Design from Wireframe (Trigger)
Naming Convention
To ensure consistency and readability, JSON nodes in requests and responses use camelCase. SQS payload nodes use snake_case.
Request and Response
Headers
Meta information is set in HTTP headers, not in the response body.
Request Headers
AuthorizationContent-TypeAcceptAccept-language
Response Headers
Content-Type
Generate Design from Wireframe (Trigger)
URI
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| organization_id | integer | Required | Organization ID |
| project_id | integer | Required | Project ID |
Request Body
The request body is JSON.
{
"figmaFileKey": "aBc1DeFg2HiJ3kLmNoPqRs",
"wfNodeId": "128:4567",
"sectionNodeId": "128:4000",
"screenId": "MEM_REG_TOP",
"prompt": "Emphasize the primary CTA and keep the form compact",
"placementTarget": "128:9001",
"autoConfirm": false
}
Request Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| figmaFileKey | string | Required | Target Figma file key the wireframe frame lives in (Figma verbatim; alphanumeric, no underscore) |
| wfNodeId | string | Required | Target wireframe frame node id (Figma node id, uses :) |
| sectionNodeId | string | Optional | The frame's enclosing board / SECTION, resolved by the caller, used to scope the snapshot fetch. Omit it and the backend resolves the scope itself by walking the whole file document. Verified server-side, never trusted: if the frame is not inside this subtree, the backend logs wf2desSnapshot.scopeRejected and falls back to the full walk. |
| screenId | string | Required | Screen ID, prefilled from the frame name and user-confirmed |
| prompt | string | Optional | Generation prompt (selection input, ranked below memo intent) |
| placementTarget | string | Optional | Placement node id, echoed into the result document request block |
| autoConfirm | boolean | Required | false normal (parse โ confirm step) / true auto-confirm (parse + assemble in one worker invocation) |
trigger_surfaceis derived backend-side from the authenticated surface (0api /1plugin) and is never caller-supplied.Why
sectionNodeIdis worth sending. The snapshot needs the frame's enclosing board, and the Figma REST API cannot return a node's ancestors โ/nodesgives a subtree with none. Without this field the backend must therefore fetchGET /files/{key}, the entire file. Measured on a 31-page production file that response was 255 MB and took 67.5s, and one trigger failed outright at 140s with "socket connection was closed unexpectedly", surfacing as a 500. The same scope fetched as a subtree is 0.38 MB. A Figma plugin knows the ancestry for free, so the plugin client always sends it.
Response
The response is JSON (HTTP status: 201 Created).
{
"wf2desId": "8f14e45f-ceea-467d-9a3e-1b2c3d4e5f60",
"status": "0",
"phase": "0",
"figmaFileKey": "aBc1DeFg2HiJ3kLmNoPqRs",
"wfNodeId": "128:4567",
"screenId": "MEM_REG_TOP",
"autoConfirm": false,
"triggerSurface": "0",
"attempt": 1,
"createdAt": "2026-07-10T09:30:00Z"
}
Response Fields
| Name | Type | Description |
|---|---|---|
| wf2desId | string | wf2des UUID โ the client-facing request id (the poll key for GET; echoed to the worker as job_id) |
| status | string (enum) | Generation status. On create always "0" processing (see Error Handling / Type Codes) |
| phase | string (enum) | Orchestration phase inside status "0". On create always "0" parse |
| figmaFileKey | string | Target Figma file key |
| wfNodeId | string | Target wireframe frame node id |
| screenId | string | Screen ID |
| autoConfirm | boolean | Echo of the requested auto-confirm mode |
| triggerSurface | string (enum) | "0" api / "1" plugin โ derived backend-side, never caller-supplied |
| attempt | integer | Monotonic fencing counter โ always 1 today; nothing bumps it |
| createdAt | string | Row creation timestamp (ISO 8601) |
Status / phase enums (wf2des row):
status:"0"processing ยท"1"completed ยท"2"failed ยท"3"rejected ยท"4"cancelledphase(insidestatus "0",nullonce terminal):"0"parse ยท"1"awaiting_confirm ยท"2"assemble
Authentication
Authentication is performed using JSON Web Tokens (JWT) issued by Amazon Cognito. The authenticated surface determines trigger_surface (0 api / 1 plugin), which the backend records on the row โ it is not accepted from the request body.
Error Handling
The following status codes are returned for errors.
| Description | Status Code | Status Name |
|---|---|---|
Missing required fields, or invalid figmaFileKey / wfNodeId format |
400 | Bad Request |
| Missing authentication credentials | 401 | Unauthorized |
| Insufficient permissions | 403 | Forbidden |
| Organization or project not found | 404 | Not Found |
Duplicate open generation โ a live run already exists for this (projectId, figmaFileKey, wfNodeId) |
409 | Conflict |
| Internal server error (e.g. snapshot capture or SQS send failure) | 500 | Internal Server Error |
A missing Figma token surfaces as 500, not 4xx. Every failure between row creation and enqueue โ including "no Figma token registered for this user" โ is deliberately normalized so that no upstream Figma status leaks to the caller, and the row is failed so the node can be re-triggered. The remedy is to register a token first via
POST /v1/figma/token; clients should checkGET /v1/figma/tokenand prompt for one before offering Generate, rather than surfacing an opaque 500 to the designer.
409 โ Duplicate Open Generation
The wf2des row enforces a partial unique index on (project_id, figma_file_key, wf_node_id) WHERE status = '0' โ at most one live (in-progress) generation per wireframe node. A duplicate trigger does not create a second row; it returns 409 Conflict together with the existing wf2desId, so the second caller attaches to the run already in flight.
{
"error": "duplicate_open_generation",
"message": "A generation is already in progress for this wireframe node.",
"wf2desId": "8f14e45f-ceea-467d-9a3e-1b2c3d4e5f60"
}
Processing Flow
- Extract
organization_idandproject_idfrom the path parameters. - Authenticate the Cognito JWT and verify the user has Write access to the project; derive
trigger_surface(0api /1plugin) from the authenticated surface. - Extract and validate the request body:
figmaFileKey,wfNodeId,screenId(required),prompt,placementTarget,autoConfirm. - Create the
wf2desrow FIRST โstatus "0"processing,phase "0"parse,attempt = 1, withfigma_file_key,wf_node_id,screen_id,prompt,placement_target,auto_confirm, and the derivedtrigger_surface. The client always has a poll target before anything is enqueued. - If the insert violates the partial unique index
(project_id, figma_file_key, wf_node_id) WHERE status = '0', return 409 with the existingwf2desId(the second caller attaches to the live run). - Resolve the requesting user's own Figma token (
figma_token.personal_access_token, keyed on theircognito_sub). Generation is a user action, so the snapshot is taken with the authority of whoever asked for it โ not a service-account PAT. A user therefore cannot snapshot a file they could not open themselves. If they have no registered token the run fails at this step; see the note under Error Responses. - Capture the trigger-time wireframe snapshot (frame + enclosing board section, memos included) to S3, auto-registering the frame if it is not yet registered; compute the snapshot
wf_content_hash. WhensectionNodeIdis supplied and contains the frame, only that subtree is fetched; otherwise the whole file document is walked. - Send the parse message to the backend-owned generation SQS queue (snake_case payload โ see below).
- Return 201 Created with the created
wf2desId,status "0",phase "0", and echoed request fields.
The worker then parses the snapshot and posts an ai-status webhook; the backend advances the row (phase "1" awaiting_confirm on interactive parse-done, or straight to assemble on autoConfirm). Workers never write PostgreSQL โ every row transition is backend-side.
Detailed Flowchart
flowchart TD
Start([POST Request]) --> Route[Route Handler]
Route --> Auth[Auth & Parameter Extraction<br/>derive trigger_surface 0 api / 1 plugin]
Auth --> AccessCheck[Access Check]
AccessCheck --> HasAccess{Write Access?}
HasAccess -->|No| Err403[403 Forbidden]
HasAccess -->|Yes| VerifyScope[Verify Organization & Project]
VerifyScope --> ScopeExists{Exists?}
ScopeExists -->|No| Err404[404 Not Found]
ScopeExists -->|Yes| Validate[Validate Body<br/>figmaFileKey / wfNodeId / screenId]
Validate --> BodyValid{Valid?}
BodyValid -->|No| Err400[400 Bad Request]
BodyValid -->|Yes| CreateRow[Create wf2des Row FIRST<br/>status=0 processing, phase=0 parse<br/>attempt=1]
CreateRow --> Dedupe{Open generation exists?<br/>partial unique figma_file_key + wf_node_id<br/>WHERE status='0'}
Dedupe -->|Yes| Err409[409 Conflict<br/>+ existing wf2desId]
Dedupe -->|No| Snapshot[Capture trigger-time WF snapshot<br/>auto-register unregistered frame<br/>compute wf_content_hash]
Snapshot --> SnapResult{Success?}
SnapResult -->|No| Err500[500 Internal Server Error]
SnapResult -->|Yes| SendSQS[SendMessage parse payload<br/>to generation SQS queue]
SendSQS --> SQSResult{Success?}
SQSResult -->|No| Err500
SQSResult -->|Yes| Success[201 Created<br/>wf2desId, status=0, phase=0]
Asynchronous Processing
The design is generated asynchronously by the wf2des worker. The backend โ never the worker โ advances the row:
status "0"processing,phase "0"parse (on create)- โ
phase "1"awaiting_confirm (interactive parse-done, via theai-statuswebhook) or straight intophase "2"assemble onautoConfirm - โ terminal
status "1"completed /"2"failed (via theai-statuswebhook), or"3"rejected (parse declined) /"4"cancelled (cancel endpoint)
The output is a native-Figma DesignSpec written to the design_generation_result DocumentDB collection (keyed by the wf2desId), fetched via the wf2des data-plane API and materialized by the Figma plugin. phase is cleared (null) once the row reaches a terminal status.
SQS Payload
On trigger, the backend sends the parse message to the backend-owned generation SQS queue. The message is in snake_case.
{
"wf2des_id": "8f14e45f-ceea-467d-9a3e-1b2c3d4e5f60",
"attempt": 1,
"nonce": "9c2b7f1a-0e44-4b8f-9d2a-77c1e0a3b512",
"wireframe_img_url": "s3://bucket-name/1/3/wf2des/snapshots/aBc1DeFg2HiJ3kLmNoPqRs/128:4567.png",
"wireframe_json_url": "s3://bucket-name/1/42/wf2des/snapshots/aBc1DeFg2HiJ3kLmNoPqRs/128-4567.<content_hash>.json",
"snapshot_scope": {
"frame_node_id": "128:4567",
"section_node_id": "128:4000"
},
"wf_content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"screen_id": "MEM_REG_TOP",
"prompt": "Emphasize the primary CTA and keep the form compact",
"placement_target": "128:9001",
"auto_confirm": false,
"design_rule_file_url": "s3://bucket-name/1/3/wf2des/rules/rule.json",
"structure_file_url": null,
"detail_design_file_url": null,
"design_file_url": null
}
SQS Payload Fields
| Name | Type | Required | Description |
|---|---|---|---|
| wf2des_id | string (uuid) | Required | = the wf2des row id; echoed as job_id in the webhook. Run identity / fencing pair member |
| attempt | integer | Required | Monotonic fencing counter; fences DocDB commits (CAS on (_id, attempt)). 1 on trigger |
| nonce | string | Required | Fresh per enqueue; part of the (job_id, attempt, nonce) webhook dedupe key |
| wireframe_img_url | string (s3://) |
Required | Trigger-time wireframe image snapshot, pre-collected by the backend |
| wireframe_json_url | string (s3://) |
Required | Trigger-time wireframe JSON snapshot (frame + enclosing board section, memos included) |
| snapshot_scope | object | Required | {frame_node_id, section_node_id} โ the frame and its enclosing board section |
| wf_content_hash | string (sha256) | Required | Snapshot content hash, computed at capture; the parse-cache key |
| screen_id | string | Required | Screen ID, from the request |
| prompt | string | null | Optional | Generation prompt (selection input, ranked below memo intent) |
| placement_target | string | null | Optional | Placement node id, echoed into the result document request block |
| auto_confirm | boolean | Required | false normal / true auto โ parse + assemble in one worker invocation |
| design_rule_file_url | string (s3://) |
Optional | Pre-collected design rule file URL, for pinning |
| structure_file_url | string (s3://) |
Optional | Pre-collected structure file URL, for pinning |
| detail_design_file_url | string (s3://) |
Optional | Pre-collected detail design file URL, for pinning |
| design_file_url | string (s3://) |
Optional | Pre-collected reference design file URL, for pinning |