Skip to content

REST API overview

The REST API under /api/v1/ lets integrations and tools (including the Obsidian plugin) manage documents and collections with a stable JSON interface. This page explains behavior that applies to all endpoints. Per-operation fields and request bodies are documented in the OpenAPI reference.

Use the same origin as your deployed Orimora instance:

  • Production: https://your-domain.example.com
  • Local (Vite): http://localhost:5173
  • Docker Compose (app service): http://localhost:3000 unless you remap the port

All paths in this document are relative to that base (e.g. GET /api/v1/documents).

  1. In the web app, go to Settings → Developers and create an API key.
  2. Keys are shown once and have the prefix kb_.
  3. Send the key as a Bearer token:
GET /api/v1/documents HTTP/1.1
Host: your-domain.example.com
Authorization: Bearer kb_your_key_here

The server resolves the key to a user and team. Routes that use requireUser() accept either a normal browser session or this Bearer token.

Each key has one or more scopes:

ScopeTypical use
readList and fetch documents and collections
writeCreate and update resources
adminOperations that change team-level settings (when exposed)

Default for new keys is read + write. Design integrations so they use the smallest scope necessary.

AreaLimit (indicative)Notes
All /api/* routes100 requests per IP or user per 60 secondsGlobal guard in the request pipeline; skipped for GET /api/health
Auth endpointsStricter (e.g. magic link / OAuth)Protects against abuse; see implementation for exact keys

When limited, the response is 429 Too Many Requests with JSON similar to:

{
"error": "Too many requests",
"retryAfterSeconds": 60
}

Successful responses may include:

  • X-RateLimit-Limit — max requests per window
  • X-RateLimit-Remaining — remaining in current window
  • Retry-After — present on 429

Treat 429 with exponential backoff in automation.

Without updatedSince, list responses include:

FieldMeaning
dataArray of documents
totalTotal rows matching filters (not just this page)
limitPage size (capped, default 25, max 100)
offsetSkip offset

Filter the list with these query parameters:

ParameterMeaning
collectionIdLimit to one collection
parentDocumentIdLimit to direct children of a document
titleExact title match, case-insensitive — for targeted existence lookups (used by the plugin)
statuspublished or draft
tagFilter to documents carrying this tag (by name, case-insensitive). Repeat the param (?tag=a&tag=b) for a multi-tag AND filter — only documents carrying all listed tags match
limit / offsetPage size (default 25, max 100) and skip offset

For tools that only need changed documents since a point in time:

  • Pass updatedSince as an ISO 8601 timestamp (e.g. 2026-04-01T12:00:00.000Z).
  • The response shape differs: there is no total/offset pagination in the same way; the server uses a higher effective limit for the sync window.
  • Add format=markdown together with updatedSince to receive a markdownText field per document for plain-text sync (used by the Obsidian selective pull).

See the OpenAPI /api/v1/documents GET operation for details.

  • text: Markdown string — the server converts it to TipTap/ProseMirror JSON internally.
  • content: Raw TipTap JSON if you already have structured content (advanced integrations).

You typically use one or the other, not both, when creating or updating body content.

POST /api/v1/documents and PATCH /api/v1/documents/:id accept an optional tags: string[] field of flat tag names. Semantics:

  • Tags are created-or-reused by normalized (trim + lowercase) name within the team.
  • The list replaces the document’s unrestricted tags; omit the field to leave tags untouched.
  • Restricted tags (those with a permission ACL) are never set or returned via the API — they stay UI-governed.

Every document object in responses (GET, POST, PATCH, and the list/updatedSince endpoints) includes:

  • tags — the document’s unrestricted tag names
  • revision — a monotonic revision counter (the exact drift signal used by the Obsidian plugin)

Success payloads usually look like:

{
"data": {
/* resource */
}
}

List endpoints return { "data": [ … ] } plus pagination fields as documented per route.

Errors use JSON with an error string (and HTTP 4xx/5xx):

{ "error": "Document not found" }
HTTPTypical cause
401Missing or invalid API key / session
403Authenticated but not allowed (e.g. onboarding incomplete)
404Resource not found or wrong team
429Rate limited
400Validation / bad JSON

Document lifecycle events can trigger webhooks configured in the workspace. Webhook delivery, signing, and retries are not part of the /api/v1/ CRUD surface; configure them in the app UI. Automation should still use idempotent handlers and verify signatures when provided.

The same operations are available as OpenAPI 3.1 for code generators and the Starlight UI:

Regenerate the bundled spec with:

Terminal window
cd docs && yarn generate:openapi

(Runs automatically as part of yarn build in the docs package.)