# BASE REST API Manual (for LLMs) > This document is a reference for LLMs / AI assistants to operate a BASE knowledge base via the REST API. > For the human-oriented introductory guide, see `/guide/api`. > > - **Target API version**: 1.1.0 (OpenAPI 3.1) > - **This manual**: `https://base.kodama.com/api/manual.md` (the same content is also served at `https://base.kodama.com/llms-full.txt`) > - **OpenAPI schema**: `https://base.kodama.com/api/openapi.yaml` > - **Base URL**: `https://base.kodama.com/api/v1` ## 1. Overview BASE is a hierarchical knowledge-base system. Via the REST API you can list / read / search pages and fetch the tree (GET), and create / partially update / soft-delete pages (CRUD). **Characteristics**: - Stateless (every request is authenticated independently) - JSON in / JSON out - UTC ISO 8601 timestamps - One token is locked to one Base (cross-Base access is forbidden) ## 2. Authentication ### Bearer Token Every request must include: ``` Authorization: Bearer kid_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` Tokens are issued from the KID (Kodama ID, `https://id.kodama.com`) API token page. ### Scope model | Scope | Allowed operations | |-------|-------------------| | `read` | GET only (list, detail, search, tree, Base info) | | `read` + `write` | GET + POST / PATCH / DELETE (all CRUD) | **Custom scope**: a target Base ID must be specified at issue time. The token is locked to that single Base; requests for other Bases get `403 FORBIDDEN` (data-leak prevention). ### Permission inside the Base | API scope | Required Base role | |-----------|--------------------| | GET | `owner` only (reader / writer cannot use the API) | | POST / PATCH / DELETE | `owner` only (plus `write` scope) | All API access (read and write) is restricted to the Base **owner**. Members joined as `reader` or `writer` cannot use the API; they get `403 FORBIDDEN`. ## 3. Endpoint list | Method | Path | Purpose | |--------|------|---------| | `GET` | `/bases/{base_id}/info` | Base summary | | `GET` | `/bases/{base_id}/pages` | Page list | | `GET` | `/bases/{base_id}/pages/{content_id}` | Page detail | | `GET` | `/bases/{base_id}/search?q={keywords}` | Search | | `GET` | `/bases/{base_id}/tree` | Page tree | | `POST` | `/bases/{base_id}/pages` | Create page | | `PATCH` | `/bases/{base_id}/pages/{content_id}` | Partial update | | `DELETE` | `/bases/{base_id}/pages/{content_id}` | Soft delete | **Path parameters**: - `base_id`: lowercase letters + digits + hyphen, 6 chars or more - `content_id`: alphanumeric, exactly 6 chars ## 4. Data model ### Page (detail) ```json { "content_id": "9y6f4u", "title": "Page title", "body": "# Body\n\nMarkdown source...", "type": "M", "level": 0, "path": ["Parent title", "Page title"], "lang": "en", "created_at": "2026-05-26T01:34:10Z", "updated_at": "2026-05-26T01:34:27Z" } ``` | Field | Type | Description | |-------|------|-------------| | `content_id` | string | Content ID (6 alphanumeric chars, immutable) | | `title` | string | Page title | | `body` | string | Body content (Markdown source if type=M) | | `type` | string | `T`=Text / `H`=HTML / `M`=Markdown | | `level` | integer | Hierarchy depth (0=root) | | `path` | string[] | Array of titles from root to this page | | `lang` | string | `ja` / `en` | | `created_at` | string | UTC ISO 8601 | | `updated_at` | string | UTC ISO 8601 | ### PageSummary (for listings) ```json { "content_id": "9y6f4u", "title": "Page title", "type": "M", "level": 0, "order": 5, "lang": "en", "updated_at": "2026-05-26T01:34:27Z" } ``` `order` is the display order inside the Base (lower comes first). ## 5. GET endpoints ### 5.1 GET /bases/{base_id}/info Base metadata. ```bash curl -H "Authorization: Bearer $TOKEN" \ https://base.kodama.com/api/v1/bases/my-knowledge/info ``` **Response**: ```json { "base_id": "my-knowledge", "description": "Description of the Base", "view": "L", "total_pages": 42 } ``` `view`: `O`=public / `L`=member-only / `I`=logged-in users only. ### 5.2 GET /bases/{base_id}/pages Page list (in display order). Array of `PageSummary`. ```json { "pages": [{ "content_id": "...", "title": "...", ... }], "total": 42 } ``` ### 5.3 GET /bases/{base_id}/pages/{content_id} Page detail. Returns `Page` (see ยง4). ### 5.4 GET /bases/{base_id}/search?q={keywords} Partial-match search over title and body. Title matches rank first, then by descending update time. ```json { "pages": [{ "content_id": "...", "title": "...", ... }], "total": 3, "keywords": "search term" } ``` ### 5.5 GET /bases/{base_id}/tree Returns the hierarchy as nested JSON (up to 3 levels). ```json { "tree": [ { "content_id": "abc123", "title": "Root page", "level": 0, "children": [ { "content_id": "def456", "title": "Child", "level": 1, "children": [] } ] } ] } ``` ## 6. POST /bases/{base_id}/pages (create page) Appends a new page at the end of the Base (`level=0`, fixed). **Required scopes**: `read` + `write`. **Required role**: `owner` only. ### Request ```bash curl -X POST https://base.kodama.com/api/v1/bases/my-knowledge/pages \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "New page", "body": "# Body\n\nWritten in Markdown", "content_type": "M", "content_lang": "en" }' ``` | Field | Required | Type | Default | Allowed values | |-------|----------|------|---------|----------------| | `title` | Yes | string | - | 1 char or more | | `body` | No | string | `""` | - | | `content_type` | No | string | `"M"` | `T` / `H` / `M` | | `content_lang` | No | string | `"ja"` | `ja` / `en` | ### Response (201 Created) The `Location` header points to the created resource; the body is in `Page` format. ``` Location: /api/v1/bases/my-knowledge/pages/9y6f4u ``` ```json { "content_id": "9y6f4u", "title": "New page", "body": "# Body\n\nWritten in Markdown", "type": "M", "level": 0, "path": ["New page"], "lang": "en", "created_at": "2026-05-26T01:34:10Z", "updated_at": "2026-05-26T01:34:10Z" } ``` ### Notes - When `content_type=M`, the server caches the rendered HTML (`content_body_html`) automatically - To place the page in the hierarchy, change `level` with a subsequent PATCH - `content_id` is server-generated (6 chars, lowercase + digits) ## 7. PATCH /bases/{base_id}/pages/{content_id} (partial update) Only the fields you send are updated. **Omitted fields keep their current value.** **Required scopes**: `read` + `write`. **Required role**: `owner` only. ### Request ```bash curl -X PATCH https://base.kodama.com/api/v1/bases/my-knowledge/pages/9y6f4u \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Updated title", "body": "## Updated body" }' ``` | Field | Type | Allowed values | Description | |-------|------|----------------|-------------| | `title` | string | 1 char or more | - | | `body` | string | - | - | | `content_type` | string | `T` / `H` / `M` | - | | `content_lang` | string | `ja` / `en` | - | | `view` | string \| null | `O` / `L` / `I` / `null` | Per-page visibility. Send `null` explicitly to revert to Base inheritance | | `level` | integer | 0 or more | Tree depth. Child nodes shift in sync | **The body must contain at least one field.** `{}` returns `400 BAD_REQUEST`. ### Response (200 OK) The updated `Page`. `path` also reflects the new hierarchy. ### Important behavior - **Explicit `null` on `view`**: `{"view": null}` resets the page to "inherit from Base." Omitting `view` means "do not change." The two are distinguished. - **Changing `level`**: when you change a page's level, all descendants shift by the same delta (e.g. self goes 0 โ†’ 1, children go 1 โ†’ 2). - **Markdown HTML rebuild**: if the page ends up with `content_type=M` and you sent `body` or `content_type`, `content_body_html` is regenerated. - **Base mismatch**: if `content_id` belongs to a different Base, the response is `404 NOT_FOUND` (information hiding). - **Soft-deleted pages**: pages with `status='D'` return `404 NOT_FOUND`. ### Examples | Use case | Request body | |----------|--------------| | Change title only | `{"title": "New title"}` | | Rewrite body | `{"body": "## New content"}` | | Indent one level | `{"level": 1}` | | Publish single page | `{"view": "O"}` | | Clear per-page visibility | `{"view": null}` | | Convert format (H โ†’ M) | `{"content_type": "M", "body": "# Markdown"}` | ## 8. DELETE /bases/{base_id}/pages/{content_id} (soft delete) Moves the page to the trash (`status='D'`). Not a physical delete; restorable from the web UI (`/trash`). **Required scopes**: `read` + `write`. **Required role**: `owner` only. ### Request ```bash curl -X DELETE https://base.kodama.com/api/v1/bases/my-knowledge/pages/9y6f4u \ -H "Authorization: Bearer $TOKEN" ``` ### Response (204 No Content) Empty body. ### Important behavior - If the page has children: each child moves up one level (taking the parent's slot) - `content_order` of following pages is compacted - **Not idempotent**: DELETE against an already-deleted page returns `404 NOT_FOUND` - No restore API. Use the web UI to restore. ## 9. Error responses All errors share this JSON shape: ```json { "error": { "code": "BAD_REQUEST", "message": "title is required (must be a string with at least 1 char)" } } ``` ### HTTP status codes | Status | `error.code` | Typical cause | |--------|--------------|---------------| | 200 | - | GET / PATCH succeeded | | 201 | - | POST succeeded | | 204 | - | DELETE succeeded (no body) | | 400 | `BAD_REQUEST` | JSON parse error, missing required field, invalid enum, empty PATCH body | | 401 | `UNAUTHORIZED` | Bearer Token missing / invalid / expired | | 403 | `FORBIDDEN` | Base scope mismatch, not the Base `owner`, or missing `write` scope on a write request | | 404 | `NOT_FOUND` | Base / page does not exist, or operation on a soft-deleted page | | 405 | `METHOD_NOT_ALLOWED` | Unexpected HTTP method | | 415 | `UNSUPPORTED_MEDIA_TYPE` | POST / PATCH without `Content-Type: application/json` | | 500 | `INTERNAL_ERROR` | Server-side error | ## 10. Best practices for LLM use ### 10.1 Check before creating Before creating a new page, search for a same-title page: ``` 1. GET /search?q={title} to check for duplicates 2. If none, POST /pages to create ``` Note: BASE does **not** enforce title uniqueness at the database level. This check exists to prevent autonomous LLM agents from creating duplicate pages on retry loops; treat it as a soft idempotency guard. ### 10.2 Bulk updates PATCH is one request per page. To update many pages, call sequentially. Parallel requests are allowed (but concurrent PATCHes against the same page are last-write-wins). ### 10.3 Hierarchy operations Understand the tree before acting: ``` 1. GET /tree to inspect current shape 2. POST /pages to append at the tail (level=0) 3. PATCH /pages/{id} to set the final level ``` ### 10.4 Confirm before deleting `DELETE` is not idempotent. Calling `GET /pages/{id}` first is safer (especially when you need to distinguish "never existed" from "already deleted" - both return 404). ### 10.5 Body format - **Prefer Markdown**: `content_type: "M"`. LLMs read and write it well, and the server caches the rendered HTML. - HTML input is sanitized via `Parsedown SafeMode`-equivalent (script tags etc. stripped). - Line breaks: in Markdown, plain newlines render as `
` (no need for trailing two spaces). ### 10.6 Error-handling guidance | Situation | Action | |-----------|--------| | 401 / 403 | Check the token. Ask the user to re-issue. | | 404 | Resource missing or deleted. Re-fetch the list to confirm. | | 400 | Validation error. Surface `message` to the user. | | 415 | Always send `Content-Type: application/json`. | | 5xx | Server error. Retry with backoff. | ## 11. Full curl examples ### 11.1 Create -> reparent -> publish -> delete ```bash TOKEN="kid_xxxxxxxx" BASE="my-knowledge" API="https://base.kodama.com/api/v1/bases/$BASE" # 1. Create page RESP=$(curl -sS -X POST "$API/pages" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"title":"API spec notes","body":"# Spec\n\n## Auth","content_type":"M"}') CID=$(echo "$RESP" | jq -r .content_id) echo "Created: $CID" # 2. Move to level=1 curl -sS -X PATCH "$API/pages/$CID" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"level":1}' > /dev/null # 3. Publish this single page curl -sS -X PATCH "$API/pages/$CID" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"view":"O"}' > /dev/null # 4. Append to body curl -sS -X PATCH "$API/pages/$CID" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"body":"# Spec\n\n## Auth\n\n## Errors"}' > /dev/null # 5. Verify curl -sS -H "Authorization: Bearer $TOKEN" "$API/pages/$CID" | jq # 6. Clean up curl -sS -X DELETE -H "Authorization: Bearer $TOKEN" "$API/pages/$CID" ``` ### 11.2 Search -> fetch matching page ```bash # Search curl -sS -H "Authorization: Bearer $TOKEN" "$API/search?q=$(jq -rn --arg q 'auth' '$q|@uri')" # Fetch the body of a hit curl -sS -H "Authorization: Bearer $TOKEN" "$API/pages/abc123" ``` ## 12. Constraints and notes - **CORS**: `Access-Control-Allow-Origin: *` (callable from browsers as well) - **Rate limits**: none today (planned for the future) - **Relation to MCP**: MCP (Model Context Protocol) is read-only. Writes are REST API only. - **Body size limit**: nginx / PHP-FPM defaults (no per-endpoint cap) - **Time zone**: input and output are UTC ISO 8601. Convert to local time on the client. - **Not yet implemented** (future work): page reordering (changing `order`), trash operations, Base settings, user/role management, ETag-based optimistic locking, webhooks.