# kbase — Agent API Guide

This file is intended for LLM agents interacting with this site programmatically.
All write operations use the JSON API described below. GET endpoints are public.
Write endpoints (POST, PUT, DELETE) require HTTP Basic Authentication.

## Authentication

All mutating requests must include an `Authorization` header:

```
Authorization: Basic <base64(username:password)>
```

Example with curl:

```bash
curl -u username:password -X POST ...
```

## Base URL

Replace `http://localhost:3000` with the actual host when deployed.

---

## Data model

```
spaces            (top-level grouping, e.g. "Notes", "Essays", "Project X")
  id, name, slug, description, private (bool, default false)

pages             (belongs to one space — this is the published content)
  id, space_id, title, slug, summary, body (markdown), tags (JSON string),
  published (bool, default true), private (bool, default false), cover_path
```

**body** is markdown — GitHub-flavoured. It is rendered to HTML in the browser view.
Supports headings, lists, code blocks, tables, blockquotes, images, and inline formatting.

**summary** is an optional one-line abstract shown in page listings and at the top of the page.

**tags** is a free-form JSON array (stored as a string), e.g. `["draft","research"]`.

**published** defaults to `true`. Set it to `false` to save a draft; the browser only
shows pages where `published = true`. Drafts are still readable via the API with `?include_drafts=1`.

**private** defaults to `false` (public). When `true`, the browser shows a "Private" badge next to
the item (and "Public" otherwise) on the home page, space page, and page detail. It is a display
label only — it does **not** restrict browser access. Use `published: false` to hide a page entirely.

**cover_path** is optional. When uploading an image it becomes `/uploads/<filename>`.

---

## Spaces

### List all spaces
```
GET /api/spaces
```

### Get one space
```
GET /api/spaces/:id
```

### Create a space
```
POST /api/spaces
Content-Type: application/json

{
  "name": "Notes",
  "slug": "notes",
  "description": "Quick thoughts and references",
  "private": false
}
```
`name` and `slug` are required. `slug` must be unique and URL-safe.
`private` is optional (defaults to `false`).

### Update a space
```
PUT /api/spaces/:id
Content-Type: application/json

{ "name": "...", "slug": "...", "description": "...", "private": true }
```

### Delete a space
```
DELETE /api/spaces/:id
```
Deleting a space cascades and deletes all of its pages.

---

## Pages

Pages are scoped to a space.

### List pages in a space
```
GET /api/spaces/:spaceId/pages
```
By default only published pages are returned. Pass `?include_drafts=1` to include drafts.

### Get one page
```
GET /api/spaces/:spaceId/pages/:pageId
```

### Create a page (JSON, no cover image)
```
POST /api/spaces/:spaceId/pages
Content-Type: application/json

{
  "title": "Why SQLite is enough",
  "slug": "why-sqlite-is-enough",
  "summary": "A short defence of single-file databases for personal apps.",
  "body": "# Why SQLite is enough\n\nMost apps don't need a server...",
  "tags": ["databases","architecture"],
  "published": true,
  "private": false
}
```
`title`, `slug`, and `body` are required. `slug` must be unique within the space.
`private` is optional (defaults to `false`).

### Create a page (with a cover image)

Use multipart/form-data when attaching an image:

```bash
curl -u user:pass \
  -F "title=Trip to the Coast" \
  -F "slug=trip-to-the-coast" \
  -F "summary=A weekend away." \
  -F "body=# Trip to the Coast\nLong walk, cold swim." \
  -F "cover=@/path/to/photo.jpg" \
  http://localhost:3000/api/spaces/1/pages
```

Accepted image types: any `image/*` MIME type. Max size: 10 MB.
The stored `cover_path` is a URL path, e.g. `/uploads/abc123.jpg`.

### Update a page
```
PUT /api/spaces/:spaceId/pages/:pageId
Content-Type: application/json

{ "title": "New title", "body": "Updated body", "published": true }
```
All fields are optional — only the fields you send are updated.
Send a multipart request with a `cover` file to replace the cover image.

### Delete a page
```
DELETE /api/spaces/:spaceId/pages/:pageId
```

---

## Typical agent workflow

```bash
# 1. Find or create the space
SPACE=$(curl -s -u user:pass -X POST http://localhost:3000/api/spaces \
  -H 'content-type: application/json' \
  -d '{"name":"Notes","slug":"notes","description":"Quick thoughts"}')
SPACE_ID=$(echo $SPACE | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')

# 2. Publish a page (JSON only)
curl -s -u user:pass -X POST http://localhost:3000/api/spaces/$SPACE_ID/pages \
  -H 'content-type: application/json' \
  -d '{"title":"Hello world","slug":"hello-world","body":"# Hello\n\nFirst post.","tags":["meta"]}'
```

When the user supplies a cover photo, switch to multipart/form-data and attach it via the `cover` field.

---

## Browser URLs (read-only)

| Path | Shows |
|------|-------|
| `/` | All spaces |
| `/s/:spaceSlug` | All published pages in a space |
| `/s/:spaceSlug/:pageSlug` | A single rendered page |
