---
name: lutrix
description: >-
  Create, edit, export and batch-generate images (data-merge) on Lutrix — a
  Canva-style design platform built for agents — via its REST API or MCP server.
  Use when the user wants to build images/social posts programmatically from a
  design document (JSON), fill a template with data to produce many pieces at once,
  export PNG/JPG/SVG/PDF, generate images with AI, remove backgrounds, or manage
  design projects. Authentication is a per-organization API key sent in the
  X-Api-Key header.
license: MIT
metadata:
  homepage: https://lutrix.app
  docs: https://lutrix.app/docs
---

# Lutrix — design creation via API/MCP

Lutrix is an **API/MCP-first** design platform (like Canva, but built for AI
agents). Anything the visual editor does can also be done over HTTP.

## Mental model
- Hierarchy: **Organization → Workspaces → Projects**.
- A **project** holds a **design document** (JSON): one or more **pages**, each a
  canvas with a list of **nodes** (text, shapes, images, vectors).
- From a project you **export** an image (PNG/JPG/SVG/PDF) **or** use it as a
  **template** and **data-merge**: one image per data row.
- Everything is isolated per organization (multi-tenant). An API key only sees its own org.

## Recommended flow when creating with AI
1. **Consult the brand** — `lutrix_get_brand_kit` (or GET /workspaces/:id/brand-kit/brief): base colors, fonts and voice on the client's identity. To **set up/update** the kit (Studio plan), use `lutrix_set_brand_kit` — logos need an `assetKey` (upload the image first).
2. **Build the design** — `lutrix_set_project_doc`.
3. **Show the preview** — `lutrix_render_preview`: returns the image (stamped "Prévia"), **free**. **Show it to the user** to approve before spending a credit.
4. **Measure** — `GET .../measure` (free): the REAL box of every node. This is the step that catches text that didn't fit (wrapped or overflowed) and overlapping elements — things you cannot predict while writing the doc, because they depend on the loaded font and tracking. Fix using the numbers, then measure again.
5. **Audit** — `lutrix_preflight` (free): fix the `warn`s (off-canvas, low contrast, color/font off the brand kit).
6. **Only then export** — `lutrix_export_png`/exports: export is the **only** step that costs **1 credit per piece**.

> Preview, measure and preflight are **free** — iterate freely; you're only charged on the approved final export. AI image generation costs a credit per attempt (the prompt is the client's).

## Setup
- **Base URL:** `https://lutrix-api-739d.onrender.com`
- **Auth:** send the header `X-Api-Key: lutrix_sk_...` on EVERY request. Create a key
  in the app at `https://lutrix.app/app` (*API Keys* panel) — it is shown **only once**. Missing /
  invalid key → `401`.
- Bodies are JSON (`Content-Type: application/json`), except file uploads
  (`multipart/form-data`).

## Quickstart (create → fill → export)
```bash
API="https://lutrix-api-739d.onrender.com"; KEY="lutrix_sk_..."
# 1. pick an existing workspace
WS=$(curl -s $API/workspaces -H "X-Api-Key: $KEY" | jq -r '.[0].id')
# 2. create a 1080x1080 project
PID=$(curl -s -X POST $API/workspaces/$WS/projects \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"name":"Promo","width":1080,"height":1080}' | jq -r '.id')
# 3. set the design document (text with a {{name}} field)
curl -s -X PUT $API/workspaces/$WS/projects/$PID/doc \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{
    "schemaVersion":1,"width":1080,"height":1080,"background":"#0f172a",
    "nodes":[{"type":"text","id":"t1","x":80,"y":420,"width":920,"height":240,
      "text":"Hello {{name}}","fontSize":96,"fill":"#ffffff","align":"center"}]}'
# 4a. export a PNG (1 credit) → returns a signed URL
curl -s -X POST $API/workspaces/$WS/projects/$PID/export -H "X-Api-Key: $KEY"
# 4b. OR generate many pieces at once (1 credit per row)
curl -s -X POST $API/workspaces/$WS/projects/$PID/merge \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"rows":[{"name":"Ana"},{"name":"Bruno"}]}'
```

## Endpoints (all require X-Api-Key)

### Workspaces
- `GET  /workspaces` — list
- `POST /workspaces` — `{ name }` (min 2 chars)
- `GET  /workspaces/role` — your role in the org
- `DELETE /workspaces/:wid` — delete + cascade projects (admin)

### Projects
- `GET  /workspaces/:wid/projects` — list (id, name, tags, size, updatedAt)
- `POST /workspaces/:wid/projects` — `{ name, width?, height?, background?, tags? }`
- `GET  /workspaces/:wid/projects/:pid` — includes the design document (`doc`)
- `PATCH /workspaces/:wid/projects/:pid` — `{ name?, tags? }`
- `DELETE /workspaces/:wid/projects/:pid` — delete
- `PUT  /workspaces/:wid/projects/:pid/doc` — replace the doc (body = document)
- `POST /workspaces/:wid/projects/:pid/export` — render PNG → `{ url }` (1 credit)
- `POST /workspaces/:wid/projects/:pid/exports` — `{ format:"png|jpg|svg|pdf", pageIds? }` → `{ url }` or `{ files:[...] }` (1 credit/page)
- `GET  /workspaces/:wid/projects/:pid/fields` — template `{{...}}` fields → `{ fields:[] }`
- `POST /workspaces/:wid/projects/:pid/merge` — `{ rows:[{ field:value }] }` → `{ results:[{ index, url }] }` (1 credit/row)
- `POST /workspaces/:wid/projects/:pid/preview` — low-res preview (stamped "Prévia") → `{ url }` — FREE. Show it to the user before exporting.
- `GET  /workspaces/:wid/projects/:pid/preflight` — audit the design before exporting → `{ ok, checks:[{ level, code, message, nodeId? }] }` — FREE
- `GET  /workspaces/:wid/projects/:pid/measure?pageId=` — measures the page with the DRAWING ENGINE → `{ pageId, width, height, boxes:[{ id, type, x, y, width, height, lines?, overflow? }], warnings:[{ kind, nodeId, otherId?, message }] }` — FREE.
  A text's final box depends on the loaded font, tracking and width wrapping — you can't predict it by counting characters. Ask for the measurement, fix with numbers, then export. `kind`: `overflow` (text didn't fit: wrapped into more lines or overflowed), `collision` (two texts overlap), `offcanvas` (left the page).

### Brand kit
- `GET  /workspaces/:wid/brand-kit` — workspace brand kit (colors/fonts/logos/voice)
- `PUT  /workspaces/:wid/brand-kit` — `{ colors, fonts, logos, voice }` (Estúdio feature → 402 on Ateliê)
- `GET  /workspaces/:wid/brand-kit/brief` — AI-readable brief (palette, fonts, voice + ready-to-use text)

### Assets & AI
- `POST /assets` — multipart upload (png/jpg/webp/gif/svg) → `{ assetKey, src }`
- `POST /assets/from-url` — `{ url }` → `{ assetKey, src }`
- `GET  /assets/ai/models` — AI models + credit cost per size
- `POST /assets/generate` — `{ model, prompt, width, height, inputImageUrl? }` → `{ assetKey, src }`
- `POST /assets/remove-background` — `{ imageUrl }` → `{ assetKey, src }` (5 credits)
- `GET  /assets/stock/photos?source=unsplash|pexels&q=` — search stock photos
- `GET  /assets/stock/icons?q=` — search icons (Iconify)
- `POST /assets/stock/pick` — `{ source, url, downloadLocation? }` — save a stock item as an asset
- `GET|POST /assets/folders`, `DELETE /assets/folders/:id` — library folders
- `GET /assets/library`, `POST /assets/library` (multipart), `PATCH /assets/library/:id` `{ folderId }`, `DELETE /assets/library/:id`
- `GET|POST /fonts` (multipart ttf/otf/woff/woff2), `DELETE /fonts/:id`

### Account
- `GET /credits` → `{ balance }`
- `GET /templates` → ready-made design documents (public)
- `GET|POST /api-keys`, `DELETE /api-keys/:id` (admin)

## Design document (schema)
`PUT .../doc` accepts two forms (both normalized to v2 on read):

**v1 (single page):**
```json
{ "schemaVersion":1, "width":1080, "height":1080, "background":"#ffffff", "nodes":[ ... ] }
```
**v2 (multi-page):**
```json
{ "schemaVersion":2, "layout":"strip", "pages":[
  { "id":"p1", "width":1080, "height":1080, "background":"#ffffff", "x":0, "y":0, "nodes":[ ... ] }
]}
```

**Fields common to every node:** `id` (string, required), `x`, `y`, `width`,
`height` (numbers), `rotation?`, `opacity?` (0–1), `name?`, `hidden?`, `locked?`,
shadow (`shadowColor?`, `shadowBlur?`, `shadowOffsetX?`, ...), `blur?`.

### Assisted layout (skip the position arithmetic)
Building a piece over the API means computing `x`/`y` for every element — and that's where the
classic defects come from: four different starting points on the same page, and elements colliding
because the text above grew. Two shortcuts avoid it:

- **`page.columns`** (array of `x` in px) + **`col`** on the node → the node gets `x = columns[col]`.
- **`stackAfter`** (another node's id) + **`stackGap?`** (px, default 24) → the node's `y` becomes that
  node's bottom + the gap. **The height used is the real one** — if the text above wraps into two
  lines, the one below moves down with it.

Both are resolved **at save time**: the stored document comes back with absolute `x`/`y` and without
these props. They are writing sugar, not state — changing `columns` later won't reposition what was saved.

**Node types:**
- `text` — `text`, `fontSize?`, `fontFamily?`, `fontStyle?` (normal|bold|italic|italic bold),
  `fill?`, `align?` (left|center|right), `lineHeight?`, `wrap?` (`word` = wraps on words, the default;
  `none` = never wraps and overflows the box). Use `{{field}}` inside `text` for data-merge.
- `rect` — `fill`, `fillGradient?`, `cornerRadius?`, `stroke?`, `strokeWidth?`.
- `ellipse` / `triangle` — `fill`, `fillGradient?`, `stroke?`, `strokeWidth?`.
- `path` — `d` (SVG path in viewBox coords), `viewWidth`, `viewHeight`, `fill`, `stroke?`,
  `strokeWidth?`. The renderer scales `d` to `width`×`height`.
- `image` — `assetKey` (from an upload/generate/from-url call), `src` (signed URL), `crop?`,
  `flipX?`, `flipY?`, `field?` (data-merge: each row supplies this image's assetKey).
- `draw` — freehand: `points` (flat `[x0,y0,...]`), `stroke`, `strokeWidth`, `tension?`, `lineCap?`.

**`background` and `fillGradient`** can be a hex string OR
`{ "type":"linear", "angle":90, "stops":[{ "color":"#fff", "at":0 }, ...] }`
or `{ "type":"radial", "stops":[...] }`.

## Data-merge (batch generation)
Put `{{field}}` markers in text nodes (or `field` on an image node).
`GET .../fields` lists the markers found. `POST .../merge` with `rows` returns
one rendered image per row.

## AI images
- `GET /assets/ai/models` returns models (text→image and edit) with the credit cost
  per size. Cheapest: `flux-schnell`.
- Text→image: `POST /assets/generate { model, prompt, width, height }`.
- Image edit (`mode: "edit"` models): also pass `inputImageUrl`.
- Background removal: `POST /assets/remove-background { imageUrl }`.
All return `{ assetKey, src }` — use them in an `image` node.

## Credits
export 1 · exports 1/page · merge 1/piece · remove background 5 · generate = per
model/size (see `/assets/ai/models`) · file import 2. Every new org starts with
**200**. Balance at `GET /credits`.

## MCP (for MCP-capable agents)
Run the Lutrix MCP server (stdio) and configure your client:
```json
{
  "mcpServers": {
    "lutrix": {
      "command": "node",
      "args": ["/caminho/lutrix-mcp-server/dist/index.js"],
      "env": {
        "LUTRIX_API_URL": "https://lutrix-api-739d.onrender.com",
        "LUTRIX_API_KEY": "lutrix_sk_..."
      }
    }
  }
}
```
**15 tools:** lutrix_list_workspaces, lutrix_create_workspace, lutrix_list_projects, lutrix_create_project, lutrix_update_project, lutrix_get_project, lutrix_get_brand_kit, lutrix_set_brand_kit, lutrix_set_project_doc, lutrix_render_preview, lutrix_preflight, lutrix_export_png, lutrix_list_template_fields, lutrix_merge, lutrix_upload_asset_from_url.

## Rules for agents
- Always send `X-Api-Key`. **Never** expose the key in client-side code or logs.
- Reuse an existing workspace (`GET /workspaces`) instead of creating new ones needlessly.
- After `generate`/`upload`/`from-url`, use the returned `assetKey` **and** `src` in an
  `image` node; `src` is re-signed on the project `GET` and on export.
- Credit-spending calls (export, exports, merge, generate, remove-background) cost
  real credits — **confirm with the user** before large batches.
