# enso Content API - landing pages

Publish landing pages on enso.bot. Live at `https://www.enso.bot/p/{slug}`.

Base URL: `https://www.enso.bot/api/public/content/pages`
Auth: header `x-api-key: <CONTENT_API_KEY>` on every request.
Content type: `application/json`.

## Endpoints

| Method | Path | What it does |
| --- | --- | --- |
| POST | `/generate` | send a brief, enso writes the copy and a hero visual, saves a draft |
| POST | `/` | create or replace a page from finished blocks |
| GET | `/` | list pages |
| GET | `/{slug}` | read one page |
| PATCH | `/{slug}` | partial edit |
| DELETE | `/{slug}` | unpublish (soft delete) |

## Page object

```json
{
  "slug": "growth-hacks-for-b2b-founders",
  "title": "Growth hacks for B2B founders",
  "description": "One sentence under 160 chars, used for search and social.",
  "og_image_url": "https://... (absolute https only, optional)",
  "cta_label": "Book a call",
  "cta_href": "/book-a-call",
  "noindex": false,
  "is_published": true,
  "blocks": []
}
```

- `slug`: lowercase, hyphens, no slashes, 2-80 chars. Reposting the same slug replaces the page.
- Reserved slugs rejected: `blog`, `research`, `admin`, `api`, `book-a-call`, `p`, `pricing`, `about`.
- `title` max 60 chars, `description` max 160 chars.
- `blocks`: 1-30 blocks, rendered top to bottom.

## Blocks

Send content only. The enso design system styles everything - never send HTML,
CSS, class names, or inline styles. Any markup you send is treated as text.

| Block | Fields |
| --- | --- |
| `hero` | `eyebrow?`, `title` (<=90), `sub?` (<=220), `bullets?` (<=5, each <=100), `ctaLabel?`, `ctaHref?`, `imageUrl?`, `imageAlt?` |
| `logos` | `title?` (enso customer logo strip, images are ours) |
| `stats` | `items[1-4]` of `{ value (<=24), label (<=80) }` |
| `features` | `title?`, `lede?`, `items[1-8]` of `{ title (<=80), body (<=320) }` |
| `steps` | `title?`, `items[1-6]` of `{ title, body }` |
| `quote` | `quote` (<=500), `who?`, `company?` |
| `richtext` | `title?`, `markdown` (<=4000) |
| `faq` | `title?`, `items[1-12]` of `{ q, a }` |
| `leadform` | `title?`, `sub?` (real book-a-call form, work email required) |
| `cta` | `title`, `sub?`, `ctaLabel?`, `ctaHref?` |

Recommended order: `hero -> logos -> stats -> features -> steps -> quote -> faq -> leadform` (or `cta`).

### Layout notes that keep a page looking right

- `hero` without `imageUrl` renders centered; with `imageUrl` it renders as a
  two-column split. Use an absolute https image at roughly 1200x900 or wider.
- Item counts drive the grids. `features` and `steps` look best at 2, 3, 4 or 6
  items; `stats` at 3; other counts still render, just less evenly.
- Links: `ctaHref` must be a site path starting with `/` or an absolute
  `https://` URL. Point at `/book-a-call` unless there is a better landing spot.

## Quality gate (why a POST gets rejected)

The API validates brand and structure before saving. `422 invalid_payload`
returns `issues[]` naming each field to fix. Current rules:

1. The first block must be `hero`, and there must be exactly one hero.
2. The hero `sub` is required and must state the target ICP (who this is for).
3. No em dashes anywhere. Use `-`.
4. Banned phrases: `autonomous business`, `backdoor`, `loophole`, `hack the system`.
5. Every string has a max length - see the tables above.

## Brand and copy rules

- Tagline: "Agentic Growth Lab". Never "autonomous business".
- Say "Growth Hack", never "loophole", "backdoor", or "hack the system".
- Framing: platforms are castles; we discover / find / notice glowing cracks or
  seams in the walls where attention leaks out. Never "we built a crack".
- Sentence-case headlines, concrete numbers over adjectives, no hype vocabulary
  ("revolutionary", "game-changing", "unlock", "supercharge").
- No bylines, author names, or dates - these are landing pages, not blog posts.
- Never reference 2024 as a year; use the current year or no year.
- Every page states one clear ICP in the hero sub or the first feature.

## Responses

- `200` / `201` - `{ "ok": true, "slug", "url", "published" }`
- `401` - `unauthorized` (missing or wrong `x-api-key`)
- `404` - `not_found`
- `422` - `invalid_payload` with `issues[]`

## Examples

Create a page from finished blocks:

```bash
curl -sS -X POST https://www.enso.bot/api/public/content/pages \
  -H "x-api-key: $CONTENT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "slug": "agentic-seo-for-fintech",
    "title": "Agentic SEO for fintech",
    "description": "How fintech growth teams find the seams where buyer attention already leaks.",
    "cta_label": "Book a call",
    "cta_href": "/book-a-call",
    "is_published": true,
    "blocks": [
      { "type": "hero", "eyebrow": "Agentic Growth Lab", "title": "Agentic SEO for fintech",
        "sub": "For fintech growth leads at Series A to C companies selling to SMB finance teams.",
        "bullets": ["Find the seams first", "Build the play with agents", "Run it with your team"],
        "ctaLabel": "Book a call", "ctaHref": "/book-a-call" },
      { "type": "logos" },
      { "type": "stats", "items": [
        { "value": "11x", "label": "qualified signups" },
        { "value": "7 days", "label": "first hack live" },
        { "value": "1", "label": "hack shipped per week" } ] },
      { "type": "leadform", "title": "Talk to the lab" }
    ]
  }'
```

Let enso write the page from a brief (saves as a draft):

```bash
curl -sS -X POST https://www.enso.bot/api/public/content/pages/generate \
  -H "x-api-key: $CONTENT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "slug": "agentic-outbound-for-devtools",
    "topic": "Agentic outbound for devtools",
    "icp": "Heads of growth at 30-200 person devtool companies",
    "offer": "One growth hack shipped per week, run by agents",
    "proofPoints": ["11x qualified signups", "first hack live in 7 days"],
    "ctaHref": "/book-a-call"
  }'
```

Then verify: `curl -sSI https://www.enso.bot/p/agentic-seo-for-fintech`

## Checklist before publishing

- [ ] Hero sub names the ICP
- [ ] No em dashes, no banned phrases
- [ ] 3 stats, 2-4 or 6 features, 3 steps
- [ ] Every `ctaHref` resolves (site path or https URL)
- [ ] `description` under 160 chars
- [ ] Page opened at `/p/{slug}` and checked on mobile width
