Extension and MCP API reference

Client API version 1. An extension declares the scopes it needs in its manifest; every call checks them, and a call outside them fails with a scope error. The same scopes authorize MCP clients and scoped REST tokens.

Scopes

Scope Covers
read:notes Read pages, their bodies, tags and links
write:notes Change pages, their bodies, tags and links
read:tasks Read tasks, subtasks, captures and the parser
write:tasks Change tasks, subtasks, captures and the parser
read:projects Read projects
write:projects Change projects
read:routines Read routines, their steps and runs
write:routines Change routines, their steps and runs
read:tags Read the workspace's tags
write:tags Change the workspace's tags
read:calendar Read calendar events
write:calendar Change calendar events
read:focus Read focus sessions
write:focus Change focus sessions
read:notifications Read notifications
write:notifications Change notifications
read:workspace Read the workspace's name, members' names, preferences, today, the week report and the change feed
write:workspace Change the workspace's name, members' names, preferences, today, the week report and the change feed
read:settings Read preferences, devices, passkeys, webhooks, members, invitations and connections
write:settings Change preferences, devices, passkeys, webhooks, members, invitations and connections
read:extensions Read the records every extension stores, such as recipes and people
write:extensions Change the records every extension stores, such as recipes and people
publish:notes Publish and unpublish pages

A token made before scopes existed, or minted without a list, holds every scope. Mint a scoped token with POST /api/v1/settings/devices ({ "name", "scopes": [...], "kind": "extension", "extension_id" }); the list must be a subset of the caller's own scopes. A scoped token is bound to the workspace it was minted in and is refused in any other.

Client API (aquamarine)

Every method is asynchronous and returns a promise. Arguments are listed in order. Records have the shape the REST API answers.

aquamarine.notes

Method Scope Returns Description
notes.list() read:notes Note[] without bodies Every active page of the workspace, newest first.
notes.get(id) read:notes Note with body One page, loading its body when the app has not yet.
notes.search(query) read:notes Note[] Pages whose title or synced body contains the query.
notes.create({ title, body }) write:notes Note Makes a page on the server.
notes.update(id, { title, body, tag_names }) write:notes Note Changes a page on the server.

aquamarine.tasks

Method Scope Returns Description
tasks.list() read:tasks Task[] The tasks the app has synced, open ones first.
tasks.get(id) read:tasks Task One task.
tasks.create({ title, notes, due_on, scheduled_at, priority, project_id, tags }) write:tasks Task Makes a task on the server.
tasks.update(id, attrs) write:tasks Task Changes a task on the server.
tasks.complete(id) write:tasks Task Marks a task done.
tasks.reopen(id) write:tasks Task Reopens a done task.

aquamarine.routines

Method Scope Returns Description
routines.list() read:routines Routine[] with steps Every routine the app has synced.
routines.get(id) read:routines Routine One routine with its steps.
routines.start(id) write:routines RoutineRun Starts a run of the routine.

aquamarine.tags

Method Scope Returns Description
tags.list() read:tags { name, count }[] Tags in use on synced pages and tasks, most used first.

aquamarine.workspace

Method Scope Returns Description
workspace.info() read:workspace { name, user, timezone } The workspace's name, the signed-in person's name and the workspace timezone.
workspace.layout() read:workspace Layout The saved pane layout (the layout preference).
workspace.view() read:workspace { route, pageId } What the active tab shows.
workspace.open(route) none Opens a route in the active tab, such as today or page/<id>. Only works while one of the extension's commands runs, so the person started it.

aquamarine.settings

Method Scope Returns Description
settings.get() none object The extension's own saved settings on this device.
settings.set(values) none object Replaces the extension's saved settings on this device.

aquamarine.records

Method Scope Returns Description
records.list(collection, { query, filter, limit, offset }) none Record[] The extension's own records in a collection its manifest declares, newest first. query matches any text; filter keeps records whose data contains it; offset skips that many matches, for the next page.
records.get(collection, id) none Record One of the extension's records.
records.create(collection, data) none Record Stores a record; the server checks data against the collection's schema.
records.update(collection, id, data) none Record Merges data into the record; a field set to null is removed.
records.delete(collection, id) none Deletes one of the extension's records.

Registrations

Call Arguments Description
aquamarine.widgets.register { kind, name, render } Adds a widget to the tray. render({ view, note }) returns HTML; it runs again when the workspace or the active tab changes. view is what the active tab shows when the extension holds read:workspace, else an empty route. note is the open page when the extension holds read:notes. Elements with data-command="<id>" run that command when clicked.
aquamarine.commands.register { id, name, run } Adds a command to the palette (Ctrl+K), shown as ": ".
aquamarine.views.register { id, name, icon, render, action } Adds a tab to the sidebar. render({ path }) returns HTML for the view; path is what follows the view in the route ext/<extension>/<view>/<path>. Views may use forms and tables; the app cleans the HTML as it does a widget's, removing href (use data-url for a web page, opened in the browser), styles and any form field named name or id after a form property. A field named x[] arrives as a list. An element with data-go="<path>" opens that path; a form with data-action="<name>" calls action(name, fields, { path }) on submit, and a button, select or input with it calls action(name, { value }, { path }) on click or change, where value is its data-value or its value. A checkbox's value is true or false, with its data-value as item. action may return { go: path, toast }, where toast is text for the status line or { text, undo: { action, value, label } } for one with a button that runs that action. An element with data-key="<key>" is clicked when that key is pressed outside a text field, and one with data-note="<page id>" opens that page. The view renders again after an action and when the extension's records change. icon is one of chef-hat, users, book, heart, star, box.
aquamarine.ui.crud { id, name, icon, collection, singular, title, subtitle, columns, sorts, filters, pageSize, fields, defaults, detail, beforeSave, action } Registers a whole view over one collection: a searchable list, a record page, a form for new and edited records built from the collection's schema, and Delete with a confirmation and Undo. See components.
aquamarine.ui.list { id, placeholder, text, sorts, filters, pageSize } A search box, sort menu and filter menus for a view's list, run by the app so the view's action never sees their actions. Returns { apply(items), search, sort, filter(id), clearAction }; apply answers { items, total, active, toolbar, more }. See components.
aquamarine.ui.table items, { id, columns, row, empty } A table whose column headers sort it when it has an id. Each column is { id, label, value(item), html(item), align, sort }. See components.
aquamarine.ui.form collection or schema, values, { action, submit, cancel, title, fields, errors, error, confirm } A form built from a JSON Schema or a declared collection's schema. Read what it sends with ui.readForm. See components.
aquamarine.ui.readForm collection or schema, fields, { fields } Turns the fields a ui.form sent into { data, errors, ok } that match the schema; an emptied field is null.
aquamarine.ui.html template literal A tagged template that escapes every value except the output of another ui call or ui.raw(text). Lists are joined; null, undefined and booleans render nothing. ui.escape(text) escapes one value.
aquamarine.ui.confirm { label, action, value, message, body, confirm, danger, small } A button that asks first, in the app's own dialog, then calls action.
aquamarine.ui.date value, { relative } A <time> for an ISO date or time: "5 Nov", or "in 3 days" with relative, with the full date on hover.
aquamarine.ui.stats [{ label, value, hint }] A row of number tiles.
aquamarine.ui.progress value, max, { label, text } A progress bar with "value / max" or text beside it.
aquamarine.ui.checklist items, { action, label(item), checked(item), value(item) } Checkboxes that call action(name, { value, item }) when ticked or unticked.
aquamarine.ui.note id, title A link that opens one of the workspace's pages.
aquamarine.ui.notePicker name, pageId, { placeholder, required } A form field for choosing a page by title; it sends the page's id. The extension sees only the page it is given.
aquamarine.ui.tagPicker name, tags, { placeholder } A form field for tags, suggesting the workspace's own; it sends name[], a list.
aquamarine.ui.sortable items, { action, label(item), value(item) } A list the person reorders by dragging or with Alt+Up and Alt+Down; action gets { order }, the items' values in their new order.
aquamarine.ui.chart { type, data, label, format } A bar, line or spark chart the app draws, with a table for screen readers. data is numbers or { label, value }. Works in widgets too.
aquamarine.events.on name, handler Listens for change (the synced records changed) or view (the active tab changed; the handler gets { route, pageId }; needs read:workspace). Returns a function that stops listening.

Extension records

A manifest's collections name the records an extension keeps, each with a JSON Schema every record must match; its tools turn actions on them into MCP tools. The app registers both with the server when the extension is enabled. Records are stored encrypted, scoped to the workspace.

Request Answers
GET /api/v1/extensions The registered extensions, with their collections and MCP tool names
PUT /api/v1/extensions/<id> Registers { name, version, collections, tools }; needs write:settings
DELETE /api/v1/extensions/<id> Removes the registration and its tools; the records stay
GET /api/v1/extensions/<id>/records/<collection> Records, newest first; query matches any text, filter (a JSON object) keeps records that contain it, limit up to 500, offset skips that many matches
POST /api/v1/extensions/<id>/records/<collection> Creates a record from { data }
GET /api/v1/extensions/<id>/records/<collection>/<record id> One record
PATCH /api/v1/extensions/<id>/records/<collection>/<record id> Merges { data } into the record; a null field removes it
DELETE /api/v1/extensions/<id>/records/<collection>/<record id> Deletes the record

A record answers { id, extension_id, collection, data, created_at, updated_at }. An extension's own token reaches its own records without a scope and no one else's; any other token needs read:extensions or write:extensions. A collection holds at most 5000 records of at most 256 KB each.

Each declared tool becomes the MCP tool <id>_<name> (dashes in the id become underscores), shown to tokens with read:extensions for list and get and write:extensions for the rest. list takes query, filter, limit and offset; get and delete take id; create takes the collection's schema; update takes id and any of the schema's fields.

MCP server

POST /mcp on the Rails app is a streamable HTTP MCP endpoint: one JSON-RPC message per request, answered in full. Send Authorization: Bearer <token> and X-Aquamarine-Account: <workspace id> (the header is optional with one workspace or a scoped token). Only bearer tokens sign in. A call whose scope the token lacks is refused with HTTP 403 and { "error", "scopes" } before it reaches the server; tools/list and resources/templates/list show only what the token can use.

{ "mcpServers": { "aquamarine": { "type": "http", "url": "https://<server>/mcp",
  "headers": { "Authorization": "Bearer <token>", "X-Aquamarine-Account": "<workspace id>" } } } }

Tools

Tool Scope Arguments Description
notes_list read:notes query?, limit? List active notes, newest first, without bodies. query searches titles and bodies.
notes_get read:notes id A note with its body, tags and links.
notes_create write:notes title, body?, parent_id?, tags? Create a note.
notes_update write:notes id, title?, body?, parent_id?, tags? Change a note's title, body, parent or tags.
notes_publication_get read:notes id A note's publication, or null, and the links that would not work for visitors if it were published now.
notes_publish publish:notes id, confirm?, display?, password?, expires_at?, slug?, domain_id? Publish a note, or republish it with its current text. Keeps the URL and settings. When links would not work for visitors it fails with the list; call again with confirm: true to publish anyway.
notes_unpublish publish:notes id, confirm_takedown? Take a published note offline. When other published notes link to it, it fails with their list; call again with confirm_takedown: true.
tasks_list read:tasks status?, date? List top-level tasks. status is inbox, planned or done; date lists the tasks scheduled on a day.
tasks_get read:tasks id A task with its subtasks.
tasks_create write:tasks title, notes?, priority?, due_on?, scheduled_at?, duration_minutes?, project_id?, page_id?, parent_id?, tags? Create a task.
tasks_update write:tasks id, title?, notes?, priority?, due_on?, scheduled_at?, duration_minutes?, project_id?, page_id?, parent_id?, tags? Change a task's fields.
tasks_complete write:tasks id Mark a task done.
tasks_reopen write:tasks id Reopen a done task.
routines_list read:routines none List active routines with their steps.
routines_get read:routines id A routine with its steps and the id of its running run.
routines_start write:routines id, finish_by? Start a routine, or answer the run already going.
search any read: scope query Search notes, tasks, projects and routines; only the families the token can read are searched.

Resources

URI Scope Content
aquamarine://notes/<id> read:notes One note as JSON
aquamarine://tasks/<id> read:tasks One task as JSON
aquamarine://routines/<id> read:routines One routine as JSON

resources/list lists the active notes of a token holding read:notes.