# Agentic PDF Editor — Agent Guide

You are an agent that can produce **beautiful, fully-custom PDF presentations** through this HTTP API.
Upload brand materials, author slides as structured JSON, render, then download the PDF or hand off links.

- **Base URL:** `https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai`
- **Machine-readable spec:** `https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/openapi.json`
- **This guide (raw markdown):** `https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/docs.md`

## Authentication

Agents authenticate with the shared password (`ACCESS_PSW`) on every request except `/`,
`/healthz`, `/docs`, `/docs.md`, `/openapi.json`, and `/api/login`:

- `Authorization: Bearer <ACCESS_PSW>` (preferred)
- `X-Access-Password: <ACCESS_PSW>`

Browsers use `POST /api/login` (`{ "password": "..." }`), which sets an HttpOnly session cookie — so
the password never appears in a URL. Rendered image/PDF URLs are authorized by that cookie (for the
dashboard) or by a short-lived signed token the renderer injects internally; you do not pass the
password as a query parameter.

Missing/incorrect credentials return `401`.

## The 4-step workflow

### 1. Upload materials (optional but recommended)

`POST /assets` — `multipart/form-data`, field `file` (required), field `role` (optional: `logo`,
`background`, `image`, `example`, `other`).

Returns `AssetMeta` including an `id`. Reference that `id` later as `imageAssetId` on a slide, or as
`logoAssetId` on a theme. Fetch bytes back with `GET /assets/{id}`.

```bash
curl -X POST https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/assets \
  -H "Authorization: Bearer $ACCESS_PSW" \
  -F "file=@logo.png" -F "role=logo"
# => { "id": "a1b2...", "role": "logo", ... }
```

### 2. Create a theme or a reusable Brand Kit

**Brand Kit (recommended for consistency)** — a named, reusable identity that reliably reproduces the
same look across every deck. It bundles palette + fonts + logo + named reusable images + layout
defaults + style/voice notes.

- `POST /brand-kits` with `{ "name": "Acme", "slug": "acme", "colors": {...}, "fonts": {...},
  "logoAssetId": "<id>", "images": { "hero": "<assetId>", "icon-cloud": "<assetId>" },
  "layoutDefaults": { "coverLayout": "title", "contentLayout": "title-bullets" },
  "styleNotes": "Confident, minimal, lots of whitespace." }`
- `POST /brand-kits/derive` with `{ "assetIds": ["<image ids>"], "brief": "...", "name": "Acme",
  "logoAssetId": "<id>" }` — derives palette + fonts from the materials via LLM vision.
- `GET /brand-kits`, `GET /brand-kits/{idOrSlug}`, `PATCH /brand-kits/{id}`.

Then reference it on any deck with `brandKitId` or `brandKitSlug` — the render uses the kit's palette,
fonts, and logo automatically. This is the reliable way to get the same style every time.

**Theme (one-off)** — `POST /themes/derive` (from materials) or `POST /themes` (explicit tokens).
Both return a `Theme` with an `id` usable as a deck's `themeId`.

### 3. Create a deck

`POST /decks` with `{ "title": "...", "themeId": "<optional>", "slides": [ Slide, ... ] }`.

Each **Slide** is:

```json
{
  "layout": "title | title-bullets | two-column | image-full | image-right | quote | section | blank",
  "blocks": {
    "title": "string",
    "subtitle": "string",
    "bullets": ["string", "..."],
    "body": "string",
    "columnLeft": "string",
    "columnRight": "string",
    "quote": "string",
    "attribution": "string",
    "imageAssetId": "asset id from POST /assets",
    "imageUrl": "https://... (alternative to imageAssetId)",
    "notes": "speaker notes"
  },
  "rawHtml": "OPTIONAL full HTML override for this slide body",
  "themeOverrides": { "background": "#000000" }
}
```

**Layout cheat-sheet** (which blocks each layout uses):

- `title` — cover slide: `title`, `subtitle` (+ theme logo if set).
- `section` — section divider: `title`, `subtitle`.
- `title-bullets` — heading + `bullets` and/or `body`/`subtitle`.
- `two-column` — `title`, `columnLeft`, `columnRight`.
- `image-right` — `title`, `body`/`bullets` on the left, `imageAssetId`/`imageUrl` on the right.
- `image-full` — full-bleed `imageAssetId`/`imageUrl` with optional `title`/`subtitle` overlay.
- `quote` — big `quote` + `attribution`.
- `blank` — just `body`.

**Escape hatch:** set `rawHtml` to take full control of a slide's body. It still inherits the theme's
CSS variables (`--primary`, `--accent`, `--text`, `--font-head`, etc.) so it stays on-brand.

You can later replace a whole deck with `PUT /decks/{id}` or edit one slide with
`PATCH /decks/{id}/slides/{slideId}`.

### 4. Render, then consume

`POST /decks/{id}/render` — renders every slide to a PNG and the whole deck to a PDF (via headless
Chromium), stores them, and returns links:

```json
{
  "status": "ready",
  "deckId": "...",
  "slideCount": 5,
  "pdf": "https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/decks/<id>/pdf",
  "view": "https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/decks/<id>/view",
  "slideImages": ["https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/decks/<id>/slides/1.png", "..."]
}
```

Then:

- **`GET /decks/{id}/pdf`** — download the final PDF (this is your deliverable / download link).
- **`GET /decks/{id}/slides/{n}.png`** — per-slide image (1-indexed). **Fetch these and look at them**
  to visually verify and critique the design, then edit slides and re-render until it looks great.
- **`GET /decks/{id}/view`** — a plain, non-SPA HTML page of the whole deck. Ideal for browser
  automation to observe without running any client JS.

## Two fast paths (in addition to hand-authoring slides)

### Generate a whole deck from a brief

`POST /decks/generate` with `{ "brief": "outline or prompt", "brandKitSlug": "acme",
"slideCount": 8, "model": "claude-sonnet-5" }`. The service uses the LLM (guided by the brand kit's
style notes + named images) to author a full themed deck, then renders it and returns
`{ deck, pdf, view, slideImages }`. Pass `"render": false` to skip rendering.

### Edit a deck by natural language (chat)

`POST /decks/{id}/chat` with `{ "message": "remove the pricing slide and tighten the intro",
"model": "claude-sonnet-5", "history": [] }`. This runs a bounded agent loop with deck-editing tools
(list/get/update/add/remove/reorder slides, set title), applies the edits, re-renders, and returns
`{ summary, changed, slideCount, deck, pdf, slideImages }`. Keep passing prior turns back in
`history` for multi-turn edits. This is exactly what the dashboard's chat editor uses.

Available chat model ids come from `GET /api/models` (default `claude-sonnet-5`).

## Recommended agent loop for great design

1. Upload the logo + any brand/example images.
2. `POST /themes/derive` from those to get an on-brand palette + fonts.
3. Create the deck with clear, concise slides (prefer `title`, `section`, `title-bullets`, `image-right`).
4. Render.
5. **Fetch the slide PNGs and inspect them.** If anything looks off (overflow, contrast, emphasis),
   `PATCH` the offending slides or tweak the theme, then re-render.
6. Return the `/pdf` link.

## End-to-end example

```bash
BASE=https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai
AUTH="Authorization: Bearer $ACCESS_PSW"

# 1. deck with a couple of slides
DECK=$(curl -s -X POST $BASE/decks -H "$AUTH" -H 'Content-Type: application/json' -d '{
  "title": "Q3 Business Review",
  "slides": [
    { "layout": "title", "blocks": { "title": "Q3 Business Review", "subtitle": "Prepared by the agent" } },
    { "layout": "title-bullets", "blocks": { "title": "Highlights", "bullets": ["Revenue up 24%", "Churn down to 1.8%", "Two new markets"] } },
    { "layout": "quote", "blocks": { "quote": "Best quarter yet.", "attribution": "The CEO" } }
  ]
}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

# 2. render
curl -s -X POST $BASE/decks/$DECK/render -H "$AUTH"

# 3. inspect + download (agents use the Authorization header, not a query param)
curl -s "$BASE/decks/$DECK/slides/1.png" -H "$AUTH" -o slide1.png
curl -s "$BASE/decks/$DECK/pdf" -H "$AUTH" -o deck.pdf
```

## Notes

- Storage is persistent (S3); decks and assets survive restarts and redeploys.
- Slides render at 1280x720 (16:9). Keep text concise so it fits comfortably.
- The dashboard at `https://5581577a-ac59-45cb-ba81-18636e949179.hosted-with.matagi.ai/` lists and previews all rendered decks (enter the password once).
