> ## Documentation Index
> Fetch the complete documentation index at: https://docs.allcodex.allmaker.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Portal API Proxy

> Reference for the AllCodex Portal Next.js API proxy routes, covering credential flow, ETAPI forwarding, AllKnower endpoints, and zero-login provisioning.

The Portal acts as a Backend for Frontend (BFF) proxy. The browser does not communicate directly with the database (AllCodex Core) or the parsing brain (AllKnower). It sends requests to `/api/*` on the Portal, which parses authentication cookies server-side and forwards requests to private backends.

***

## Credential flow

All backend calls resolve credentials from HTTP-only cookies via `getEtapiCreds()` and `getAkCreds()` (defined in `lib/get-creds.ts`). If cookies are not set, the route falls back to environment variables in `.env.local`. The browser does not receive tokens.

```
Browser  →  POST /api/auth/login or /api/config/allknower-login  (AllKnower: email/password → Bearer token)
         →  HTTP-only cookies set (allknower_url, allknower_token)
         →  POST /api/integrations/allcodex/connect  (Core: password → ETAPI token,
              stored as the user's encrypted UserIntegration inside AllKnower)
         →  Subsequent /api/* calls read the AllKnower token from cookies; Core creds
              are resolved server-side per-user by AllKnower
```

<Warning>
  **Zero-login auto-provisioning**: In dev/zero-login deployments, the auto-provisioning middleware sets the AllKnower token cookie, and Core credentials are bootstrapped into the default user's `UserIntegration` — so neither step above is required for the first user.
</Warning>

***

## Lore CRUD

These routes proxy requests directly to the AllCodex Core ETAPI.

| Method   | Path             | Description                                                                           | Backend        |
| :------- | :--------------- | :------------------------------------------------------------------------------------ | :------------- |
| `GET`    | `/api/lore`      | Search lore notes. Default: `?q=#lore` (all lore). Supports full ETAPI search syntax. | AllCodex ETAPI |
| `POST`   | `/api/lore`      | Create a new lore note.                                                               | AllCodex ETAPI |
| `GET`    | `/api/lore/[id]` | Get note metadata + attributes.                                                       | AllCodex ETAPI |
| `PATCH`  | `/api/lore/[id]` | Update note properties (title, type, mime).                                           | AllCodex ETAPI |
| `DELETE` | `/api/lore/[id]` | Delete a note. Returns 204.                                                           | AllCodex ETAPI |

### POST `/api/lore` — create note example

```json theme={null}
{
  "title": "Lord Ashvane",
  "type": "text",
  "mime": "text/html",
  "loreType": "character",
  "templateId": "_template_character",
  "parentNoteId": "root",
  "attributes": { "fullName": "Lord Ashvane of Solara", "race": "Human" }
}
```

***

## Note attributes and relationships

| Method   | Path                                    | Description                                                              | Backend                    |
| :------- | :-------------------------------------- | :----------------------------------------------------------------------- | :------------------------- |
| `POST`   | `/api/lore/[id]/attributes`             | Create a label or relation attribute.                                    | AllCodex ETAPI             |
| `DELETE` | `/api/lore/[id]/attributes?attrId=<ID>` | Delete an attribute by ID. Returns 204.                                  | AllCodex ETAPI             |
| `POST`   | `/api/lore/[id]/relationships`          | Get existing relations + automatically suggested connections for a note. | AllKnower + AllCodex ETAPI |

### POST `/api/lore/[id]/attributes` — create label

```json theme={null}
{ "type": "label", "name": "fullName", "value": "Lord Ashvane" }
```

### POST `/api/lore/[id]/attributes` — create relation

```json theme={null}
{ "type": "relation", "name": "~belongsTo", "value": "<targetNoteId>" }
```

***

## Note content, revisions, and media

| Method | Path                                       | Description                                                                                                                           | Backend               |
| :----- | :----------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | :-------------------- |
| `GET`  | `/api/lore/[id]/content`                   | Get the HTML content of a note. Returns `text/html`.                                                                                  | AllCodex ETAPI        |
| `PUT`  | `/api/lore/[id]/content`                   | Update the HTML content of a note. Body: raw HTML string. Returns 204.                                                                | AllCodex ETAPI        |
| `GET`  | `/api/lore/[id]/preview?mode=gm\|player`   | Get sanitized HTML content. `gm` mode uses `sanitizeLoreHtml()`, `player` mode uses `sanitizePlayerView()` (strips `gmOnly` content). | AllCodex ETAPI        |
| `GET`  | `/api/lore/[id]/image`                     | Proxy image binary from AllCodex. Cached 1 day.                                                                                       | AllCodex ETAPI        |
| `GET`  | `/api/images/[id]/[filename]`              | Redirect compatibility route for editor image URLs. Forwards to `/api/lore/[id]/image`.                                               | None (local redirect) |
| `GET`  | `/api/lore/[id]/backlinks`                 | Get notes that link to this note (inbound relations + content links).                                                                 | AllCodex ETAPI        |
| `GET`  | `/api/lore/[id]/breadcrumbs`               | Get the ancestor branch path for breadcrumb navigation.                                                                               | AllCodex ETAPI        |
| `GET`  | `/api/lore/[id]/revisions`                 | List a note's revision history.                                                                                                       | AllCodex ETAPI        |
| `GET`  | `/api/lore/[id]/revisions/[revId]/content` | Get the content of a specific revision.                                                                                               | AllCodex ETAPI        |
| `GET`  | `/api/lore/[id]/graph`                     | Get the relationship graph (existing relations + traversal) for a note.                                                               | AllKnower             |
| `GET`  | `/api/lore/[id]/relationship-history`      | Get the applied-relationship history for a note.                                                                                      | AllKnower             |
| `GET`  | `/api/lore/[id]/map`                       | Build map data for a note: map image URL, dimensions, and geolocation pins derived from child notes.                                  | AllCodex ETAPI        |
| `POST` | `/api/lore/[id]/map/upload`                | Upload a map image for a note.                                                                                                        | AllCodex ETAPI        |

***

## Lore tree operations

| Method | Path                     | Description                                                                        | Backend        |
| :----- | :----------------------- | :--------------------------------------------------------------------------------- | :------------- |
| `POST` | `/api/lore/move`         | Move a note to a different parent branch.                                          | AllCodex ETAPI |
| `POST` | `/api/lore/upload-image` | Upload an image file and create an AllCodex image note. Returns `{ url, noteId }`. | AllCodex ETAPI |

### POST `/api/lore/move`

```json theme={null}
{ "noteId": "<noteId>", "newParentId": "<parentId>", "index": 0 }
```

### POST `/api/lore/upload-image`

Send binary image body. Set `x-vercel-filename` header for the file name.

***

## Search and discovery

| Method | Path                                         | Description                                                                               | Backend                     |
| :----- | :------------------------------------------- | :---------------------------------------------------------------------------------------- | :-------------------------- |
| `GET`  | `/api/lore/mention-search?q=<text>`          | Autocomplete for `@`-mentions. Min 2 chars. Returns up to 8 results.                      | AllCodex ETAPI + AllKnower  |
| `GET`  | `/api/lore/note-search?q=<text>&type=<type>` | Title/type note search for picker-style UI. Min 2 chars. Returns up to 12 results.        | AllCodex ETAPI              |
| `POST` | `/api/lore/autolink`                         | Scan a text block for lore title matches. Cached 60s.                                     | AllCodex ETAPI              |
| `GET`  | `/api/search?q=<query>&mode=etapi\|rag`      | Dual-mode search. `etapi` = ETAPI full-text/attribute, `rag` = AllKnower semantic search. | AllCodex ETAPI or AllKnower |

### POST `/api/lore/autolink`

```json theme={null}
{ "text": "Lord Ashvane marched to Solara with the Blackthorn Company." }
```

Returns:

```json theme={null}
{ "matches": [
  { "term": "Lord Ashvane", "noteId": "abc123", "title": "Lord Ashvane" },
  { "term": "Solara", "noteId": "def456", "title": "Solara" }
] }
```

***

## Brain dump

| Method   | Path                              | Description                                                            | Backend   |
| :------- | :-------------------------------- | :--------------------------------------------------------------------- | :-------- |
| `POST`   | `/api/brain-dump`                 | Process raw text through the extraction pipeline.                      | AllKnower |
| `POST`   | `/api/brain-dump/stream`          | SSE-streamed auto brain dump (progressive entity cards, tokens, done). | AllKnower |
| `POST`   | `/api/brain-dump/commit`          | Commit reviewed entities from review mode.                             | AllKnower |
| `GET`    | `/api/brain-dump/history`         | List the 20 most recent brain dumps.                                   | AllKnower |
| `GET`    | `/api/brain-dump/history/[id]`    | Get full detail for a single brain dump entry.                         | AllKnower |
| `GET`    | `/api/brain-dump/[id]/diffs`      | Per-note content diffs (revision history) produced by a brain dump.    | AllKnower |
| `POST`   | `/api/brain-dump/batch`           | Submit a batch (bulk) of brain dumps for background processing.        | AllKnower |
| `GET`    | `/api/brain-dump/batch/[batchId]` | Batch processing status.                                               | AllKnower |
| `DELETE` | `/api/brain-dump/batch/[batchId]` | Cancel queued jobs in a batch.                                         | AllKnower |

### POST `/api/brain-dump`

```json theme={null}
{
  "rawText": "Lord Ashvane is a human noble who rules Solara...",
  "mode": "auto"
}
```

**Modes:**

* `auto` — extract and write entities immediately
* `review` — extract entities and return as proposals for user approval
* `inbox` — queue the raw text for later processing

### POST `/api/brain-dump/commit`

```json theme={null}
{
  "rawText": "Lord Ashvane is a human noble...",
  "approvedEntities": [
    { "title": "Lord Ashvane", "type": "character", "action": "create", "content": "<p>...</p>" }
  ]
}
```

***

## Parsing tools and copilot

| Method      | Path                            | Description                                                                                                                                 | Backend        |
| :---------- | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------ | :------------- |
| `POST`      | `/api/ai/consistency`           | Run a RAG-augmented consistency scan.                                                                                                       | AllKnower      |
| `GET\|POST` | `/api/ai/gaps`                  | Detect underdeveloped lore areas. POST preferred (avoids caching).                                                                          | AllKnower      |
| `POST`      | `/api/ai/relationships`         | Get relationship suggestions for text/note.                                                                                                 | AllKnower      |
| `PUT`       | `/api/ai/relationships`         | Apply suggested relationships (persists as AllCodex relation attributes).                                                                   | AllKnower      |
| `POST`      | `/api/lore/[id]/copilot/chat`   | Article copilot turn — sends conversation transcript + current note context, returns assistant message + optional proposal.                 | AllKnower      |
| `POST`      | `/api/lore/[id]/copilot/stream` | SSE-streamed article copilot turn (status, token, reasoning, result, done).                                                                 | AllKnower      |
| `POST`      | `/api/lore/[id]/copilot/apply`  | Apply an approved copilot proposal (create/update notes, labels, relations). Returns `{ updatedNoteIds, createdNoteIds, skipped, failed }`. | AllCodex ETAPI |

### POST `/api/ai/consistency`

```json theme={null}
{ "noteIds": ["abc123", "def456"] }
```

### PUT `/api/ai/relationships` — apply

```json theme={null}
{
  "sourceNoteId": "abc123",
  "relations": [
    { "targetNoteId": "def456", "type": "rulerOf", "description": "Lord Ashvane rules Solara" }
  ],
  "bidirectional": true
}
```

***

## RAG

| Method | Path                            | Description                                               | Backend   |
| :----- | :------------------------------ | :-------------------------------------------------------- | :-------- |
| `GET`  | `/api/rag?text=<query>&topK=10` | Semantic similarity search against the lore vector index. | AllKnower |
| `POST` | `/api/rag`                      | Same, via POST body.                                      | AllKnower |

***

## Content collections

These routes query AllCodex ETAPI for notes with specific labels.

| Method | Path              | Description                | Filter                                    |
| :----- | :---------------- | :------------------------- | :---------------------------------------- |
| `GET`  | `/api/quests`     | List all quest notes.      | `#quest` label                            |
| `GET`  | `/api/statblocks` | List all statblock notes.  | `#statblock` label                        |
| `GET`  | `/api/timeline`   | List events and timelines. | `#loreType=event` OR `#loreType=timeline` |

***

## Share settings

| Method | Path              | Description                                        | Backend        |
| :----- | :---------------- | :------------------------------------------------- | :------------- |
| `GET`  | `/api/share`      | Get the current share root configuration.          | AllCodex ETAPI |
| `PUT`  | `/api/share`      | Set a note as the share root (public entry point). | AllCodex ETAPI |
| `GET`  | `/api/share/tree` | List all lore notes with share visibility flags.   | AllCodex ETAPI |

### GET `/api/share/tree` — response fields

Each note includes: `noteId`, `title`, `isDraft`, `isGmOnly`, `shareAlias`, `isProtected`, `isShared`.

***

## Public share endpoints

These routes power the Portal's public-facing site (`/` and `/public/lore/[id]`) and do not require authentication. They only return content from the configured `#shareRoot` subtree, and they strip notes tagged `#draft` or `#gmOnly`. Use them to embed AllCodex lore in your own website or to integrate a campaign wiki with other tools.

| Method | Path                                 | Description                                                                                                                                                                                                                                                                              | Backend                 |
| :----- | :----------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- |
| `GET`  | `/api/public/lore/[id]`              | Get a public lore note by `noteId` or `#shareAlias`. Returns sanitized HTML, display attributes, resolved relations, portrait image ID, theme song URL, and last-modified date. Returns `404` for missing, draft, or GM-only notes, and `401` if the share tree requires authentication. | AllCodex ETAPI          |
| `GET`  | `/api/public/search?q=<query>`       | Full-text search restricted to the share tree. Pass `q=#lore` to list all published lore. Returns `{ results: [{ id, title, score, path }] }`.                                                                                                                                           | AllCodex Core share API |
| `GET`  | `/api/public/images/[id]/[filename]` | Stream an image attached to a public lore note. Returns `404` for non-public notes, `401` if the share is password-protected. Cached for 24 hours.                                                                                                                                       | AllCodex Core share API |

### GET `/api/public/lore/[id]` — example response

```json theme={null}
{
  "noteId": "abc123",
  "title": "Lord Ashvane",
  "loreType": "character",
  "contentHtml": "<p>Lord Ashvane is a legendary general...</p>",
  "attributes": [
    { "name": "fullName", "value": "Lord Ashvane of Solara" },
    { "name": "race", "value": "Human" }
  ],
  "resolvedRelations": [
    { "name": "rulerOf", "targetNoteId": "def456", "targetTitle": "Solara", "loreType": "location" }
  ],
  "portraitImageNoteId": "img789",
  "themeSongUrl": "https://open.spotify.com/track/...",
  "dateModified": "2026-06-10T14:22:00Z"
}
```

<Tip>
  Before you call these endpoints, set [`NEXT_PUBLIC_PORTAL_URL`](/self-hosting/configuration) and tag a note in your grimoire with `#shareRoot`. See [Sharing lore with players](/guides/sharing-lore) for the full setup.
</Tip>

***

## Configuration and auth

| Method   | Path                                                      | Description                                                                                                                     | Backend                    |
| :------- | :-------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :------------------------- |
| `POST`   | `/api/auth/login`                                         | AllKnower email/password login → sets Bearer token cookie.                                                                      | AllKnower (better-auth)    |
| `POST`   | `/api/auth/register`                                      | Register AllKnower account → sets token cookie.                                                                                 | AllKnower (better-auth)    |
| `POST`   | `/api/auth/logout`                                        | Sign out, clear AllKnower session cookie.                                                                                       | AllKnower (better-auth)    |
| `GET`    | `/api/auth/session`                                       | Verify AllKnower session, return user.                                                                                          | AllKnower (better-auth)    |
| `POST`   | `/api/config/allknower-login`                             | AllKnower email/password login → sets Bearer token cookie.                                                                      | AllKnower (better-auth)    |
| `POST`   | `/api/config/allknower-register`                          | Register AllKnower account → sets token cookie.                                                                                 | AllKnower (better-auth)    |
| `POST`   | `/api/integrations/allcodex/connect`                      | Core password (or pre-acquired token) → obtains ETAPI token → stores it as the user's encrypted `UserIntegration` in AllKnower. | AllCodex ETAPI + AllKnower |
| `GET`    | `/api/integrations/allcodex/status`                       | Whether a Core integration is connected for the user.                                                                           | AllKnower                  |
| `DELETE` | `/api/integrations/allcodex`                              | Remove the user's stored Core integration.                                                                                      | AllKnower                  |
| `DELETE` | `/api/config/disconnect?service=allcodex\|allknower\|all` | Clear stored credentials.                                                                                                       | None (local)               |
| `GET`    | `/api/config/models`                                      | List configured/available parser models per task.                                                                               | AllKnower                  |
| `POST`   | `/api/config/wipe`                                        | Dangerous: wipe the user's lore + RAG state (LanceDB table + Prisma tracking).                                                  | AllKnower                  |
| `GET`    | `/api/config/portal`                                      | Get portal config (lore root note ID).                                                                                          | None (local)               |
| `PUT`    | `/api/config/portal`                                      | Set portal config.                                                                                                              | None (local)               |
| `GET`    | `/api/config/status`                                      | Check connectivity to AllCodex and AllKnower.                                                                                   | Both                       |

***

## Import

| Method | Path                      | Description                                                                | Backend   |
| :----- | :------------------------ | :------------------------------------------------------------------------- | :-------- |
| `POST` | `/api/import/system-pack` | Import a system pack (SRD, monster manual, etc.).                          | AllKnower |
| `POST` | `/api/import/azgaar`      | Import Azgaar Fantasy Map JSON. Supports `?action=preview` for simulation. | AllKnower |

***

## Notifications

Web-push notifications (e.g. brain dump completion status).

| Method   | Path                                  | Description                                     | Backend   |
| :------- | :------------------------------------ | :---------------------------------------------- | :-------- |
| `GET`    | `/api/notifications/vapid-public-key` | Get the VAPID public key for push subscription. | AllKnower |
| `POST`   | `/api/notifications/subscribe`        | Register a browser push subscription.           | AllKnower |
| `DELETE` | `/api/notifications/unsubscribe`      | Remove a push subscription.                     | AllKnower |

***

## Usage and budgets

| Method | Path                      | Description                                                    | Backend   |
| :----- | :------------------------ | :------------------------------------------------------------- | :-------- |
| `GET`  | `/api/usage/summary`      | Token/cost usage summary (per day, task, model).               | AllKnower |
| `GET`  | `/api/usage/budgets`      | Get the user's token-budget thresholds.                        | AllKnower |
| `PUT`  | `/api/usage/budgets`      | Set the user's token-budget thresholds.                        | AllKnower |
| `GET`  | `/api/usage/alert-status` | Whether the user is over budget / nearing the alert threshold. | AllKnower |

***

## Key developer patterns

1. **Proxy architecture**: Every `/api/*` route is a thin proxy. No domain logic lives in the Portal.
2. **Error handling**: Routes call `notConfigured()` when credentials are missing and `handleRouteError()` for exceptions.
3. **HTML sanitization**: The Portal performs DOMPurify sanitization. Do not assume content from ETAPI is safe for direct HTML insertion.
4. **Content-Type constraint**: PUT requests to `/api/lore/[id]/content` **must** use `Content-Type: text/plain` due to Express raw-parser behavior in Core.
5. **Cookie-only secrets**: The system stores all tokens in HTTP-only cookies. Browser JavaScript does not have access to credentials.
