Mikipage.ai API
The Mikipage.ai REST API is the complete feature set of the product. Everything you can do on Mikipage — create and search notes, build AI pages, manage groups, publish to the web, run AI, chat — is available here.
The other ways to use Mikipage are deliberate subsets of this API:
- Web is an opinionated, minimalistic surface for everyday use. It makes choices for you and hides advanced options.
- MCP is a curated subset aimed at AI power users. Some operations are intentionally withheld (account settings, group and member management, file bytes, comments) to keep AI usage safe and cheap. See [1].
Anything that exists on Web or MCP is expressible through the API. The API is where the full, unopinionated set of options lives.
Python SDK
We provide an official Python SDK in the open-source miki_client library. It handles authentication (including the OAuth browser flow, token caching, and automatic refresh), pagination, file uploads, and the presigned-download flow for you.
miki_clientcurrently lives in a private GitHub repository and will be published publicly (PyPI + open source) at a later date. The examples below show both the SDK and the raw HTTP calls, so you can integrate in any language today.
from miki_client import MikiClient
# OAuth (default): opens a browser to log in, then caches + auto-refreshes tokens.
client = MikiClient("https://mikipage.com")
note = client.create_note("# Shopping list\n- milk\n- eggs")
print(note["id"])
Base URL and conventions
-
Base URL:
https://mikipage.com/api/...— there is no version prefix. -
JSON everywhere: request and response bodies are JSON with
snake_casefield names. -
Partial updates use
PATCH— send only the fields you want to change. -
List responses are enveloped:
{ "items": [ /* ... */ ], "next_cursor": "opaque-string-or-null", "has_more": false }To page, pass
next_cursorback as thecursorquery parameter untilhas_moreisfalse. -
Timestamps are RFC 3339 strings (e.g.
2026-07-23T18:04:00Z). -
Errors use RFC 9457
application/problem+json. Every error body carries a stable machine-readablecodeyou can switch on — never string-match the humandetail:{ "type": "about:blank", "title": "Not Found", "status": 404, "code": "not_found", "detail": "Note not found", "request_id": "req_..." }Common codes:
invalid_request(400),unauthenticated/invalid_token(401),insufficient_credits(402),permission_denied/insufficient_scope/quota_exceeded(403),not_found(404),conflict(409),content_too_large(413),rate_limited(429),internal(500),unavailable(503). A missing resource and one you're not allowed to see return an identicalnot_foundbody — there are no existence leaks. -
OpenAPI: the machine-readable spec is served at
GET /api/openapi.json(OpenAPI 3.1). It is the source of truth from which the SDKs are generated.
Authentication
Mikipage uses OAuth 2.1 access tokens (Cognito-issued JWTs), verified at the edge. Send the token as a Bearer header on every request:
Authorization: Bearer <access-token>
How to get a token
-
OAuth authorization-code + PKCE (recommended for apps and interactive tools). Discover the endpoints at
GET /.well-known/oauth-authorization-server:authorization_endpoint:https://mikipage.com/oauth/authorizetoken_endpoint:https://mikipage.com/oauth/tokenregistration_endpoint:https://mikipage.com/oauth/register(dynamic client registration)
Refresh tokens are issued for long-lived CLI/SDK sessions. The Python SDK automates the whole loop — browser login, token cache (
~/.mikipage/), and silent refresh. -
Email/password (for headless / CI where a browser isn't available). Credential lifecycle belongs to Cognito, not the API — there is no
/api/auth/login. The SDK mints tokens directly against Cognito for you:client = MikiClient( "https://mikipage.com", email="user@example.com", password="…", auth_mode="password", ) client.login()
Scopes
Access tokens carry scopes that gate what a client may do. This is how Web, MCP, and your own scripts become subsets of the same API — they differ only in which scopes were granted at authorization time.
| Scope | Grants |
|---|---|
notes:read / notes:write | Notes incl. versions, attachments, note↔group membership |
pages:read / pages:write | Pages incl. versions, note associations |
groups:read / groups:write | Group CRUD, membership, user lookup |
comments:write | Create / edit / delete your own comments |
runs:read / runs:write | List/poll AI runs; create / complete |
chat | AI chat threads (billed to server credits) |
account:read / account:write | Profile and space settings |
usage:read | Quotas, credit balances, credit ledger |
publishing | Public URL aliases and public exposure |
Comment reads ride on the parent's :read scope. The /api/public/* namespace needs no token at all.
Note: scope enforcement is currently annotation-only in the spec — today a valid token grants full access. The
x-required-scopeson each endpoint (below and inopenapi.json) documents the scope each operation will require once per-scope enforcement is enabled, so build against them now.
Quickstart
Create a note
A note is just Markdown content. Mikipage indexes it for AI search and page-matching automatically.
SDK
note = client.create_note(
"# Meeting notes\n\nDiscussed Q3 roadmap. #planning",
group_ids=["<group-id>"], # optional: share to groups on creation
)
note_id = note["id"]
HTTP
curl -X POST https://mikipage.com/api/notes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "# Meeting notes\n\nDiscussed Q3 roadmap. #planning"}'
Response (Note):
{
"id": "note_abc123",
"content": "# Meeting notes\n\nDiscussed Q3 roadmap. #planning",
"starred": false,
"stale_match": false,
"owner_id": "…",
"state": "active",
"created_at": "2026-07-23T18:04:00Z",
"updated_at": "2026-07-23T18:04:00Z"
}
Retrieve a note
SDK
note = client.get_note("note_abc123")
# Pull in related data in one call:
note = client.get_note("note_abc123", expand=["groups", "pages", "attachments"])
HTTP
curl https://mikipage.com/api/notes/note_abc123?expand=groups,pages \
-H "Authorization: Bearer $TOKEN"
You can read a note if you own it or if it's shared to a group you belong to.
Search notes
GET /api/notes is a unified list + semantic search. Omit q to list by recency; pass q to rank by cross-lingual semantic similarity. Any "quoted" substring in q becomes a strict phrase filter.
results = client.list_notes(q='roadmap "Q3"', tags=["planning"], limit=20)
for item in results["items"]:
print(item["id"], item["content"][:60])
API reference
All endpoints below are under https://mikipage.com. The scope shown is the one the operation will require under scope enforcement.
Notes
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/notes | notes:read | List + semantic search over your notes |
POST /api/notes | notes:write | Create a note |
GET /api/notes/{id} | notes:read | Get a note (expand=groups,pages,attachments) |
PATCH /api/notes/{id} | notes:write | Update content / starred |
DELETE /api/notes/{id} | notes:write | Soft-delete a note |
GET /api/notes/{id}/versions | notes:read | Up to 5 historical versions |
GET /api/notes/{id}/pages | notes:read | Pages that contain this note |
GET /api/notes/{id}/groups | notes:read | Groups this note is shared to |
PUT /api/notes/{id}/groups/{groupId} | notes:write | Share note to a group |
DELETE /api/notes/{id}/groups/{groupId} | notes:write | Un-share note from a group |
Attachments
Attachments ride on notes:* scopes. Small files (≤ ~700 KB) upload in one multipart request; larger files use presign → S3 PUT → confirm (the SDK does this automatically).
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/notes/{id}/attachments | notes:read | List a note's attachments |
POST /api/notes/{id}/attachments | notes:write | Upload (multipart) or presign an upload |
POST /api/notes/{id}/attachments/{attachmentId}/confirm | notes:write | Confirm a presigned upload |
DELETE /api/notes/{id}/attachments/{attachmentId} | notes:write | Delete an attachment |
GET /api/attachments/{attachmentId} | notes:read | Attachment metadata (for [[file:id]] refs) |
GET /api/attachments/{attachmentId}/download | notes:read | Presigned download URL (~15 min) |
Pages
A page (internally "Miki") takes a title and instructions, and the AI assembles relevant notes into a result. Comment reads ride on pages:read.
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/pages | pages:read | List + semantic search over pages |
POST /api/pages | pages:write | Create a page (starts stale — no AI run is queued) |
GET /api/pages/{id} | pages:read | Get a page (expand=notes,group) |
PATCH /api/pages/{id} | pages:write | Update title / instructions / result |
DELETE /api/pages/{id} | pages:write | Soft-delete a page |
GET /api/pages/{id}/versions | pages:read | Up to 5 historical versions |
GET /api/pages/{id}/notes | pages:read | Notes associated to the page (with pinned flags) |
PUT /api/pages/{id}/notes/{noteId} | pages:write | Attach a note (optionally pinned) |
PATCH /api/pages/{id}/notes/{noteId} | pages:write | Update the pinned flag |
DELETE /api/pages/{id}/notes/{noteId} | pages:write | Detach a note |
Comments
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/notes/{id}/comments | notes:read | List a note's comment thread |
POST /api/notes/{id}/comments | notes:write* | Add a comment / reply |
PATCH /api/notes/{id}/comments/{commentId} | notes:write* | Edit your comment |
DELETE /api/notes/{id}/comments/{commentId} | notes:write* | Delete (author or target owner) |
GET /api/pages/{id}/comments | pages:read | List a page's comment thread |
POST /api/pages/{id}/comments | pages:write* | Add a comment / reply |
PATCH /api/pages/{id}/comments/{commentId} | pages:write* | Edit your comment |
DELETE /api/pages/{id}/comments/{commentId} | pages:write* | Delete (author or target owner) |
* Writing comments will require the comments:write scope under enforcement.
Groups
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/groups | groups:read | Groups you own or belong to |
POST /api/groups | groups:write | Create a group (you become owner) |
GET /api/groups/{id} | groups:read | Get a group (expand=members) |
PATCH /api/groups/{id} | groups:write | Update name / description / public flag |
DELETE /api/groups/{id} | groups:write | Delete a group |
GET /api/groups/{id}/members | groups:read | List members |
POST /api/groups/{id}/members | groups:write | Invite a member by email |
PATCH /api/groups/{id}/members/{userId} | groups:write | Update member_name (owner, or yourself); notifications_muted (yourself only) |
DELETE /api/groups/{id}/members/{userId} | groups:write | Remove a member (owner); yourself ⇒ leave the group, unsharing your notes from it |
GET /api/users/lookup?email=… | groups:read | Invite preflight: does an account exist? |
Publishing (public URL aliases)
Claiming an alias does not publish anything by itself — the group must also be public (PATCH /api/groups/{id} with is_public: true). A published group and its notes/pages then resolve at a public URL under the group's alias.
| Method & path | Scope | Purpose |
|---|---|---|
GET/PUT/DELETE /api/groups/{id}/alias | publishing | The group's public slug |
GET /api/groups/{id}/aliases | publishing | Every alias in the group |
GET/PUT/DELETE /api/notes/{id}/alias | notes:read / notes:write | A note's public slug (within a group) |
GET/PUT/DELETE /api/pages/{id}/alias | pages:read / pages:write | A page's public slug |
Public namespace (no authentication)
Read-only access to content in public groups. A non-public target returns not_found (or an empty list for comments) — no existence leak.
| Method & path | Purpose |
|---|---|
GET /api/public/groups/{id} | Public group profile |
GET /api/public/groups/{id}/pages | Public group's pages |
GET /api/public/pages/{id} | A public page |
GET /api/public/notes/{id} | A public note |
GET /api/public/pages/{id}/comments | A public page's comments |
GET /api/public/notes/{id}/comments | A public note's comments |
GET /api/public/attachments/{attachmentId} | Public attachment metadata |
GET /api/public/attachments/{attachmentId}/download | Public attachment presigned download |
AI Runs
Runs are entity-bound AI jobs. execution: "server" enqueues the job on Mikipage's infrastructure (billed to server credits). execution: "client" returns an llm_input payload you run against your own LLM, then submit back via complete — this is how MCP-style clients do AI without server credits.
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/runs?target_type=…&target_id=… | runs:read | Run history for a note or page (last 10) |
POST /api/runs | runs:write | Create a run (server or client execution) |
POST /api/runs/{id}/complete | runs:write | Submit client-executed LLM output |
Run types include page.run, page.analyze, page.generate, page.match_notes, note.match_pages, note.edit, space.suggest_pages, and space.refresh_all.
Chat
An interactive AI tool-use loop with thread state (distinct from entity-bound runs). Billed to server credits.
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/chats | chat | List your chat threads |
POST /api/chats | chat | Start a new thread |
GET /api/chats/{id} | chat | Load a thread with its messages |
POST /api/chats/{id}/messages | chat | Add a follow-up turn |
Account, spaces, and usage
A "space" is a viewing scope for AI organization — your Personal space (space_id = your own user id) or a group space (space_id = a group id).
| Method & path | Scope | Purpose |
|---|---|---|
GET /api/me | account:read | Your identity + profile |
PATCH /api/me | account:write | Update profile (name, timezone, editor, …) |
GET /api/spaces/{spaceId} | account:read | A space's AI-org settings + page suggestions |
PATCH /api/spaces/{spaceId} | account:write | Curate a space's page suggestions |
GET /api/usage | usage:read | Quota counts + AI credit balances |
GET /api/usage/credit-transactions | usage:read | AI credit ledger (newest first) |
Meta
| Method & path | Purpose |
|---|---|
GET /api/openapi.json | The served OpenAPI 3.1 spec |
GET /.well-known/oauth-authorization-server | OAuth discovery metadata |
GET /.well-known/oauth-protected-resource | OAuth protected-resource metadata |
To be implemented
The following are designed but not yet available. They will require additional backend work and are tracked for a follow-up iteration — build against the current surface for now.
- Scope enforcement: per-scope authorization is annotation-only today (a valid token grants full access). The
x-required-scopesvalues above document the target model. - Webhooks & events: the entire webhooks family and a server-sent-events stream (
GET /api/events). - Chat: streaming responses and
DELETE /api/chats/{id}. - Runs: run-id-addressable reads (
GET /api/runs/{id}), a global cross-entity runs list with cursors,POST /api/runs/{id}/cancel, and server runs returning a run id on creation. - Restore:
POST /api/notes/{id}/restoreandPOST /api/pages/{id}/restorefor soft-deleted items. - Pictures:
PUT /api/me/pictureandPUT /api/groups/{id}/picture(today, clients upload directly to storage). - Reliability:
Idempotency-Keysupport and rate limiting. - Personal access tokens (PATs): the design is PAT-ready (reserved
mikip_prefix); OAuth + refresh tokens cover current flows. A device-authorization grant is also under evaluation for headless bootstrap. - List refinements: a
stale=filter andexpand=on list endpoints.