# Look sheets

`POST /v1/sheets` — Blend a character's or element's photos into one canonical identity sheet.

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: 20 credits (the Eroq One scene rate)

A **look sheet** is one generated image that merges a subject's reference photos into a multi-angle design sheet. A neutral canvas image pins the size so it never inherits a reference's dimensions, and every image (canvas + references) is resized to the same encoder target — that uniform sizing is what keeps the references driving the ART STYLE; when the references were left at their upload size the big canvas dominated the conditioning and the model fell back to a generic look. Output is ~1056×1568 (2:3) on the default engine. The panels tile the sheet edge to edge (thin separators only): a large front face panel, three head panels (three-quarter, profile, back) and three full-body views (front, side, back). Up to 9 references are blended (the studio caps uploads at the same number, so a 10th photo is refused rather than charged); each must be at least 512px and at most 1024px on the long side (the studio enforces both on upload; API references outside the range are skipped and reported in `warnings`). Identity tightens with the count — 1 is thin, 3 is enough, 4 is recommended, 9 is the maximum — and more references add render time, not sheet size (the output stays at the layout's resolution). A thumbnail is refused because upscaled it carries no facial detail and the model tends to paste it through as a panel. It is the canonical (and only) reference the studio feeds when you cast that subject. The pool is the subject's reference photos only — a character's **avatar is card art and never joins it** (an element's hero photo does: the form declares it a visual reference, and it leads the pool). Every panel holds the same neutral look: the persona's behavioral quirks never leak into the faces.

Like image generation, `POST /v1/sheets` is **asynchronous**: it charges, enqueues the render and answers `202` with one image job. Poll `GET /v1/images/generations/{id}` until `succeeded` (the sheet is in `data[0]`, and its `library_id` is what you apply). Nothing becomes a subject's active sheet until you `POST /v1/sheets/apply` it — every accepted take keeps its history in your creation library.

**The render gate.** Once a character or element has an accepted sheet, `char:<id>` / `el:<id>` references in image and video renders feed that sheet. A subject WITHOUT a sheet cannot appear in a render yet — image and video generations answer `422 sheet_required` before charging. Roleplay/chat is exempt: the persona sheet is enough to talk.

One sheet per subject, and one layout per subject kind — there is no layout to choose: `turnaround` for characters, `hero` for objects, `scene` for locations (the old identity/portrait split is gone; a trained Imprint will replace the sheet later). With zero stored references the sheet is invented from the persona/description alone — pass an explicit `description` to anchor it, or it falls back to the card text. Pass `style` to TRANSFORM the look instead of holding it (3D references into a photoreal sheet); omit it (or `match`) to stay as close to the references as the engine can hold.

## Operations

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/v1/sheets` | Generate a look sheet (async — returns one image job). |
| `POST` | `/v1/sheets/apply` | Accept a finished render as the subject's active sheet. |

## POST /v1/sheets

Generate a look sheet (async — returns one image job).

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `target` | string | yes | `char:<id>` or `el:<id>` — the subject to sheet, in the same id language the `elements` arrays use. |
| `layout` | string | no | Optional; the subject kind already fixes it — `turnaround` (characters), `hero` (objects), `scene` (locations). Omit it: each kind has exactly one sheet. |
| `model` | string | no | The Eroq One scene (`eroq-one`, default — 10 reference slots). It is the only engine offered for sheets: the Krea 2 reference node is tuned for single-pose refs, not multi-panel sheets. |
| `style` | string | no | Per-call look transform: a style id, `custom` (+ `style_custom`), or `match`/omitted to hold the references' look. |
| `style_custom` | string | no | Free-text look, used when `style` is `custom`, up to 120 characters. |
| `description` | string | no | Per-call look description (up to 2000 chars) — used instead of the card text. Required in spirit when the pool is empty. |
| `guidance` | string | no | Free-text extra direction for this take (up to 600 chars) — "longer copper hair", "oil-painting look", a scar. Address one reference as `Picture N` (1-based over the pool — for an element the hero photo is Picture 1, then the newest photos): the render layer binds it to that picture's image slot, so you can steer per-attribute ("keep the hairstyle from Picture 2, the build from Picture 1"). Shapes the look; never overrides the layout or the neutral-pose rules. |
| `nsfw` | boolean | no | Character sheets only: the body-reference panel dress state. `false` (default) renders it in minimal underwear that fully covers the body; `true` renders it fully nude. The panel is otherwise identical (same pose, body, proportions and lighting). |
| `store` | boolean | no | Unlock a permanent public URL for the sheet (2 credits / started 10MB on top). |

### Example request

```bash
curl https://eroq.ai/v1/sheets \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "target": "char:9c1e…",
  "layout": "turnaround"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/sheets', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "target": "char:9c1e…",
    "layout": "turnaround"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/sheets",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "target": "char:9c1e…",
        "layout": "turnaround"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "target": "char:9c1e…",
  "layout": "turnaround"
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/sheets", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "object": "sheet.generation", "target": "char:9c1e…", "layout": "turnaround",
  "model": "eroq-one",
  "job": { "id": "8a2f…", "poll": "/v1/images/generations/8a2f…" },
  "usage": { "credits_spent": 20, "credits_remaining": 1180 }
}
```

## POST /v1/sheets/apply

Accept a finished render as the subject's active sheet.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `target` | string | yes | `char:<id>` or `el:<id>`. |
| `creation_id` | uuid | yes | The Library creation id of the accepted sheet (the completed image job's `data[0].library_id`). |

### Example request

```bash
curl https://eroq.ai/v1/sheets/apply \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "target": "char:9c1e…",
  "creation_id": "4f7a…"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/sheets/apply', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "target": "char:9c1e…",
    "creation_id": "4f7a…"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/sheets/apply",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "target": "char:9c1e…",
        "creation_id": "4f7a…"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "target": "char:9c1e…",
  "creation_id": "4f7a…"
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/sheets/apply", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "object": "sheet.generation", "target": "char:9c1e…", "layout": "turnaround",
  "model": "eroq-one",
  "job": { "id": "8a2f…", "poll": "/v1/images/generations/8a2f…" },
  "usage": { "credits_spent": 20, "credits_remaining": 1180 }
}
```

---
Canonical: https://eroq.ai/docs/sheets · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
