Efecto Design Tool

Design Tool

Design REST API

Complete REST API for the Efecto Design Tool. Create sessions, execute design tools, upload images, search icons, publish sites, manage sharing, and more.

Prefer MCP?

If you're using Claude Code, the MCP integration handles all of this automatically. The REST API is for custom integrations.

OpenAPI Specification

The full API schema is available at /api/v1/design/openapi.json. Use it with code generators, API clients, or AI agents.

How It Works

Agent → creates session via REST → Server → returns session URL → Browseropens URL → WebSocket connects → Agent executes tools via REST → Server forwards to browser → changes render in real-time

  1. Your agent creates a session via POST /sessions
  2. The user opens the returned URL in their browser
  3. Your agent executes design tools via POST /sessions/:id/execute
  4. Changes render instantly in the browser via WebSocket
  5. Optionally subscribe to GET /sessions/:id/stream (SSE) for real-time updates

Base URL

https://efecto.app/api/v1/design

Authentication

Most design endpoints work without authentication. Sign-in is only required for endpoints that write persistent data or use paid features.

EndpointsAuth
Sessions, Execute, Stream, Poll, ToolsNone
Icon Search, Icon SVG, Image ProxyNone
Upload Image, Publish, SharesRequired
AI Chat, AI UsageRequired + Pro

Authentication uses Supabase session cookies, which are set automatically when a user signs in via the browser. API calls from the same browser context (e.g. fetch with credentials: "include") send cookies automatically.

Error Responses

All endpoints return errors as JSON with a consistent format:

error-response
{
  "error": "Human-readable error message"
}
StatusMeaning
400Invalid input, missing fields, validation failed
401Not authenticated (no valid session)
403Insufficient permissions or subscription required
404Resource not found
409Conflict (e.g. duplicate share)
413Payload too large
429Rate limited
502External API error (e.g. Vercel, upstream image)

Sessions

Manage the lifecycle of a design session. No authentication required.

Create Session

POST/api/v1/design/sessions

Creates a new design session. Returns a session ID and a URL for the user to open.

create-session
// Request
POST /api/v1/design/sessions
Content-Type: application/json

{
  "label": "Landing page design"
}

// Response
{
  "sessionId": "abc-123-def",
  "designUrl": "https://efecto.app/design?session=abc-123-def",
  "expiresAt": "2026-03-01T12:00:00Z"
}

Get Session Status

GET/api/v1/design/sessions/:id

Check if the browser is connected and see pending/completed tool calls.

session-status
// Response
{
  "session": {
    "id": "abc-123-def",
    "browserConnected": true,
    "label": "Landing page design",
    "createdAt": 1709280000000,
    "lastActivity": 1709280060000,
    "expiresAt": 1709283600000
  },
  "pendingCalls": 0,
  "completedCalls": 12
}

Close Session

DELETE/api/v1/design/sessions/:id

Closes the session, disconnects the browser, and cleans up resources.

Tool Execution

Execute design tools, poll for results, and discover available tools.

Execute Tool

POST/api/v1/design/sessions/:id/execute

Execute any design tool in the connected browser. Add ?wait=true to wait for the browser to process the call and return the result.

execute-tool
// Request
POST /api/v1/design/sessions/abc-123-def/execute?wait=true
Content-Type: application/json

{
  "tool": "efecto_create_artboard",
  "input": {
    "name": "Desktop",
    "width": 1440,
    "height": 900,
    "className": "flex flex-col",
    "backgroundColor": "#ffffff"
  }
}

// Response (wait=true)
{
  "callId": "call-xyz",
  "status": "completed",
  "result": {
    "success": true,
    "message": "Artboard created",
    "data": { "artboardId": "artboard-1" }
  }
}

// Response (wait=false, default)
{
  "callId": "call-xyz",
  "status": "pending"
}

Tool names

The REST API prefixes tool names with efecto_ (e.g. efecto_create_artboard). See the Tool Reference for all available tools.

Poll for Result

GET/api/v1/design/sessions/:id/poll/:callId

Poll for the result of an async tool call (when you used wait=false or omitted it). Use this instead of ?wait=truewhen you don't want to block.

poll-result
// Response (still processing)
{
  "callId": "call-xyz",
  "status": "pending",
  "result": null
}

// Response (completed)
{
  "callId": "call-xyz",
  "status": "success",
  "result": { "success": true, "message": "Artboard created", "data": {...} }
}

// Response (failed)
{
  "callId": "call-xyz",
  "status": "error",
  "result": { "error": "Node not found" }
}

List Tools

GET/api/v1/design/tools

Returns all available design tools with their input schemas. Useful for tool discovery and building dynamic UIs.

list-tools
// Response
{
  "total": 44,
  "tools": [
    {
      "name": "efecto_create_artboard",
      "description": "Creates a new artboard...",
      "input_schema": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "width": { "type": "number" },
          ...
        }
      }
    },
    ...
  ]
}

SSE Event Stream

GET/api/v1/design/sessions/:id/stream

Subscribe to real-time updates from the browser via Server-Sent Events.

sse-stream
// Connect
GET /api/v1/design/sessions/abc-123-def/stream
Accept: text/event-stream

// Events (unnamed — use onmessage, not addEventListener)
data: {"type":"connected","sessionId":"abc-123-def"}

data: {"type":"tool_call","callId":"call-xyz","tool":"efecto_create_artboard","input":{...}}

data: {"type":"heartbeat","timestamp":1709280060000}

data: {"type":"session_closed","reason":"Session expired"}

Media & Assets

Upload images, search icons, and proxy external media. Icon endpoints are unauthenticated. Image upload requires sign-in.

Upload Image

POST/api/v1/design/upload-image

Upload an image file or fetch from a URL. Returns a permanent hosted URL. Requires authentication.

ConstraintValue
Max file size10 MB
Allowed typesPNG, JPEG, GIF, WebP, SVG, AVIF
SSRF protectionBlocks localhost, private networks, cloud metadata
upload-image
// Option 1: Upload file (from browser)
const form = new FormData()
form.append("file", fileBlob)
const res = await fetch("/api/v1/design/upload-image", {
  method: "POST",
  credentials: "include",
  body: form,
})

// Option 2: Fetch from URL (from browser)
const res = await fetch("/api/v1/design/upload-image", {
  method: "POST",
  credentials: "include",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://example.com/photo.jpg" }),
})

// Response (201)
{ "url": "https://abc123.public.blob.vercel-storage.com/hero-xyz.png" }
GET/api/v1/design/icons/search

Search across 4 icon libraries (~9,000+ icons). No authentication required.

ParamTypeDescription
qstringSearch query (e.g. "arrow", "heart")
librarystringphosphor | lucide | heroicons | radix (default: all)
limitnumberMax results (default: 50, max: 200)
icon-search
// Request
GET /api/v1/design/icons/search?q=arrow&library=phosphor&limit=10

// Response
{
  "query": "arrow",
  "library": "phosphor",
  "count": 10,
  "results": ["arrow-right", "arrow-left", "arrow-up", ...],
  "libraries": [
    {
      "id": "phosphor",
      "name": "Phosphor",
      "iconCount": 7000,
      "variants": ["thin", "light", "regular", "bold", "fill", "duotone"]
    },
    {
      "id": "lucide",
      "name": "Lucide",
      "iconCount": 1500,
      "variants": ["regular"]
    },
    {
      "id": "heroicons",
      "name": "Heroicons",
      "iconCount": 600,
      "variants": ["outline", "solid", "mini"]
    },
    {
      "id": "radix",
      "name": "Radix",
      "iconCount": 300,
      "variants": ["regular"]
    }
  ]
}

Get Icon SVG

GET/api/v1/design/icons/svg

Returns the raw SVG string for a specific icon. Cached for 1 year (immutable).

ParamTypeDescription
namestringIcon name (required)
librarystringphosphor | lucide | heroicons | radix (default: phosphor)
variantstringWeight/variant (e.g. bold, fill, duotone, outline, solid)
sizenumberIcon size in pixels (default: 24)
icon-svg
// Request
GET /api/v1/design/icons/svg?library=phosphor&name=arrow-right&variant=bold&size=32

// Response (Content-Type: image/svg+xml)
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256">...</svg>

Image Proxy

GET/api/v1/design/image-proxy

Proxy external images server-side to bypass CORS and mixed-content restrictions. Returns the raw image bytes with proper content type. No authentication required.

ConstraintValue
Max size10 MB
Max redirects3
Timeout10 seconds
Caching1 hour
image-proxy
GET /api/v1/design/image-proxy?url=https%3A%2F%2Fexample.com%2Fphoto.jpg

// Returns raw image bytes with Content-Type header

Publishing

Publish designs as live websites hosted on Vercel. Requires authentication.

Publish Design

POST/api/v1/design/publish

Deploy a design as a static website. Provide the HTML and assets as files.

ConstraintValue
Max files50
Max per file5 MB
Max total payload10 MB
publish
// Request
POST /api/v1/design/publish
Content-Type: application/json

{
  "siteName": "my-landing-page",
  "files": [
    {
      "file": "index.html",
      "data": "<!DOCTYPE html><html>...</html>",
      "encoding": "utf-8"
    }
  ],
  "projectId": "prj_abc123"  // optional: update existing site
}

// Response
{
  "url": "my-landing-page.efecto.app",
  "vercelUrl": "efecto-my-landing-page.vercel.app",
  "projectId": "prj_abc123",
  "projectName": "efecto-my-landing-page",
  "deploymentId": "dpl_xyz789",
  "status": "QUEUED"
}

Check Deployment Status

GET/api/v1/design/publish?projectId=:id

Poll for the status of a published site. You must own the project. The url returned here is the Vercel deployment URL (use the url from the POST response for the efecto.app subdomain).

publish-status
// Response
{
  "url": "efecto-my-landing-page-abc123.vercel.app",
  "status": "READY",        // QUEUED | BUILDING | READY | ERROR
  "createdAt": "2024-01-15T10:30:00Z"
}

Sharing & Permissions

Share designs with collaborators via email invites or share links. Requires authentication. Note: PATCH and DELETE identify the share via request body / query param respectively (not as a path segment).

Create Share

POST/api/v1/design/shares

Invite a collaborator by email or generate a share link. You must own the document.

create-share
// Email invite
POST /api/v1/design/shares
{
  "documentId": "doc-uuid",
  "email": "collaborator@example.com",
  "role": "editor"     // "viewer" or "editor"
}

// Generate share link
POST /api/v1/design/shares
{
  "documentId": "doc-uuid",
  "role": "viewer",
  "type": "link"
}

// Response (201)
{
  "share": {
    "id": "share-uuid",
    "document_id": "doc-uuid",
    "role": "viewer",
    "share_token": "abc-def-123",
    "created_at": "2024-01-15T10:30:00Z"
  }
}

List Shares

GET/api/v1/design/shares?documentId=:id

List all shares for a document. Owner only.

list-shares
// Response
{
  "shares": [
    {
      "id": "share-uuid",
      "document_id": "doc-uuid",
      "shared_with": "user-uuid",
      "email": "collaborator@example.com",
      "role": "editor",
      "share_token": "abc-def-123",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ]
}

Update Share Role

PATCH/api/v1/design/shares
update-share
// Request
PATCH /api/v1/design/shares
{ "shareId": "share-uuid", "role": "viewer" }

// Response
{ "success": true }

Remove Share

DELETE/api/v1/design/shares?shareId=:id

Revoke a share. Owner only.

Check Permissions

GET/api/v1/design/permissions

Check what permission level the caller has for a document. Works for both authenticated users and unauthenticated users with a share token.

ParamTypeDescription
documentIdstringDocument ID (required)
tokenstringShare token for unauthenticated access
permissions
// Response
{
  "role": "owner"    // "owner" | "editor" | "viewer" | null
}

AI Chat

Streaming AI design assistant with tool use. Requires authentication and a Design Pro subscription.

Chat (Streaming)

POST/api/v1/design/ai/chat

Send messages to the AI design assistant. Returns a Server-Sent Event stream with text deltas and tool calls that execute in the browser.

ai-chat
// Request
POST /api/v1/design/ai/chat
Content-Type: application/json

{
  "messages": [
    { "role": "user", "content": "Make the heading bigger and bolder" }
  ],
  "documentState": "<artboard data-id=\"ab-1\" ...>...</artboard>",
  "selectedIds": ["node-abc"],
  "selectedSubtrees": "<h1 data-id=\"node-abc\">Hello</h1>",
  "artboards": [
    { "id": "ab-1", "name": "Desktop", "width": 1440, "height": 900 }
  ],
  "activeArtboardId": "ab-1"
}

// Response: Server-Sent Events
data: {"type":"route","model":"sonnet","intent":"modify","reason":"Simple style change"}

data: {"type":"text_delta","text":"I'll make the heading"}

data: {"type":"tool_start","id":"call-1","name":"efecto_update_node"}

data: {"type":"tool_call","id":"call-1","name":"efecto_update_node","input":{"nodeId":"node-abc","className":"text-6xl font-black"}}

data: {"type":"tool_calls_pending","toolCalls":[{"id":"call-1","name":"efecto_update_node","input":{...}}]}

data: {"type":"usage","used":5,"limit":100,"remaining":95,"bonusCredits":0,"totalRemaining":95}

data: {"type":"done","reason":"complete"}

Agentic loop

After receiving tool_calls_pending, execute the tools in the browser, then send the results back in a follow-up request so the AI can continue.
ai-chat-followup
// Follow-up request with tool results
POST /api/v1/design/ai/chat
{
  "messages": [
    { "role": "user", "content": "Make the heading bigger and bolder" }
  ],
  "documentState": "<artboard ...>...updated state...</artboard>",
  "selectedIds": [],
  "artboards": [...],
  "pendingAssistantText": "I'll make the heading",
  "pendingToolCalls": [
    { "id": "call-1", "name": "efecto_update_node", "input": { "nodeId": "node-abc", "className": "text-6xl font-black" } }
  ],
  "pendingToolResults": [
    { "tool_use_id": "call-1", "result": "{\"success\":true,\"message\":\"Node updated\"}" }
  ]
}

Check AI Usage

GET/api/v1/design/ai/usage

Check remaining AI message quota for the authenticated user.

ai-usage
// Response
{
  "used": 12,
  "limit": 100,
  "remaining": 88,
  "bonusCredits": 0,
  "totalRemaining": 88,
  "unlimited": false    // true for admin users
}

Quick Start Example

Create a landing page in 4 API calls:

quickstart.sh
# 1. Create session
curl -X POST https://efecto.app/api/v1/design/sessions \
  -H "Content-Type: application/json" \
  -d '{"label":"My landing page"}'

# 2. Create artboard (after user opens the URL)
curl -X POST https://efecto.app/api/v1/design/sessions/SESSION_ID/execute?wait=true \
  -H "Content-Type: application/json" \
  -d '{"tool":"efecto_create_artboard","input":{"name":"Desktop","width":1440,"height":900,"className":"flex flex-col"}}'

# 3. Add hero section
curl -X POST https://efecto.app/api/v1/design/sessions/SESSION_ID/execute?wait=true \
  -H "Content-Type: application/json" \
  -d '{"tool":"efecto_add_section","input":{"parentId":"ARTBOARD_ID","jsx":"<section className=\"flex flex-col items-center justify-center py-24 px-8\"><h1 className=\"text-6xl font-bold\">Hello World</h1></section>"}}'

# 4. Close session
curl -X DELETE https://efecto.app/api/v1/design/sessions/SESSION_ID

Reference