Submit Plugin Component Captures
Method
This API follows the REST methodology.
HTTP Method
POST: Submit plugin-observed component captures โ enqueue a component_capture event, which routes to the worker's registry merge.
Why this endpoint exists
The REST sweep behind POST โฆ/wf2des/components cannot read a design-system library. A remote component is only visible through an instance of it, so the sweep lands keyless local twins with fragmentary variant axes โ and a registry document with no component_key cannot be materialized by the plugin at all. On one live project, 106 of 279 registry documents had no variant data, 56 of them declaring axes regardless.
The plugin can read the authoritative main component from inside Figma (getMainComponentAsync yields the main id, name, publish key, component-set id and variant values even for components REST returns 403 for). These captures are therefore the authoritative source for component_key and variant_properties.
The worker merges them monotonically โ a richer capture wins, sweep-only fields (text_slots / image_slots / default_size) on a local component are never clobbered, and provenance: "plugin-observed" stops a later sweep from downgrading the record.
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
Request Headers
AuthorizationContent-TypeAcceptAccept-language
Response Headers
Content-Type
Submit Component Captures
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": "abc123XYZ",
"captures": [
{
"kind": "set",
"nodeId": "10:300",
"setKey": "a1b2c3d4e5f6",
"componentName": "Button",
"componentKeys": ["k1", "k2"],
"axes": { "Size": ["S", "M"], "State": ["default", "disabled"] },
"defaults": { "Size": "M", "State": "default" },
"textProps": ["Label#1:0"],
"variants": [
{
"variantProps": { "Size": "M", "State": "default" },
"size": { "w": 120, "h": 40 },
"textSlots": [{ "layerPath": "Label", "defaultText": "Button" }],
"nestedComponents": ["Icon"],
"appearance": ["fill: #FF6214", "radius: 8"],
"layoutShape": { "units": 2, "columns": 2, "rows": 1 }
}
],
"setName": "Button",
"provenance": "plugin-observed"
}
]
}
Request Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| figmaFileKey | string | Required | The file the captured components live in. Figma verbatim. |
| captures | object[] | Required | Plugin scanSelection() output โ at least one entry per observed COMPONENT_SET / COMPONENT. |
Capture Object
| Name | Type | Required | Description |
|---|---|---|---|
| kind | string (enum) | Required | set = a COMPONENT_SET (variant axes on axes); standalone = a bare COMPONENT. |
| nodeId | string | Required | The COMPONENT_SET (or standalone COMPONENT) node id. |
| setKey | string | null | Required | COMPONENT_SET publish key; null for a bare or unpublished component. |
| componentName | string | Required | The set (or component) name as Figma reports it. |
| componentKeys | string[] | Required | Publish keys of the set's components, or the single component. |
| axes | object (stringโstring[]) | Required | Variant axis name โ legal options. Authoritative โ replaces the swept fragments. |
| defaults | object (stringโstring) | Required | Variant axis name โ default value. |
| textProps | string[] | Required | Non-variant BOOLEAN / TEXT / INSTANCE_SWAP component-property names. |
| variants | object[] | Optional | Per-variant description; defaults to []. May be partial โ the whole set when reachable, else only the observed variant. |
| setName | string | null | Optional | The COMPONENT_SET's real name when reachable; defaults to null. The sweep names a remote document after the instance it saw, so this is what lets the registry hold the real component name. |
| provenance | string (literal) | Required | Always plugin-observed. Marks the capture as read from inside Figma so a later sweep cannot downgrade it. |
Variant Object
| Name | Type | Required | Description |
|---|---|---|---|
| variantProps | object (stringโstring) | Required | This variant's own axis values, parsed from the variant COMPONENT's name. |
| size | { w, h } |
Required | The variant's own bounding size. |
| textSlots | object[] | Required | { layerPath, defaultText } โ text layers of this variant. A set's variants expose different layers. |
| nestedComponents | string[] | Required | Instance-layer names this variant contains (traversal stops at each instance). |
| appearance | string[] | Optional | The variant's look as facts read from its own node โ fill, border (including the dash that conventionally marks a placeholder), radius, and whether it holds content at all. Defaults to []. |
| layoutShape | { units, columns, rows } | null |
Optional | The grid this variant tiles its cells into; null when the content is not a regular tiling. This is the field that separates variants identical in every other respect โ a container holding the same six cells 2-across in one variant and 3-across in another. |
Response
The response is JSON (HTTP status: 202 Accepted).
{
"eventRunId": "0190f3a1-2c4e-7b8d-9e0f-1a2b3c4d5e72",
"figmaFileKey": "abc123XYZ",
"captureCount": 1
}
Response Fields
| Name | Type | Description |
|---|---|---|
| eventRunId | string | Poll GET โฆ/wf2des/events/{eventRunId}/status for live progress. |
| figmaFileKey | string | Echoed from the request. |
| captureCount | integer | How many captures were forwarded to the worker. |
Authentication
Authentication is performed using JSON Web Tokens (JWT) issued by Amazon Cognito. The caller must additionally hold Write access to the project.
Error Handling
| Description | Status Code | Status Name |
|---|---|---|
Invalid body (captures empty, or a capture failing its schema) |
400 | Bad Request |
| Missing authentication credentials | 401 | Unauthorized |
| Insufficient permissions (no Write access to the project) | 403 | Forbidden |
| Project or organization not found | 404 | Not Found |
| SQS send failure | 500 | Internal Server Error |
Processing Flow
- Extract organization ID and project ID from path parameters; extract the body.
- Verify the user has Write access to the project.
- Mint a fresh
eventRunId. - Snake-case each capture and send the
component_captureevent to thewf2des-eventsSQS queue. - Return 202 with the poll handle and the forwarded capture count.
Asynchronous Processing
The component_capture event routes to the worker's registry merge, which upserts each capture into design_component. Captures win on component_key and variant_properties; the worker merges per axis and leaves sweep-derived slot data on a local component untouched.
This is an internal run โ no ai-status webhook, no PostgreSQL effect.
SQS Payload
Field names are snake-cased for the worker's ComponentCapture / VariantProfile models.
{
"event_type": "component_capture",
"organization_id": 1,
"project_id": 7,
"event_run_id": "0190f3a1-2c4e-7b8d-9e0f-1a2b3c4d5e72",
"figma_file_key": "abc123XYZ",
"captures": [
{
"kind": "set",
"node_id": "10:300",
"set_key": "a1b2c3d4e5f6",
"component_name": "Button",
"component_keys": ["k1", "k2"],
"axes": { "Size": ["S", "M"] },
"defaults": { "Size": "M" },
"text_props": ["Label#1:0"],
"variants": [
{
"variant_props": { "Size": "M" },
"size": { "w": 120, "h": 40 },
"text_slots": [{ "layer_path": "Label", "default_text": "Button" }],
"nested_components": ["Icon"]
}
]
}
]
}