Getting Started
Programmatic access to Moda canvases, designs, brand kits, and AI design tasks via the REST API.
The Moda REST API provides direct HTTP access to your canvases, designs, brand kits, and AI design capabilities. Use it to build integrations, automate workflows, or embed Moda functionality in your own applications.
Base URL
All API requests use the following base URL:
https://api.moda.app/v1Authentication
Authenticate by including an API key as a Bearer token in the Authorization header. Generate API keys from Settings > Developer > REST API in the Moda app.
Authorization: Bearer moda_live_abc123...See Authentication for details on creating keys, available scopes, and security best practices.
Quick example
List your canvases with a single curl command:
curl https://api.moda.app/v1/canvases \
-H "Authorization: Bearer moda_live_abc123..."Response:
{
"canvases": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Marketing Website",
"url": "https://moda.app/canvas/550e8400-e29b-41d4-a716-446655440000",
"updated_at": "2025-03-15T10:30:00Z"
}
],
"total": 42,
"limit": 20,
"offset": 0
}What you can do
| Area | Capabilities |
|---|---|
| Canvases | List and search your canvases |
| Designs | Extract semantic pseudo-HTML, design tokens, page metadata, and export as PNG/JPEG/PDF/PPTX |
| Jobs | Start AI design tasks, poll for progress, list recent jobs |
| Organizations | List your organizations and teams |
| Brand Kits | List, create, and update brand kits |
| Uploads | Upload files for use as attachments in design tasks |
| Remix | Duplicate a canvas and optionally apply AI edits |
Rate limiting
The API allows 120 requests per minute per API key. If you exceed this limit, requests return 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.
Error format
Every error response carries a single structured envelope nested under an error key:
{
"error": {
"type": "not_found",
"code": "file_not_found",
"message": "Canvas cvs_abc123 not found",
"doc_url": "https://docs.moda.app/errors/file_not_found",
"request_id": "019d8996-16b3-73ee-841a-5bc5038eb972"
}
}Fields
| Field | Type | Notes |
|---|---|---|
type | string | Stable high-level category. One of invalid_request, authentication, permission, not_found, conflict, rate_limited, idempotency_conflict, unprocessable, upstream_error, internal_error. Branch your retry logic on this. |
code | string | Narrow machine-readable identifier for the specific failure. Stable once published; each code maps to a doc_url. |
message | string | Human-readable message for developers. Not localized, not user-facing. |
doc_url | string | Permalink to the documentation page for this code. |
request_id | string | Correlator echoed from the X-Request-ID response header. Include it when contacting support -- it's how we find your request in our logs. |
causes | array? | Optional. A list of nested error envelopes for aggregated failures (e.g. a multi-page export where individual pages failed). Each entry is itself a full error object. |
details | object? | Optional. Code-specific structured detail. For validation errors (code: "validation_failed"), contains {"fields": [{"field": "body.canvas_id", "code": "string_too_short"}, ...]}. |
retry_after_ms | number? | Optional. Hint in milliseconds for transient and rate-limited errors. |
Status codes
| Status | Type | Typical causes |
|---|---|---|
400 | invalid_request | Bad parameters, malformed JSON, unknown canvas format |
401 | authentication | Missing or invalid API key |
403 | permission | Key lacks the required scope, or resource belongs to a different team |
404 | not_found | Resource does not exist or is not visible to the key |
409 | conflict / idempotency_conflict | Name collision, or an idempotency key reused with a different body |
422 | unprocessable | Request is well-formed but fails validation |
429 | rate_limited | Per-key or per-org rate / concurrency limit exceeded |
502/503/504 | upstream_error | A third-party service (e.g. web scrape, model provider) failed |
500 | internal_error | Unexpected server error -- include request_id if reporting |
Client guidance
- Branch on
type, not status code alone. Status codes collapse distinct failure modes together;typeseparatesrate_limitedfromidempotency_conflicteven though both are409-adjacent. - Log
request_idon every error. It is present on every response (success and failure) both in the body and in theX-Request-IDheader. Pass it to support if you need help diagnosing a specific call. - Retry on
upstream_errorandrate_limited. Everything else is either permanent or requires you to fix the request. - Do not parse
message. It can change without notice. Usetypeandcodefor branching,messageonly for display.
Use Moda docs in your AI editor
Give your AI agent direct access to these docs while building with the API. Using mcpdoc by LangChain, your agent can search and read Moda documentation without you having to copy-paste.
claude mcp add mcpdoc -- uvx mcpdoc --urls "https://docs.moda.app/llms.txt"Go to Settings > Extensions and add a new MCP server with these settings:
{
"mcpServers": {
"moda-docs": {
"command": "uvx",
"args": ["mcpdoc", "--urls", "https://docs.moda.app/llms.txt"]
}
}
}Open Cursor Settings > MCP and add a new server, or add to ~/.cursor/mcp.json:
{
"mcpServers": {
"moda-docs": {
"command": "uvx",
"args": ["mcpdoc", "--urls", "https://docs.moda.app/llms.txt"]
}
}
}Add to your VS Code settings (settings.json):
{
"mcp": {
"servers": {
"moda-docs": {
"command": "uvx",
"args": ["mcpdoc", "--urls", "https://docs.moda.app/llms.txt"]
}
}
}
}You can also access the docs as plain text for any LLM:
- Index: docs.moda.app/llms.txt
- Full docs: docs.moda.app/llms-full.txt
Next steps
- Authentication -- Create and manage API keys
- Webhooks -- Receive notifications when jobs complete