HTML to Figma
Convert an HTML document into a Figma-importable file via a single HTTPS request, authenticated with an Open API Key.
You’ll need an API key
This endpoint is authenticated with an Open API Key (prefix
Create an API key →sk_). Your key is shown once; store it somewhere safe.Quick start
- Go to the API Keys dashboard and click Create API Key.
- Copy the full key (starts with
sk_) — it is displayed only once. - Send your first request:
curl -X POST https://api.copyto.design/v1/open/html_to_figma \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"html":"<h1 style=\"color:#2563eb\">Hello, Figma</h1>"}'The response body is the converted Figma file payload (see Response).
Endpoint
POST https://api.copyto.design/v1/open/html_to_figmaOnly POST is supported. Content-Type must be application/json.
Authentication
Pass your Open API Key in the Authorization header:
Authorization: Bearer sk_...Important notes:
- An Open API Key is only accepted on this endpoint. Using it against any other path returns
403 endpoint not accessible with api key. - Session tokens issued by the copyto.design web app are a different credential and are not interchangeable with
sk_keys in your integrations. - Deleting a key from the dashboard removes it from your list immediately, but the underlying token stays valid until its natural expiry. Rotate keys if you believe one is compromised.
Request body
At minimum, provide either html or url.
{
"html": "<!doctype html><html>...</html>",
"url": "https://example.com",
"baseUrl": "https://cdn.example.com",
"options": {
"viewportWidth": 1440,
"viewportHeight": 900,
"deviceScaleFactor": 1,
"waitUntil": "networkidle",
"waitForSelector": ".hero",
"timeoutMs": 15000
}
}Fields
| Field | Type | Required | Description |
|---|---|---|---|
html | string | conditional | Single-file HTML document. Required if url is not provided. |
url | string | conditional | Public URL to fetch and convert. Required if html is not provided. |
baseUrl | string | optional | Base URL used to resolve relative assets in html. Ignored when url is provided. |
options.viewportWidth | number | optional | Viewport width in CSS pixels. Default 1440. |
options.viewportHeight | number | optional | Viewport height. Omit to auto-size to the rendered document height. |
options.deviceScaleFactor | number | optional | Device pixel ratio. Default 1. |
options.waitUntil | string | optional | Navigation wait condition: load, domcontentloaded, or networkidle. |
options.waitForSelector | string | optional | CSS selector to wait for before capturing. |
options.timeoutMs | number | optional | Global timeout in milliseconds. Default 15000. |
Response
A successful request returns 200 OK with a JSON string body — the base64-encoded Figma Kiwi (.fig) binary produced by the converter. Write it to a file and open it in Figma, or feed it into the Copy to Design Figma plugin.
HTTP/1.1 200 OK
Content-Type: application/json
"AAECAwQF...Base64EncodedFigBinary..."Examples
cURL
curl -X POST https://api.copyto.design/v1/open/html_to_figma \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"html": "<!doctype html><html><body><h1>Hello</h1></body></html>",
"options": { "viewportWidth": 1280 }
}' \
-o output.fig.b64JavaScript (fetch)
const res = await fetch('https://api.copyto.design/v1/open/html_to_figma', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.COPYTO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: '<!doctype html><html><body><h1>Hello</h1></body></html>',
options: { viewportWidth: 1280 },
}),
});
if (!res.ok) {
throw new Error(`convert failed: ${res.status} ${await res.text()}`);
}
const base64Fig = await res.json();
await fs.promises.writeFile('output.fig', Buffer.from(base64Fig, 'base64'));Python (requests)
import base64
import os
import requests
resp = requests.post(
'https://api.copyto.design/v1/open/html_to_figma',
headers={
'Authorization': f"Bearer {os.environ['COPYTO_API_KEY']}",
'Content-Type': 'application/json',
},
json={
'url': 'https://example.com',
'options': {'waitUntil': 'networkidle'},
},
timeout=60,
)
resp.raise_for_status()
with open('output.fig', 'wb') as f:
f.write(base64.b64decode(resp.json()))Errors
All error responses use the standard { code, message } envelope.
| HTTP | When |
|---|---|
401 | Authorization header missing, malformed, or the sk_ key is invalid / expired. |
403 | Valid sk_ key used on a path that is not listed as an open endpoint. |
402 | Your plan’s monthly conversion quota is exhausted (sorry, you have used up all your credit). Upgrade from Pricing to continue. |
429 | Per-IP rate limit hit. Back off and retry with exponential delay. |
500 | Upstream conversion failure. Retry once; if it persists, contact support. |
Usage and limits
- Quota is counted against the
copy_to_designproduct on the key owner’s account. Calls viask_keys consume the same monthly balance as calls from the web app. - Requests are rate-limited per source IP. For production workloads, keep requests serial or add a modest concurrency cap.
- Payload size is bounded by your current plan (2 MB on Free, 20 MB on paid plans).
Next steps
- Manage keys from the API Keys dashboard.
- Review the authentication guide for key lifecycle and security best practices.