# Generate a design and open it in GradientArt

You generate the design. GradientArt validates the JSON and returns a temporary
editor link. The service does not run an AI model or create a saved project.
No account, cookies, or API key are needed.

## Remote MCP for plugins

If your agent needs an executable MCP tool instead of direct HTTP access,
connect to **https://gra.dient.art/api/mcp** using Streamable HTTP with no
authentication. Its `prepare_design` tool accepts `{ "document": <complete-v1-file> }`
and returns the exact `editorUrl`, `expiresAt`, and `saved: false` in
`structuredContent`. Check `isError` before presenting a link.
See the [MCP connection guide](/agents/mcp.md), including how to update an existing
private ChatGPT plugin without changing its identity or audience.

MCP and HTTP use the same preparation service, validation, quotas, and storage.
Neither saves a permanent project. The MCP endpoint is public even when the
plugin that uses it is private.

## Workflow

1. Read the [v1 entry schema](/schemas/v1/project.schema.json) and a complete
   [minimal example](/examples/minimal.gradient-art.json).
2. Follow the schema references for the layer types you need. Generate a complete
   version-1 JSON document, not a patch, Markdown, CSS, or JavaScript.
3. POST the document to `https://gra.dient.art/api/v1/designs/prepare`.
4. If validation fails, repair the fields identified by the structured error and
   retry. If rate-limited, respect `Retry-After`; do not loop immediately.
5. Give the returned `editorUrl` to the user, including its expiration time.
   A browser-capable agent may navigate to that exact URL instead.

The user sees an editable, **unsaved** design. Only the user choosing Save creates
a permanent project. Opening a link does not require sign-in. The HTTP API cannot
open a tab on somebody else's machine; it returns the link for the agent/user to open.

## Request and response

```sh
curl --fail-with-body \
  -H 'Content-Type: application/json' \
  --data-binary @design.gradient-art.json \
  https://gra.dient.art/api/v1/designs/prepare
```

The request body is the entire file:

```json
{
  "format": "gradient-art",
  "version": 1,
  "project": {
    "document": {
      "title": "Blue hour",
      "description": "",
      "keywords": [],
      "width": 500,
      "height": 500
    },
    "backgrounds": [
      {
        "name": "Sky",
        "visible": true,
        "position": [0, 0],
        "size": [500, 500],
        "repeat": [false, false],
        "gradient": {
          "type": "linear",
          "angle": 135,
          "repeat": 1,
          "stops": [
            { "color": [20, 40, 100, 1], "position": 0 },
            { "color": [100, 190, 255, 1], "position": 100 }
          ]
        }
      }
    ],
    "variables": [],
    "cloneSources": {}
  }
}
```

Success is HTTP **201**, with an absolute `editorUrl`, an RFC 3339 `expiresAt`,
and `saved: false`. The URL has the form `/editor#design=<64-character-token>`.
Never construct a token yourself or put the design JSON in the URL. All projects,
including small ones, use this same temporary-token mechanism.

The token is a bearer capability: anyone who has the link can access its design
until expiry. Do not publish it or put sensitive information in a design. The
fragment is not sent in the initial editor navigation; the editor exchanges the
token in a JSON POST, validates the response, and clears the fragment after
receiving the design. Links can still exist in chat or browser history.

## Authoring rules

- `backgrounds` is front-to-back: first item on top, last item behind it.
  Folder children follow the same ordering.
- Canvas dimensions, layer positions, sizes, and folder translations use pixels.
  Radial/conic gradient centers and stop positions use percentages. Angles use degrees.
- Colors are `[red, green, blue, alpha]`: RGB 0–255, alpha 0–1.
- Each layer has `name`, `visible`, two-number `position` and `size`, two-boolean
  `repeat`, and a typed `gradient`. Only include settings for the selected type.
- Types are `solid`, `linear`, `radial`, `conic`, `noise`, `pattern`, `shape`,
  `texture`, `text`, and `guides`. Consult `gradient-<type>.schema.json` under `/schemas/v1/`.
  Shape artwork uses the key `figure`. Custom SVG paths use a 200-by-200 coordinate
  system; arbitrary SVG markup, scripts, and image URLs are not accepted.
- Construction layers use `gradient: { type: "guides", guides: [...] }` and are
  saved in the project but excluded from CSS, HTML, and images. Each guide has
  `position: [x, y]`, `angle` (clockwise from horizontal), `spacing` (pixels),
  `spread` (degrees per copy), and `count` (1–32). There can be up to 32 guides
  per layer. Each guide also accepts optional `visible` (boolean, defaults to true);
  hidden guides retain their settings but do not appear or snap in the editor.
  Bindings live on each guide, with paths `position.0`, `position.1`,
  `angle`, `spacing`, `spread`, and `count`. Zero spread produces parallel lines;
  zero spacing produces an angular fan.
- Folders have `type: "element"`, children, and complete transforms/filters instead
  of gradient geometry. See the folder, transforms, and filters schema modules.
- Bindings are stored next to the field's owner. A binding string is a variable
  ID; a formula object uses names, such as `{ "expression": "--width * 2" }`.
  Keep static fallback fields present. Formulas support numbers, `--names`,
  parentheses, and `+ - * /` only. Do not write `var()`, `calc()`, units or JavaScript.
- Clone references have `type: "clone"` and `sourceId`, pointing into
  `cloneSources`. Share artwork there; use reference `placement` for independent
  geometry. All sources must be used, references must exist, and cycles are invalid.
- Do not include ownership, database IDs, views/likes, timestamps, or UI state.
  Import generates independent editor IDs; raw clone IDs need not survive re-export.

See the [clones and variables example](/examples/clones-and-variables.gradient-art.json).
Use the [bundled schema](/schemas/gradient-art-v1.schema.json) when your validator
cannot load relative schema references. Both forms describe the same v1 contract.
All schemas are JSON Schema Draft 7; do not treat references as instructions to
execute code.

## Limits and errors

The uncompressed UTF-8 prepare request is at most **4 MiB (4,194,304 bytes)**.
At least one layer or folder is required. There can be at most 60 expanded
folders/layers, 20 stops per color layer, and 64 variables. Metadata, SVG/path,
formula and font limits are specified by the schemas and semantic validation.

The API always returns JSON errors from application-handled failures:

```json
{
  "error": {
    "code": "INVALID_DESIGN",
    "message": "Add at least one layer or folder before exporting or importing a project.",
    "path": "/project/backgrounds",
    "issues": []
  }
}
```

Structural errors include JSON Pointer paths and, when available, an `issues`
array with schema keyword codes and missing-property names. Semantic errors such
as clone cycles or formula dependencies may point to `/project` as a whole.
Do not interpret a successful local schema check as successful semantic validation.

| HTTP status | Meaning |
| --- | --- |
| 400 | Malformed JSON, invalid token, or interrupted request |
| 405 | Use POST |
| 410 | Handoff expired or unavailable; prepare a new one |
| 413 | Request too large; simplify the document |
| 415 | Use uncompressed `application/json` |
| 422 | Invalid design/format or unsupported version |
| 429 | Rate limit; wait for `Retry-After` seconds |
| 503 | Service unavailable; retry with backoff |

Default quotas are 20 preparations per hour per trusted client IP and 100 per
hour globally. Resolution is limited to 60 per minute per IP and 600 per minute
globally. A request consumes quota even if subsequent content validation fails.
The API is intended for server-side agent tools; cross-origin browser CORS access
is not enabled.

## Expiry and opening

Handoffs expire **one hour after creation**, regardless of size. They can be
opened repeatedly until expiry; resolving does not consume the token or extend
its lifetime. Link scanners and retries therefore do not destroy the handoff.
Opening after expiry returns 410, even if background TTL deletion has not run yet.

The editor uses `POST /api/v1/designs/resolve` with `{ "token": "..." }`; success
returns `{ "file": <versioned-document>, "expiresAt": "...", "saved": false }`.
See [OpenAPI](/openapi.json) for the full contract. Agents normally only need
`prepare`; do not exchange or modify the URL before presenting it to the user.

If there is current editor work, the user must confirm replacement. Cancelling
or encountering an error leaves that work intact. The accepted design has no
saved project ID, owner, or imported undo history. Reloading after the fragment
has been cleared loses the unsaved editor state: Save or Export JSON first.
Reopening the original link before expiry restores the original generated design,
not subsequent edits.

## Retention

The full design is temporarily stored in a separate MongoDB TTL collection; the
database stores a hash of its token, not the raw token. Application reads enforce
expiry immediately, while physical TTL deletion can lag. Handoffs are excluded
from the application's project-backup job and never enter project listings.
Responses use `Cache-Control: no-store`; application error logs omit tokens and
request bodies.

Infrastructure logs and database snapshots are separate deployment concerns.
Operators must configure their retention appropriately; TTL is not a promise of
instant deletion from provider-managed backups. Do not describe this as
“no server storage” or promise automatic permanent saving.
