# Editing Anticode projects as a coding agent

Anticode projects live in one open, documented file: `anticode.json`. You can read it, edit it and write it back. The builder canvas notices the change within about 15 seconds and redraws the page live. The person sees "Updated from outside Anticode" and can undo your change.

- Schema: https://anticode.dev/schema/project.v1.json (JSON Schema 2020-12)
- Format id: `anticode.project/v1`

## Authenticate

Use a personal access token from the Anticode CLI. It is created once by `anticode login`, which uses a device-code sign-in.

```
Authorization: Bearer ac_pat_…
```

Tokens can read and write projects, collections and entries, sites and builds. They can't touch the account, the password or billing. Never put a token in a file you commit.

If you are an assistant that speaks MCP (Claude, ChatGPT, Cursor and others), you don't need this file directly: connect to the MCP server at `https://anticode.dev/mcp`, which has tools for projects, quoted builds, `anticode.json`, previews and publishing. Setup: https://anticode.dev/mcp

## Read, edit, write

1. **Read** with `GET /api/projects/{id}/format`. The response is the file. Its `version` field and the `ETag: "v{n}"` header both carry the current version.
2. **Edit** the JSON. Change only what you need, and keep every key the schema allows. Unknown keys are refused.
3. **Write** with `PUT /api/projects/{id}/format`. Send the whole file, with the `version` you read (or `If-Match: "v{n}"`).
   - `200 {"ok":true,"version":n+1}` means it's saved, and the open builder will pick it up.
   - `409 version_conflict` means someone changed the project after you read it. The response includes `project`, the current copy. Re-apply your edit to that copy and try again. Never overwrite blindly.
   - `400 invalid_input` means the file doesn't match the schema. `issues` lists the fields to fix.
   - `422 unsafe_html` means an installed block failed the security gate.

List projects with `GET /api/projects` (each one carries `version`).

## Version history and undo

Every save is kept, yours included, so a bad edit is never final. Free keeps the latest 50 versions of each project and Pro keeps 200.

- `GET /api/projects/{id}/versions` lists them, newest first: `{ current, keep, versions: [{ version, source, created, size }] }`. `source` is `builder`, `agent`, `rescue`, `cli`, `mcp` or `restore`. Your token's saves show as `cli`.
- `GET /api/projects/{id}/versions/{v}` returns that version's `spec`.
- `POST /api/projects/{id}/versions/{v}/restore` with `{ "version": current }` (or `If-Match: "v{current}"`) saves version `v` again as a new version. It never deletes anything. A stale `current` gets `409 version_conflict`, just like a write. Leaving the base out gets `428 version_required`.

To undo your own change, restore the version before it rather than writing the old file back by hand.

## What to edit

| You want to… | Edit |
|---|---|
| Change a headline, button or any other text | `sections.text["hero.h1"]`, `hero.lede`, `hero.cta`, `unit.h`, `items.0.name`, `about.1.p`, `faq.2.a`, `nav.pricing`… |
| Reorder sections | `sections.order`: section ids between the header and the footer, e.g. `["hero","shop","about","pricing","contact"]` |
| Split the site into separate pages | `pages`: e.g. `["work","services","about","contact"]`. Each is published at its own address (`/about/`) and opens with the section of that id; the home page keeps the hero and the rest. `[]` puts everything back on one page |
| Rearrange, resize, pin or layer elements inside a section | `sections.layout`: `frames["services"] = { "mode": "stack", "dir": "row" }`, `items["services.head"] = { "basis": 30, "min": 200, "sticky": true }`, `items["hero.facts"] = { "over": "hero.photo", "place": "bottom-start" }`. Constraints only, never pixel positions, so the page still fits every screen |
| Switch dark or light, change the accent, make the hero bolder | `theme.mode`, `theme.accent`, `theme.scale` |
| Tune individual colours | `theme.tokens` (`a` action, `g` ground, `t` text…): hex or numeric `rgb()` only |
| Add or remove pricing, FAQ, reviews, contact, booking, sign-in | `features` |
| Rewrite the copy wholesale | `content` (the same shape the model writes) |
| Change the design system | `direction`: `editorial`, `technical`, `luxury`, `playful` or `brutalist` |
| Show CMS data on the page | `data` (see below) |

Text is plain text. It is always escaped, so HTML in it shows up literally. You never write markup, scripts or styles. Anticode's renderer applies the design system and runs its security and design checks on the page that ships.

## Data (the headless CMS)

Collections hold typed entries (`text`, `richtext` as Markdown, `number`, `date` as YYYY-MM-DD, `image` as an https URL, and `reference` to another collection).

- `GET/POST /api/projects/{id}/collections`, and `PATCH/DELETE …/collections/{slug}`
- `GET/POST …/collections/{slug}/entries`, and `PATCH/DELETE …/entries/{entryId}`. The body is `{ "data": {…}, "slug"?, "title"?, "description"? }`; title and description are the SEO title and meta description.
- `POST …/collections/{slug}/import` with `{ "csv": "…" }`. Header names match field keys or labels; `slug`, `seo title` and `meta description` are special columns.
- `PUT …/collections/{slug}/source` and `POST …/sync` pull from an https JSON or GraphQL endpoint (Sanity, Contentful and others). The fetch runs server-side.

To show a collection on the page, add a binding to `data` and its id to `sections.order`:

```json
{ "id": "cms-journal", "collection": "journal", "layout": "cards", "title": "From the journal", "limit": 6,
  "fields": { "title": "title", "text": "body", "image": "cover", "meta": "published" } }
```

Every entry also gets its own page at `/{collection}/{slug}` on the published site, and appears in `sitemap.xml`. Free projects hold 100 entries and Pro projects hold 10,000.

## Rules

- Read before you write, and handle 409.
- Keep the edit small and say what you changed. The person can undo it from the builder.
- Don't invent keys, and don't put HTML, scripts, secrets or personal data in text fields.
- Publishing (`POST /api/sites`) is a separate, deliberate step. Don't publish unless you were asked to.
