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_client currently 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_case field 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_cursor back as the cursor query parameter until has_more is false.

  • 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-readable code you can switch on — never string-match the human detail:

    {
      "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 identical not_found body — 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/authorize
    • token_endpoint: https://mikipage.com/oauth/token
    • registration_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.

ScopeGrants
notes:read / notes:writeNotes incl. versions, attachments, note↔group membership
pages:read / pages:writePages incl. versions, note associations
groups:read / groups:writeGroup CRUD, membership, user lookup
comments:writeCreate / edit / delete your own comments
runs:read / runs:writeList/poll AI runs; create / complete
chatAI chat threads (billed to server credits)
account:read / account:writeProfile and space settings
usage:readQuotas, credit balances, credit ledger
publishingPublic 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-scopes on each endpoint (below and in openapi.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 & pathScopePurpose
GET /api/notesnotes:readList + semantic search over your notes
POST /api/notesnotes:writeCreate a note
GET /api/notes/{id}notes:readGet a note (expand=groups,pages,attachments)
PATCH /api/notes/{id}notes:writeUpdate content / starred
DELETE /api/notes/{id}notes:writeSoft-delete a note
GET /api/notes/{id}/versionsnotes:readUp to 5 historical versions
GET /api/notes/{id}/pagesnotes:readPages that contain this note
GET /api/notes/{id}/groupsnotes:readGroups this note is shared to
PUT /api/notes/{id}/groups/{groupId}notes:writeShare note to a group
DELETE /api/notes/{id}/groups/{groupId}notes:writeUn-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 & pathScopePurpose
GET /api/notes/{id}/attachmentsnotes:readList a note's attachments
POST /api/notes/{id}/attachmentsnotes:writeUpload (multipart) or presign an upload
POST /api/notes/{id}/attachments/{attachmentId}/confirmnotes:writeConfirm a presigned upload
DELETE /api/notes/{id}/attachments/{attachmentId}notes:writeDelete an attachment
GET /api/attachments/{attachmentId}notes:readAttachment metadata (for [[file:id]] refs)
GET /api/attachments/{attachmentId}/downloadnotes:readPresigned 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 & pathScopePurpose
GET /api/pagespages:readList + semantic search over pages
POST /api/pagespages:writeCreate a page (starts stale — no AI run is queued)
GET /api/pages/{id}pages:readGet a page (expand=notes,group)
PATCH /api/pages/{id}pages:writeUpdate title / instructions / result
DELETE /api/pages/{id}pages:writeSoft-delete a page
GET /api/pages/{id}/versionspages:readUp to 5 historical versions
GET /api/pages/{id}/notespages:readNotes associated to the page (with pinned flags)
PUT /api/pages/{id}/notes/{noteId}pages:writeAttach a note (optionally pinned)
PATCH /api/pages/{id}/notes/{noteId}pages:writeUpdate the pinned flag
DELETE /api/pages/{id}/notes/{noteId}pages:writeDetach a note

Comments

Method & pathScopePurpose
GET /api/notes/{id}/commentsnotes:readList a note's comment thread
POST /api/notes/{id}/commentsnotes: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}/commentspages:readList a page's comment thread
POST /api/pages/{id}/commentspages: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 & pathScopePurpose
GET /api/groupsgroups:readGroups you own or belong to
POST /api/groupsgroups:writeCreate a group (you become owner)
GET /api/groups/{id}groups:readGet a group (expand=members)
PATCH /api/groups/{id}groups:writeUpdate name / description / public flag
DELETE /api/groups/{id}groups:writeDelete a group
GET /api/groups/{id}/membersgroups:readList members
POST /api/groups/{id}/membersgroups:writeInvite a member by email
PATCH /api/groups/{id}/members/{userId}groups:writeUpdate member_name (owner, or yourself); notifications_muted (yourself only)
DELETE /api/groups/{id}/members/{userId}groups:writeRemove a member (owner); yourself ⇒ leave the group, unsharing your notes from it
GET /api/users/lookup?email=…groups:readInvite 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 & pathScopePurpose
GET/PUT/DELETE /api/groups/{id}/aliaspublishingThe group's public slug
GET /api/groups/{id}/aliasespublishingEvery alias in the group
GET/PUT/DELETE /api/notes/{id}/aliasnotes:read / notes:writeA note's public slug (within a group)
GET/PUT/DELETE /api/pages/{id}/aliaspages:read / pages:writeA 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 & pathPurpose
GET /api/public/groups/{id}Public group profile
GET /api/public/groups/{id}/pagesPublic group's pages
GET /api/public/pages/{id}A public page
GET /api/public/notes/{id}A public note
GET /api/public/pages/{id}/commentsA public page's comments
GET /api/public/notes/{id}/commentsA public note's comments
GET /api/public/attachments/{attachmentId}Public attachment metadata
GET /api/public/attachments/{attachmentId}/downloadPublic 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 & pathScopePurpose
GET /api/runs?target_type=…&target_id=…runs:readRun history for a note or page (last 10)
POST /api/runsruns:writeCreate a run (server or client execution)
POST /api/runs/{id}/completeruns:writeSubmit 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 & pathScopePurpose
GET /api/chatschatList your chat threads
POST /api/chatschatStart a new thread
GET /api/chats/{id}chatLoad a thread with its messages
POST /api/chats/{id}/messageschatAdd 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 & pathScopePurpose
GET /api/meaccount:readYour identity + profile
PATCH /api/meaccount:writeUpdate profile (name, timezone, editor, …)
GET /api/spaces/{spaceId}account:readA space's AI-org settings + page suggestions
PATCH /api/spaces/{spaceId}account:writeCurate a space's page suggestions
GET /api/usageusage:readQuota counts + AI credit balances
GET /api/usage/credit-transactionsusage:readAI credit ledger (newest first)

Meta

Method & pathPurpose
GET /api/openapi.jsonThe served OpenAPI 3.1 spec
GET /.well-known/oauth-authorization-serverOAuth discovery metadata
GET /.well-known/oauth-protected-resourceOAuth 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-scopes values 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}/restore and POST /api/pages/{id}/restore for soft-deleted items.
  • Pictures: PUT /api/me/picture and PUT /api/groups/{id}/picture (today, clients upload directly to storage).
  • Reliability: Idempotency-Key support 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 and expand= on list endpoints.
Thu 2:41pm
Comments
Loading comments…