# POST /v1/render

> An answer you already have, drawn again with no model.

## Keys

Your MorphScreen key in `X-MorphScreen-Key`: the call counts against your plan.

## Example

Draw an answer you already have as HTML, with no model called:
```bash
curl https://api.morphscreen.com/v1/render \
  -H "X-MorphScreen-Key: $MORPHSCREEN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "## Budget\n\n<Stat label=\"Per day\" value=\"$165\" />\n"}'
```

The HTML needs no JavaScript and no style attributes, so it passes a strict content security policy. Load `morphscreen.css` from the web SDK on the page that shows it ([HTML without JavaScript](/docs/integrations/html)).

## Request

### RenderRequest

An answer you already have, in our format, with the options. output: html (the default), ops or tree. Free, counted, within a fair-use limit.

| Field | Type | Required | What it is |
|---|---|---|---|
| `text` | string | yes |  |
| `packs` | list of string |  |  |
| `components` | list of object |  |  |
| `image_hosts` | list of string |  |  |
| `images` | list of object |  |  At most 200. |
| `photos` | one of the shapes below |  | The app's own photo key, for one provider: with it the model may ask for photos, searched there on the app's quota, each loading from the provider's own address with its credit. Without one (or false) the model isn't asked for photos. |
| `output` | `html`, `ops`, `tree` |  |  |

### AppImage

One of your own pictures. The model shows it as ![alt](img:ID) and the API puts its URL and credit in; its host is allowed with it.

| Field | Type | Required | What it is |
|---|---|---|---|
| `id` | string | yes | Letters, digits, '-', '_' or '.'. |
| `url` | string | yes | Where it loads from, http(s). |
| `alt` | string |  | What it shows, told to the model. |
| `credit` | string |  | Shown under it, as written (Markdown links allowed). |

## Responses

| Status | What comes back |
|---|---|
| 200 | The answer as HTML, ops or the tree, with the core's verdict. |
| 400 | The request can't be run as written; model_needs_your_key: a model other than the included one, asked for without your own OpenRouter key. |
| 401 | No key, or one turned down: your MorphScreen key (morphscreen_key_required, morphscreen_key_invalid) or your OpenRouter key (openrouter_key_required, openrouter_key_invalid). |
| 403 | The account is suspended (account_suspended), or the model's moderation refused the request (model_refused). |
| 413 | The body is over 1 MB (payload_too_large), or a call with the model included has more than about 8,000 tokens of conversation (conversation_too_long). |
| 429 | A limit reached, with <span class="nb">Retry-After</span> saying how long to wait: requests a minute (rate_limited), answers at once (too_many_streams), the free tier's day (daily_limit_reached), renders this month (render_limit_reached), or OpenRouter's own (openrouter_rate_limited). |
| 503 | Accounts can't be read just now; try again. |

Errors come back as `{"error": {"code", "message", "type", "metadata": {"error_type", "reason"}}}`: `type` is the reason to act on (`rate_limited`, `plan_quota_reached`...). [Limits and errors](/docs/production/limits) says what to do about each.
