# Roma developer docs Source: https://roma.app/developers # Roma for developers and agents Connect the AI chat you already use to Roma through its MCP server: one address, OAuth 2.1, 29 tools over tasks, notes, projects, lists and automations. ## What Roma is, for an agent Roma is an AI task app. A person keeps their tasks, notes, projects, lists and automations in it, on the web, on a Mac and on an iPhone. The MCP server gives the AI chat they already use the same workspace: what is due today, what they wrote down last week, the list they are filling, the standing orders that run for them each morning. An agent connected to Roma can: - Orient itself in one call. `get_context` returns the person, today in their timezone, what they asked Roma to remember, their projects, what is due, recent notes, their lists and automations, and three days of activity. - Keep work. Create tasks with a due date and a project, save a plan as a note, add twenty rows to a list at once. - Pick work up again. Search notes, tasks and list rows by meaning or keyword, read a task with its subtasks, and update its status as the work moves. - Read what ran overnight. List the person's automations, read what each run found, and start one when asked. - Undo. Every delete is soft and restorable for about 30 days. Everything is scoped to the signed-in person and private to them. There is no shared or team data on this surface. ## One address, every client The server speaks the Model Context Protocol over Streamable HTTP at `https://api.roma.app/mcp`. Sign-in is OAuth 2.1 with PKCE. A client that follows the protocol's discovery needs nothing else: point it at the address, sign in to Roma in the browser, approve, and the tools are there. [Connect a client](/developers/connect) has the steps for ChatGPT, Claude, Claude Code, Cursor, Codex, VS Code, Poke, Town, Grok and others. For a tool that speaks HTTP rather than MCP, the same operations are a [REST API](/developers/api) under `https://api.roma.app/api/v1`, described by an OpenAPI document, with an [API key](/developers/authentication#api-keys) as the bearer. ## How the server behaves Roma tells every connected model the same eight facts on connect. They are worth knowing before you read the tool reference: - `get_context` returns the current state of the workspace in one call. - `search` covers notes, tasks, lists and list rows by meaning or keyword. What the person refers to ("my tax note", "the wine list") usually already exists, and a second copy drifts from the first. - A note is prose; a collection is a table. Rows go in with `add_collection_items` and are corrected with `update_collection_items`, many rows per call. - `update_note` and `update_task` append to the body by default. Replacing a whole body takes `mode: "replace"` plus `confirmReplace: true`. - Every delete is soft: an item stays recoverable for about 30 days (`list_deleted`, `restore_deleted`). - An automation's run happens inside Roma and its result lands in the person's Roma chat, not in the calling conversation; `get_automation_runs` reads it back. ## Safety - The connection acts only as the signed-in person, only on their own workspace. - Every tool declares whether it reads, adds, or updates and removes. Clients run read-only tools freely and ask before the rest; the person can allow more in their client's settings. - Deletes go to a 30-day trash. Whole-body replacements need an explicit confirmation flag. - Three tools reach outside Roma: `run_automation`, because an automation can act through the person's connected apps, and the two row tools, because they download images from public URLs you name. Every client asks before `run_automation`. - What an agent writes is recorded in the person's event log as their own action, marked as coming through this connection. ## Where next - [Connect a client](/developers/connect): the two-minute setup for each app. - [Authentication](/developers/authentication): the OAuth 2.1 flow, discovery, tokens and what to do when sign-in fails. - [Concepts](/developers/concepts): tasks, notes, projects, collections, automations, memory and the trash. - [Tools](/developers/tools): every tool with its parameters, result and an example call. - [REST API](/developers/api): the same operations over plain HTTP, with the OpenAPI document. - [Guides](/developers/guides): making Roma the list your assistant reaches for, saving research, working from a task. - [Changelog](/developers/changelog): what changed on the server. Every page here is also plain Markdown: add `.md` to its address, or read [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt). # Connect a client The address is the same everywhere: `https://api.roma.app/mcp`. You need a Roma account; the first connection opens a browser where you sign in to Roma and approve the app. Nothing is pasted, no key is copied. Every client below speaks the Model Context Protocol over Streamable HTTP with OAuth 2.1, which is all the server needs. If yours is not listed, point it at the address and it will most likely work; [authentication](/developers/authentication) says what it has to support. To check any connection, ask: "What's on my plate today in Roma?" The answer should use your own task titles. ## ChatGPT ChatGPT connects a custom MCP server through Developer mode. Which plans can add one, and whether it may make changes or only read, depends on your plan and your workspace's settings. 1. Open Settings, then Security and login, and turn on Developer mode. 2. Go to chatgpt.com/plugins, choose the plus, and enter a name (Roma), a short description and the server address `https://api.roma.app/mcp`. Choose OAuth. 3. Sign in to Roma and approve. Then start a new chat: an existing conversation keeps the tool list it started with. Ask for Roma by name in a message (`@Roma`) or just ask about your tasks. ChatGPT confirms before a tool changes anything; the plugin's permission setting lets you relax that. ## Claude Claude.ai, Claude Desktop and Claude on your phone share one connector list; a connector added on the web is available everywhere you are signed in, including Claude Code and Cowork. 1. Open Customize (or Settings), then Connectors, then Add custom connector. 2. Name it Roma, paste `https://api.roma.app/mcp`, and add it. 3. Choose Connect, sign in to Roma, and approve. On a Team or Enterprise plan an owner adds the connector under Organization settings, then Connectors, and members connect their own account. In a conversation, Roma is on under the plus menu's Connectors. Under Customize, then Connectors, each tool can be set to Always allow, Needs approval or Blocked; read-only tools run without asking by default. ## Claude Code ```bash claude mcp add --transport http roma https://api.roma.app/mcp ``` Then run `/mcp` inside Claude Code and choose Roma to sign in. Add `--scope user` to keep the server across every project. Claude Code lists the tools again at the start of every session, so a new tool appears next session. ## Cursor Use the one-click link, [Add Roma to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=roma&config=eyJ1cmwiOiJodHRwczovL2FwaS5yb21hLmFwcC9tY3AifQ==), or add this to `~/.cursor/mcp.json` (or the project's `.cursor/mcp.json`) and sign in when Cursor asks: ```json { "mcpServers": { "roma": { "url": "https://api.roma.app/mcp" } } } ``` ## Codex ```bash codex mcp add roma --url https://api.roma.app/mcp codex mcp login roma ``` The login opens the browser for the sign-in. The same entry serves the Codex CLI and the Codex app inside ChatGPT's desktop app. ## VS Code Use the install link, [Add Roma to VS Code](vscode:mcp/install?%7B%22name%22%3A%22roma%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.roma.app%2Fmcp%22%7D), or from a terminal: ```bash code --add-mcp '{"name":"roma","type":"http","url":"https://api.roma.app/mcp"}' ``` The equivalent `.vscode/mcp.json`: ```json { "servers": { "roma": { "type": "http", "url": "https://api.roma.app/mcp" } } } ``` ## Gemini CLI ```bash gemini mcp add --transport http roma https://api.roma.app/mcp ``` Then `/mcp auth roma` inside the CLI to sign in. ## Poke At poke.com/integrations/new, add Roma with the address `https://api.roma.app/mcp`. Leave the key field empty to sign in with OAuth, or paste an [API key](/developers/authentication#api-keys) from Settings, then Connections. From a terminal: ```bash npx poke@latest mcp add https://api.roma.app/mcp -n "Roma" ``` Mention Roma by name in a message the first time so Poke reaches for it. ## Town Open Settings, then MCP, then Add Server, and enter a name and the address `https://api.roma.app/mcp`. Town runs the sign-in itself. ## Grok At grok.com/connectors, choose New Connector, then Custom, and enter the address `https://api.roma.app/mcp`. Complete the sign-in when Grok asks. Connectors are available on Grok's paid tiers. ## OpenClaw ```bash openclaw mcp add roma --url https://api.roma.app/mcp --transport streamable-http openclaw mcp login roma ``` `openclaw mcp doctor roma --probe` checks the connection. The login uses a browser where OpenClaw can open one and a pasted code where it cannot. ## Zed Open Settings, then AI, then MCP Servers, then Add Remote Server, and enter the address. Or in `settings.json`: ```json { "context_servers": { "roma": { "url": "https://api.roma.app/mcp" } } } ``` Zed asks for the sign-in when the server has no header. ## Devin Desktop (Windsurf) In `~/.config/devin/mcp_config.json`, add a server with `"serverUrl": "https://api.roma.app/mcp"` under `mcpServers`, named `roma`. ## Other clients Any client that supports remote MCP servers over Streamable HTTP with OAuth works. Add the server with the address `https://api.roma.app/mcp` and no headers; the client discovers the sign-in from the server's first `401` answer. If a client offers to enter a client id and secret by hand, leave them empty: the server registers clients dynamically. ## Without a browser: an API key A scheduled job, a self-hosted agent or a client that only takes a pasted key connects with an API key instead of signing in. In Roma, open Settings, then Connections, press Create key at the bottom, and copy it once. Then send it as the bearer token: ```bash claude mcp add --transport http roma https://api.roma.app/mcp --header "Authorization: Bearer roma_…" ``` The key acts as you on your own workspace, so keep it as private as a password and revoke it in the same place when the agent no longer needs it. [Authentication](/developers/authentication#api-keys) has the details. ## Get the most out of it Add one line to your chat's custom instructions (or a Claude project's instructions), so every conversation starts from where you are: > At the start of a conversation about my work, plans or anything I keep, use Roma's get_context to see my tasks, notes and what I asked it to remember. Save tasks and notes to Roma when I ask. Then try: - "What's on my plate today?" - "Save the plan we just made as a note in my Trip project." - "Add these five books to my reading list." - "What did my morning briefing find today?" - "Mark the dentist task done and remind me about the invoice on Friday." ## Disconnect Remove Roma in your chat app's settings. That deletes the token the app holds. To have the grant revoked on our side too, email [hello@roma.app](mailto:hello@roma.app). Signing out of the Roma web app does not disconnect an AI app, and disconnecting an AI app does not sign you out of Roma. # Authentication Roma's MCP server accepts two kinds of bearer token on the same endpoint. - **OAuth 2.1**, the default and what every client with a browser uses. The client discovers the authorization server from the MCP endpoint, registers itself, sends the person to sign in and approve in their browser, and receives a short-lived token it presents on every call. This is the flow the Model Context Protocol specifies, and the one ChatGPT, Claude, Cursor, Codex and the other clients implement. - **An API key**, for an agent that has no browser to open: a scheduled job, a self-hosted agent, a client that only takes a pasted key. The person creates it in Roma under Settings, then Connections, and pastes it where the client asks for a bearer token. See [API keys](#api-keys) below. Either way the token acts as the whole person on their own workspace; the tools and their behaviour are the same. ## The flow 1. The client calls `https://api.roma.app/mcp` without a token. The server answers `401` with a `WWW-Authenticate` header that names the protected-resource metadata: `https://api.roma.app/.well-known/oauth-protected-resource`. 2. The client reads that document. It names the resource (`https://api.roma.app`) and one authorization server. 3. The client reads the authorization server's metadata at its `/.well-known/oauth-authorization-server` address and learns the authorize, token and registration endpoints. 4. The client registers itself with dynamic client registration (RFC 7591) as a public client and starts the authorization-code flow with PKCE (`S256`). 5. The person's browser opens Roma's sign-in, then the consent screen at `https://roma.app/oauth/consent`. It names the app and where the app returns them after approval ("claude.ai", "chatgpt.com", "an app on this computer"), so a look-alike cannot pretend to be a real client. 6. The authorization server issues an access token and a refresh token. The client retries the call with `Authorization: Bearer ` and every tool is scoped to that person. The client configuration is therefore header-less: an address and nothing else. ## What a client has to support - The Model Context Protocol over Streamable HTTP. The server negotiates protocol versions 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 and 2024-10-07. - OAuth 2.1 authorization-code flow with PKCE `S256`. Plain PKCE is accepted but not recommended. - Dynamic client registration. The server has no fixed client ids today; a client that can only use a pre-registered id connects with an [API key](#api-keys) instead, or you write to [hello@roma.app](mailto:hello@roma.app). - A redirect URI reachable from the person's browser: an `https` address on the client's own host, or a loopback address for a command-line tool. Redirect URIs are matched exactly, including the port. - Following the `401` into discovery. A client that expects to be told the authorization server up front has to read it from the protected-resource metadata instead. ## Tokens - **Access tokens** are JWTs signed with ES256 and valid for one hour. The server verifies the signature and expiry locally on every call. - **Refresh tokens** are issued on every authorization. Refresh proactively before the access token expires; the token endpoint accepts `grant_type=refresh_token` with the client id and no secret. - **Scopes** do not narrow what a token can do. Every token acts as the whole person on their own workspace; what a client may run without asking is decided by the tool annotations on the client's side, not by scope. Request `openid email offline_access` or leave the scope out. - **Revocation.** Removing the connector in the client deletes its token. The person can also revoke a grant on Roma's side by writing to [hello@roma.app](mailto:hello@roma.app). A revoked or expired refresh token answers a `400` from the token endpoint; treat any `400` there as "sign in again", not as something to retry. ## API keys A key is for the client that cannot open a browser. It is created in Roma (Settings, then Connections, at the bottom), shown once, and sent on every call as a bearer token: ```http POST https://api.roma.app/mcp Authorization: Bearer roma_… Content-Type: application/json ``` - A key starts with `roma_` and is 48 characters long. Roma stores only its hash, so a key that is lost cannot be shown again; create a new one and revoke the old. - A key acts as the whole person on their own workspace, exactly like an OAuth token. Keep it as private as a password, out of prompts, shared configs and repositories. - Settings holds one key at a time: creating a new key replaces the old one, which answers `401` on its next call. Revoking takes effect the same way. - The same rate limit applies per key as per OAuth token. - A client that supports both should prefer OAuth: the person approves it in their own browser, the token is short-lived, and nothing has to be pasted. In a client that asks for a header rather than a bearer field, the header is `Authorization: Bearer roma_…`. Claude Code, Cursor, VS Code, Codex, Gemini CLI, OpenClaw and Poke each have a place for it; ChatGPT and Claude.ai do not, and use OAuth. The same key opens the [REST API](/developers/api). ## Rate limits Each token may make 60 requests a minute. Above that the server answers `429` with a `Retry-After` header and a JSON-RPC error. A platform-level limit per IP address sits above that. Tool lists are cheap; `search` runs an embedding per query and is the call to pace. ## What the token can reach Only the person's own tasks, notes, projects, collections, automations, trash and the read-only view of their memory. Not their connected apps, not their email, not other people's shared items, not Roma's own instructions to its model. Those stay inside Roma. ## Troubleshooting | What you see | What it means | What to do | |---|---|---| | The client never opens a browser | It did not follow the `401` into discovery, or it expects a static client id | Use an API key, or a client that supports dynamic registration | | "redirect_uri mismatch" at sign-in | The URI the client registered and the one it sent differ, often only by port | Let the client register again; for a command-line tool, pin its loopback port if it offers to | | Sign-in succeeds, tools fail with `401` | The access token expired and the refresh failed | Remove and re-add the connector | | Tools fail with `429` | More than 60 calls a minute on one token | Wait for `Retry-After`; batch list reads through `get_context` | | A tool you expect is missing | The client cached an older tool list | Refresh the connector in the client and start a new conversation | | The consent screen names the wrong app | Somebody else's client registered under a familiar name | Do not approve; the screen says where approval would return you | ## For the curious The authorization server is Roma's own identity provider, the same sign-in the web and iOS apps use, so a person has one account and one set of sessions. The consent screen is Roma's, the tokens are the same kind the apps carry, and the MCP server verifies them without a network round-trip. An API key is verified by a hash lookup on the same request path. The trade-off is that the server does not yet publish client-id metadata documents or resource indicators; when it does, this page will say so. # Concepts Roma has a small number of things a person keeps, and every tool works on one of them. The names below are the ones the tools use. ## Tasks A task is a to-do: a title, an optional Markdown body, a status, a priority and an optional due time. It lives in one project or is unsorted. - **Status** is one of `todo`, `inProgress` and `completed`. Move a task to `inProgress` when you start working on it and to `completed` when it is done; the person sees the change on every device within a second. - **Priority** is `low`, `medium` or `high`. Most tasks carry none. - **Due** is an ISO instant. A due time at midnight in the person's zone is read as a due day; a time of day is a moment, and the person is reminded then. - **Subtasks** are tasks with a parent. `get_task_context` returns a task with its subtasks and its project, the picture you need before picking work up. - A task's body is Markdown. `update_task` appends to it by default; a full replacement needs `mode: "replace"` and `confirmReplace: true`. ## Notes A note is prose the person keeps: a plan, meeting notes, a draft, a reference. It lives in a project or is unsorted, and its body is Markdown: headings, lists, task lists, bold, links. - `create_note` stores what you send as written. No cleanup pass runs on it. - `update_note` appends by default. `prepend`, `insertAfter` (place text after a line you name) and `replace` are the other modes; `replace` needs `confirmReplace: true`. - A note a meeting was recorded into also returns the attributed transcript through `get_note` once the recording is done. ## Projects A project groups tasks, notes and collections. Every item is in exactly one project or in none. There are no tags. - `list_projects` resolves names to ids. Do that before filing anything: a task filed under a guessed id lands nowhere. - `create_project` and `update_project` create and rename. One project, Life Admin, is Roma's own and cannot be created or written to from outside. - Moving a task is `update_task` with a `projectId`; `null` moves it to unsorted. ## Collections A collection is a typed list: a table with named columns, one row per thing. A reading list, a list of contacts, expenses, wines. - **Columns** have a type: `text`, `number`, `date`, `url`, `select` or `multiSelect`. A select or multiSelect column has options, each with a stable id. - **Rows** carry values keyed by column id. A select value is an option id and a multiSelect value is an array of them; when adding rows you may send the option's label and an unknown label becomes a new option. - `list_collections` returns every list with its columns and their ids, the ids you need before writing rows. `get_collection_items` pages through a list's rows, newest first, 200 per page. - `add_collection_items` adds up to 200 rows in one call. Without `matchOn` every row is inserted, so a retried batch duplicates; pass `matchOn` to merge into rows that match on a column. - `update_collection_items` corrects up to 200 rows of one list per call. Values merge per row: only the keys you send change, and an empty string clears a cell. - A row can be archived (set aside, hidden from the list, reversible) or deleted (to the trash). - Column editing is not available from outside Roma; a list's name and instructions are. - Images on rows come from URLs or from `create_image_upload`, which hands back short-lived upload slots for raw bytes. ## Automations An automation is a standing order: something Roma does on its own on a schedule or when started, working from the person's connected apps and everything in Roma. "Every weekday at 8, read my inbox and leave a digest in my chat." - `list_automations` returns each live automation with its schedule, its next run and the last run's status. - `get_automation_runs` returns an automation's recent runs: what it did, the closing message it left, any error. - `run_automation` starts one now. The run happens inside Roma, can act through the person's connected apps, and its result lands in the person's Roma chat, not in your conversation. Read it back with `get_automation_runs`. - Creating or updating an automation is not available from outside Roma. The person sets those up in the app, where an approval card shows the schedule and the steps. ## Memory The small set of facts Roma knows about the person before every turn: where they live, what they are working on, how they like things done. `get_context` returns it read-only, so an outside conversation can be consistent with the one inside Roma. Writing to it stays in Roma. ## The trash Every delete is soft. `delete_task`, `delete_note`, `delete_collection` and `delete_collection_items` tombstone the item, and `list_deleted` shows the trash with the day each item is purged, about 30 days on. `restore_deleted` brings one item back per call; restoring a collection restores the rows deleted with it. An already-live item is a success, not an error. ## Context `get_context` is the one call to make at the start of a conversation. It returns: - the person's name, timezone, today's date, weekday and local time - what they asked Roma to remember, read-only - their projects - tasks that are overdue, due today, in progress or due in the next seven days, each once - the newest unsorted tasks and how many there are - recent notes - collections with their columns - automations with their last run - three days of activity Every item carries its id, so the next call needs no lookup. Optional `focusProjectId` narrows the task sections to one project. ## Identity and audit What an agent writes is recorded in the person's event log as their own action, since they asked for it, and is marked as coming through this connection. Body edits are versioned, so a bad edit can be undone in Roma. # Tools Every tool the Roma MCP server offers, as a client sees it over `tools/list`. 29 tools, in 7 groups. Each one acts as the signed-in person, on their own workspace only, and returns both a text and a structured result. This page is generated from the server's live registration. What you read here is what your client's model reads. ## At a glance - **Context**: [`get_context`](#get_context) - **Tasks**: [`list_tasks`](#list_tasks), [`get_task_context`](#get_task_context), [`create_task`](#create_task), [`update_task`](#update_task), [`delete_task`](#delete_task) - **Projects**: [`list_projects`](#list_projects), [`create_project`](#create_project), [`update_project`](#update_project) - **Notes and search**: [`search`](#search), [`list_notes`](#list_notes), [`get_note`](#get_note), [`create_note`](#create_note), [`update_note`](#update_note), [`delete_note`](#delete_note) - **Collections**: [`list_collections`](#list_collections), [`get_collection_items`](#get_collection_items), [`create_collection`](#create_collection), [`update_collection`](#update_collection), [`delete_collection`](#delete_collection), [`add_collection_items`](#add_collection_items), [`update_collection_items`](#update_collection_items), [`delete_collection_items`](#delete_collection_items), [`create_image_upload`](#create_image_upload) - **Trash**: [`list_deleted`](#list_deleted), [`restore_deleted`](#restore_deleted) - **Automations**: [`list_automations`](#list_automations), [`get_automation_runs`](#get_automation_runs), [`run_automation`](#run_automation) **Reading the badges.** `Read-only` tools change nothing and a client can run them without asking. `Adds data` tools create something new and never touch what exists. `Updates or removes existing data` tools update or delete, and clients ask before each call by default. `Safe to retry` marks a call that lands once however often it is repeated. `Reaches outside Roma` marks a tool that touches something beyond the workspace: `run_automation` can act through the person's connected apps, and the two row tools download images from public URLs you name. **Ids.** Every task, note, project, list, row and automation has a UUID. Tools that take an id answer "not found" for an id that belongs to someone else. Get ids from `get_context`, the list tools or `search`; never guess one. **Time.** Instants are ISO 8601 with a zone. A window such as `createdAfter` and `createdBefore` includes its lower bound and excludes its upper bound. The person's timezone and today's date come from `get_context`. **Errors.** A failed call returns `isError: true` with one sentence that says what to change. Nothing is thrown. ## Context One call that orients a conversation: who the user is, what is due, recent notes, lists, automations and what they asked Roma to remember. ### get_context **Get workspace context** · `Read-only` Use this at the start of a conversation that involves the user's Roma workspace, or when you need an overview of it. Returns, in one call: how Roma works; the user (name, timezone, today's date and local time); what they asked the AI to remember about them; their projects; overdue, due-today, in-progress and upcoming tasks; unsorted tasks; recently touched notes; their collections with columns; their automations with the last run; and the last few days of activity. Everything comes with ids for follow-up calls. It is an overview, not the full data: use search, list_tasks, get_note or get_collection_items for depth. Pass focusProjectId to scope the task sections to one project. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `focusProjectId` | `string` | no | Optional project UUID. Scopes the task sections to that project; the rest stays workspace-wide. | **Returns** - `about` (`string[]`) - `user` (`object`) - `unavailable` (`string[]`) **Example** ```json { "method": "tools/call", "params": { "name": "get_context", "arguments": {} } } ``` ## Tasks The to-do list. Create, list, read, update, move and delete tasks, each scoped to a project or left unsorted. ### list_tasks **List tasks** · `Read-only` List, filter, and search the user's tasks — the way to find work to pick up. Returns tasks ordered by due date, then priority. Scope to the whole workspace, unsorted tasks only, or a single project. By default completed tasks are excluded. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `scope` | `string` | no | "all" (default) for everything, "inbox" for unsorted items only (no project — the value keeps its legacy name), or a project UUID for that project. | | `query` | `string` | no | Free-text search across title and description. | | `taskStatus` | `"todo" \| "inProgress" \| "completed"[]` | no | Filter to these statuses. Omit to use the default active set (excludes completed). | | `dueAfter` | `string` | no | ISO 8601 instant — only tasks due on/after this (inclusive). | | `dueBefore` | `string` | no | ISO 8601 instant — only tasks due before this (exclusive). Today = dueAfter today 00:00 local, dueBefore tomorrow 00:00 local; overdue = dueBefore today 00:00 alone. | | `createdAfter` | `string` | no | ISO 8601 instant — only tasks created on/after this. | | `createdBefore` | `string` | no | ISO 8601 instant — only tasks created before this (exclusive). | | `updatedAfter` | `string` | no | ISO 8601 instant — only tasks last changed on/after this. | | `updatedBefore` | `string` | no | ISO 8601 instant — only tasks last changed before this (exclusive). | | `completedAfter` | `string` | no | ISO 8601 instant — only tasks completed on/after this. Implies completed tasks unless taskStatus says otherwise. | | `completedBefore` | `string` | no | ISO 8601 instant — only tasks completed before this (exclusive). | | `sort` | `"due" \| "created" \| "updated" \| "completed"` | no | Row order. Default 'due' (due date, then priority); a timestamp sort returns newest first. | | `limit` | `integer` | no | Max results (default 50). (1 to 200) | **Returns** - `count` (`number`) - `tasks` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "list_tasks", "arguments": {} } } ``` ### get_task_context **Get task context** · `Read-only` Fetch one task with everything needed to start working on it: the full task row (description, status, priority, due date) and the project it belongs to. The equivalent of opening an issue. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `artifactId` | `string` | yes | The task's UUID. | **Returns** - `task` (`object`) - `project` (`object | null`) **Example** ```json { "method": "tools/call", "params": { "name": "get_task_context", "arguments": { "artifactId": "" } } } ``` ### create_task **Create task** · `Adds data` Create a task. Omit projectId to leave it unsorted (no project); set projectId to file it under a project. The task is enriched by AI in the background after creation, so enriched fields may appear on a subsequent read. Returns the created task. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `title` | `string` | yes | Short, clear task title. | | `description` | `string` | no | Optional longer details. | | `projectId` | `string` | no | Project UUID. Omit to leave it unsorted (no project). | | `priority` | `"low" \| "medium" \| "high"` | no | | | `taskStatus` | `"todo" \| "inProgress" \| "completed"` | no | Defaults to 'todo'. | | `dueAt` | `string` | no | ISO 8601 due timestamp. | **Returns** - `id` (`string`) - `title` (`string | null`) - `description` (`string | null`) - `taskStatus` (`string | null`) - `priority` (`string | null`) - `dueAt` (`string | null`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `updatedAt` (`string | null`) - `editedAt` (`string | null`) - `completedAt` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "create_task", "arguments": { "title": "" } } } ``` ### update_task **Update task** · `Updates or removes existing data` · `Safe to retry` Update fields on an existing task — title, body, status, priority, due date, or which project it is in (projectId; null moves it out to unsorted). Use status transitions to report progress (e.g. todo → inProgress → completed). Only the fields you pass are changed. The body merge mode controls how `description` combines with the existing body — adding text appends/prepends; it never replaces unless you explicitly ask it to. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `taskId` | `string` | yes | The task's UUID. | | `title` | `string` | no | | | `description` | `string \| null` | no | Body text to merge in per `mode`. Omit to leave the body unchanged; null to clear it. | | `mode` | `"prepend" \| "append" \| "replace" \| "insertAfter"` | no | How `description` merges: 'append' (default, at the bottom), 'prepend' (at the top), 'insertAfter' (right after the line named in `anchor`), or 'replace' (swap the whole body — only when the user explicitly asked to rewrite it; never to add content; requires confirmReplace: true). | | `confirmReplace` | `boolean` | no | Required (true) for mode 'replace': confirms the user explicitly asked to swap the WHOLE body, deleting everything in it. Never pass it on the user's behalf without that explicit ask. | | `anchor` | `string` | no | For mode 'insertAfter' only: a verbatim snippet of the existing line to insert after. Errors if not found. | | `taskStatus` | `"todo" \| "inProgress" \| "completed"` | no | | | `priority` | `"low" \| "medium" \| "high" \| null` | no | | | `dueAt` | `string \| null` | no | ISO 8601 timestamp, or null to clear. | | `projectId` | `string \| null` | no | Move the task: a project UUID (from list_projects or get_context) files it there; null moves it out to unsorted. Omit to leave it where it is. | **Returns** - `id` (`string`) - `title` (`string | null`) - `description` (`string | null`) - `taskStatus` (`string | null`) - `priority` (`string | null`) - `dueAt` (`string | null`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `updatedAt` (`string | null`) - `editedAt` (`string | null`) - `completedAt` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "update_task", "arguments": { "taskId": "<taskId>" } } } ``` ### delete_task **Delete task** · `Updates or removes existing data` · `Safe to retry` Delete a task (or subtask) by id. This is a SOFT delete — the task is recoverable for ~30 days before it is purged. Deleting a task also removes it from search. Deletes one task per call so each delete is confirmed individually. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `artifactId` | `string` | yes | The task's UUID. | **Returns** - `deleted` (`boolean`) - `id` (`string`) - `title` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "delete_task", "arguments": { "artifactId": "<artifactId>" } } } ``` ## Projects Containers for tasks, notes and lists. Resolve names to ids before filing anything. ### list_projects **List projects** · `Read-only` List all of the user's projects. Use this to resolve a project name to its UUID before creating or filing a task, or to understand the workspace structure. **Parameters** This tool takes no parameters. **Returns** - `count` (`number`) - `projects` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "list_projects", "arguments": {} } } ``` ### create_project **Create project** · `Adds data` Create a project — a folder that groups the user's tasks and notes. Use it when the user wants a new project, or to give work a home that has none; check list_projects or get_context first so an existing project is not duplicated. Returns the project with its id, ready for create_task / update_task projectId. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | Project name, in the user's own words. | | `emoji` | `string` | no | Optional single emoji for the project. | **Returns** - `id` (`string`) - `name` (`string | null`) - `emoji` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "create_project", "arguments": { "name": "<name>" } } } ``` ### update_project **Update project** · `Updates or removes existing data` · `Safe to retry` Rename a project or change its emoji. Only the fields you pass change; emoji null removes it. Does not move or change the project's tasks. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `projectId` | `string` | yes | The project's UUID (from list_projects or get_context). | | `name` | `string` | no | New name. | | `emoji` | `string \| null` | no | New emoji, or null to remove it. | **Returns** - `id` (`string`) - `name` (`string | null`) - `emoji` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "update_project", "arguments": { "projectId": "<projectId>" } } } ``` ## Notes and search Prose the user keeps. Search finds notes, tasks and list rows by meaning or keyword; notes are read and written as Markdown. ### search **Search notes & tasks** · `Read-only` Search the user's Roma workspace — their notes, tasks, typed lists and the rows inside them — by meaning or keyword (hybrid semantic + keyword retrieval). Use this when the user asks about their own information, plans or anything they may have saved, and before adding something that may already exist. Their wording often won't match their notes' wording; the semantic arm bridges that gap (and across languages). Returns the best-matching passage per item; get_note returns a note's full text. An empty result means nothing relevant is saved. Not for general web knowledge. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | Natural-language query — what the user is trying to find, in their words. | | `limit` | `integer` | no | Max results (default 10). (1 to 20) | | `after` | `string` | no | Only items CREATED on/after this ISO 8601 timestamp. Use when scoping by when it was saved ("last week", "in March"). | | `before` | `string` | no | Only items CREATED before this ISO 8601 timestamp (exclusive). Pair with `after` to bound a window. | **Returns** - `count` (`number`) - `results` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "search", "arguments": { "query": "<query>" } } } ``` ### list_notes **List notes** · `Read-only` List the user's notes, newest first. Scope to the whole workspace, unsorted notes only, or a single project. Returns a snippet of each body; use get_note for the full text. This is browse-by-container — use search to find by topic. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `scope` | `string` | no | "all" (default) for everything, "inbox" for unsorted items only (no project — the value keeps its legacy name), or a project UUID for that project. | | `createdAfter` | `string` | no | ISO 8601 instant — only notes created on/after this. | | `createdBefore` | `string` | no | ISO 8601 instant — only notes created before this (exclusive). | | `updatedAfter` | `string` | no | ISO 8601 instant — only notes last changed on/after this. | | `updatedBefore` | `string` | no | ISO 8601 instant — only notes last changed before this (exclusive). | | `sort` | `"created" \| "updated"` | no | Row order, newest first on that stamp. Default created. | | `limit` | `integer` | no | Max results (default 50). (1 to 200) | **Returns** - `count` (`number`) - `docs` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "list_notes", "arguments": {} } } ``` ### get_note **Get note** · `Read-only` Read one note in full by its id — the companion to search: search returns the matched passage, this pulls the whole document when you need more than the snippet. Returns the note's title and full body as Markdown, and, for a note a meeting was recorded into, the attributed transcript once its recording is done. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `docId` | `string` | yes | The note's UUID (from a search result). | **Returns** - `id` (`string`) - `title` (`string`) - `content` (`string`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `transcript` (`string`) - `recording` (`object`) **Example** ```json { "method": "tools/call", "params": { "name": "get_note", "arguments": { "docId": "<docId>" } } } ``` ### create_note **Create note** · `Adds data` Capture a note for the user. Omit projectId to leave it unsorted (no project); set projectId to file it under a project. The body is stored as authored within the editor's supported Markdown subset (unsupported constructs degrade to plain text) — no AI cleanup or reclassification runs — and indexed for search. Returns the created note. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `content` | `string` | yes | The note body, as Markdown (headings `#`, lists, task lists `- [ ]`, bold, links — the editor's supported subset). Rendered to rich text; author real Markdown, not literal markers. | | `title` | `string` | no | Short title. Omit to let the body speak for itself. | | `projectId` | `string` | no | Project UUID. Omit to leave it unsorted (no project). | **Returns** - `id` (`string`) - `title` (`string | null`) - `content` (`string`) - `projectId` (`string | null`) - `createdAt` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "create_note", "arguments": { "content": "<content>" } } } ``` ### update_note **Update note** · `Updates or removes existing data` Edit an existing note — its title, body, or which project it's in. Only the fields you pass change. The body merge mode controls how `content` combines with the existing body. Re-indexes for search automatically. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `docId` | `string` | yes | The note's UUID. | | `title` | `string` | no | New title. Omit to leave unchanged. | | `content` | `string` | no | Markdown body to merge in (headings, lists, bold, links). Omit for a title/project-only edit. | | `mode` | `"prepend" \| "append" \| "replace" \| "insertAfter"` | no | How `content` merges: 'append' (default, at the bottom), 'prepend' (at the top), 'replace' (swap the whole body — only when the user explicitly asked to rewrite it; never to add content; requires confirmReplace: true), or 'insertAfter' (place it right after the line named in `anchor`, matching that line's formatting). | | `confirmReplace` | `boolean` | no | Required (true) for mode 'replace': confirms the user explicitly asked to swap the WHOLE body, deleting everything in it. Never pass it on the user's behalf without that explicit ask. | | `anchor` | `string` | no | For mode 'insertAfter' only: a verbatim snippet of the existing line to insert after. Errors if not found. | | `projectId` | `string` | no | Project UUID to move the note into, or "inbox" to clear its project (unsorted — the value keeps its legacy name). Omit to leave it where it is. | **Returns** - `id` (`string`) - `title` (`string | null`) - `content` (`string`) - `projectId` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "update_note", "arguments": { "docId": "<docId>" } } } ``` ### delete_note **Delete note** · `Updates or removes existing data` · `Safe to retry` Delete a note by id. This is a SOFT delete — the note is recoverable for ~30 days before it is purged. Deleting a note also removes it from search. Deletes one note per call so each delete is confirmed individually. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `docId` | `string` | yes | The note's UUID. | **Returns** - `deleted` (`boolean`) - `id` (`string`) - `title` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "delete_note", "arguments": { "docId": "<docId>" } } } ``` ## Collections Typed lists: a table with named columns, one row per thing. Rows go in and are corrected many at a time. ### list_collections **List collections** · `Read-only` List the user's collections (typed lists — a reading list, a watchlist, an expenses tracker). Returns each list's id, name, standing instructions, row count, and its COLUMNS with their stable ids — the ids add_collection_items and update_collection_items take. Use it before writing to a collection whose columns you have not read yet. Pass suppressOptions: true when you only need names/counts — select columns can carry hundreds of options (you don't need them to write: a select value may be sent as its label). **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `suppressOptions` | `boolean` | no | true replaces each select column's option list with an optionCount — much smaller payload. | **Returns** - `count` (`number`) - `collections` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "list_collections", "arguments": {} } } ``` ### get_collection_items **Get collection rows** · `Read-only` Read the rows of one collection. Values are keyed by COLUMN ID (see list_collections); a select column's value is an option id, a multiSelect column's an array of option ids. Each row also lists its image attachments (so you can verify an import's images actually landed); pass includeImageUrls: true for short-lived viewable links. Paginated: the result carries totalCount and hasMore — keep calling with offset until hasMore is false to read a large list completely. Archived rows are left out unless includeArchived: true. Use to check what a list already holds before adding to it. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `collectionId` | `string` | yes | The collection's UUID (from list_collections). | | `includeArchived` | `boolean` | no | true also returns archived rows (hidden from every list by default) so one can be brought back with update_collection_items; each row then says whether it is archived. | | `limit` | `integer` | no | Max rows per page (default 50, newest first). (1 to 200) | | `offset` | `integer` | no | Rows to skip — page through a big list with offset + limit until hasMore is false. (0 to 9007199254740991) | | `includeImageUrls` | `boolean` | no | true additionally returns a short-lived signed `url` per attachment so the images can be viewed. Off by default. | | `createdAfter` | `string` | no | ISO 8601 instant — only rows added on/after this. | | `createdBefore` | `string` | no | ISO 8601 instant — only rows added before this (exclusive). | | `updatedAfter` | `string` | no | ISO 8601 instant — only rows last changed on/after this. | | `updatedBefore` | `string` | no | ISO 8601 instant — only rows last changed before this (exclusive). | **Returns** - `count` (`number`) - `totalCount` (`number`) - `offset` (`number`) - `hasMore` (`boolean`) - `items` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "get_collection_items", "arguments": { "collectionId": "<collectionId>" } } } ``` ### create_collection **Create collection** · `Adds data` Create a typed list with its columns. Every column is equal — declare the ones the list actually needs, including a name column if rows have names (a list of measurements or dates may have none). Returns the created list WITH the stable column ids to use when adding rows. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | The list's name. | | `instructions` | `string` | no | Standing rules for how entries should be handled, one rule per line. Max 500 characters — they ride in the prompt on every turn. (up to 500 characters) | | `properties` | `object[]` | no | The columns. Omit to start with one default text column the user can rename, retype or delete. | **Returns** - `id` (`string`) - `name` (`string`) - `columns` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "create_collection", "arguments": { "name": "<name>" } } } ``` ### update_collection **Update collection** · `Updates or removes existing data` · `Safe to retry` Rename a collection or edit its standing instructions. `instructions` REPLACES the existing text — read the current value first (list_collections) and carry forward anything still true; null clears them. Column (schema) editing is not available over MCP — new select options mint automatically when adding or updating rows. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `collectionId` | `string` | yes | The collection's UUID (from list_collections). | | `name` | `string` | no | New list name. | | `instructions` | `string \| null` | no | REPLACES the standing rules (max 500 characters — they ride the prompt every turn); null clears them. | **Returns** - `id` (`string`) - `name` (`string`) - `instructions` (`string | null`) **Example** ```json { "method": "tools/call", "params": { "name": "update_collection", "arguments": { "collectionId": "<collectionId>" } } } ``` ### delete_collection **Delete collection** · `Updates or removes existing data` · `Safe to retry` Delete a collection (typed list) by id. Deleting the list also deletes ALL its rows. SOFT delete — the list and its rows are recoverable for ~30 days via restore_deleted (type "collection"), which brings the rows deleted with it back too. When the list still holds rows the call refuses unless confirmDeleteItems is true, so the blast radius is always named before it happens. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `collectionId` | `string` | yes | The collection's UUID (from list_collections). | | `confirmDeleteItems` | `boolean` | no | Required (true) when the list holds rows: confirms deleting the list AND every row in it. Confirm with the user first. | **Returns** - `deleted` (`boolean`) - `id` (`string`) - `name` (`string`) - `itemsDeleted` (`number`) **Example** ```json { "method": "tools/call", "params": { "name": "delete_collection", "arguments": { "collectionId": "<collectionId>" } } } ``` ### add_collection_items **Add collection rows** · `Adds data` · `Reaches outside Roma` Add rows to a collection, many in one call (the import path). Values are keyed by column id or name; a select value may be its label or option id, a multiSelect value is an ARRAY of them, and unknown labels become new options. Keys that match no column come back in `unknownKeys`. Images that could not be stored come back in `imageFailures` with the reason; create_image_upload is the reliable path for images. Every row is inserted unless `matchOn` names columns to match on; a matched row keeps every filled cell and only gains what it lacks (empty cells, notes, images), so nothing is overwritten. To change a filled cell use update_collection_items (re-adding without matchOn creates a duplicate). **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `collectionId` | `string` | yes | The collection's UUID (from list_collections). | | `matchOn` | `string[]` | no | Column ids or names that identify an existing row. When given, a row whose listed cells all match an existing row is not added again: the existing row gains the incoming row's empty-cell values, notes and images and keeps every cell it already had; matching is exact. Omit to always insert — no column is a natural key, so there is no default dedup. | | `items` | `object[]` | yes | Every row to add, in one call. (up to 200 items) | **Returns** - `added` (`number`) - `itemIds` (`string[]`) - `unknownKeys` (`string[]`) - `imagesAttached` (`number`) - `imageFailures` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "add_collection_items", "arguments": { "collectionId": "<collectionId>", "items": [ {} ] } } } ``` ### update_collection_items **Update collection rows** · `Updates or removes existing data` · `Safe to retry` · `Reaches outside Roma` Update existing rows of ONE collection, many in one call; pass only what changes. `values` MERGE into the row (only the keys sent change; "" clears a cell), `body` replaces the notes, `imageUrls` add images (clearImages: true drops the old ones first), `archived` sets a row aside or brings it back. A key matching no column is an error; rows earlier in the batch stay updated and are named, so re-send only the rest. Read row ids and current values with get_collection_items first. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `collectionId` | `string` | yes | The collection's UUID (from list_collections). | | `items` | `object[]` | yes | Every row to update, in one call. (up to 200 items) | **Returns** - `updated` (`number`) - `itemIds` (`string[]`) - `imagesAttached` (`number`) - `imageFailures` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "update_collection_items", "arguments": { "collectionId": "<collectionId>", "items": [ { "itemId": "<itemId>" } ] } } } ``` ### delete_collection_items **Delete collection rows** · `Updates or removes existing data` · `Safe to retry` Delete rows from collections by id — up to 50 per call. SOFT delete: every row stays recoverable for ~30 days via restore_deleted (type "collectionItem") before it is purged, and drops out of search. Returns a per-id result — ids that don't resolve are reported and the rest still delete. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `itemIds` | `string[]` | yes | The rows' UUIDs (from get_collection_items). Up to 50 per call. (up to 50 items) | **Returns** - `deleted` (`number`) - `results` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "delete_collection_items", "arguments": { "itemIds": [ "<itemIds>" ] } } } ``` ### create_image_upload **Create image upload** · `Adds data` Get pre-signed URLs to upload images DIRECTLY into Roma storage — the reliable alternative to passing third-party links in imageUrls (those must stay fetchable until the server downloads them; short-lived signed links often expire first). For each slot: HTTP PUT the raw image bytes to uploadUrl with the matching Content-Type header (valid ~2 hours), then pass its bucketPath as an imageUrls entry in add_collection_items / update_collection_items. Keep images at or under 10 MB (maxBytes). Up to 10 slots per call. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `mimeType` | `"image/png" \| "image/jpeg" \| "image/heic" \| "image/webp"` | yes | The image type you will upload (all slots in one call share it). | | `count` | `integer` | no | How many upload slots (default 1). (1 to 10) | **Returns** - `uploads` (`object[]`) - `maxBytes` (`number`) - `validForSeconds` (`number`) **Example** ```json { "method": "tools/call", "params": { "name": "create_image_upload", "arguments": { "mimeType": "image/png" } } } ``` ## Trash Every delete is soft. The trash lists what was deleted and restores one item per call for about 30 days. ### list_deleted **List deleted items (trash)** · `Read-only` The trash: everything soft-deleted in the last ~30 days — tasks, notes, collections, and collection rows — newest first, each with the moment it is purged forever (purgesAt). Anything listed here can be brought back with restore_deleted. Filter by type, or omit `type` for all kinds. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `type` | `"task" \| "note" \| "collection" \| "collectionItem"` | no | Only this kind. Omit for all kinds. | | `limit` | `integer` | no | Max entries per kind (default 25). (1 to 100) | **Returns** - `count` (`number`) - `items` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "list_deleted", "arguments": {} } } ``` ### restore_deleted **Restore deleted item** · `Adds data` · `Safe to retry` Bring back one soft-deleted item from the trash (see list_deleted) — a task, note, collection, or collection row. Restoring a collection also brings back the rows deleted with it; a row whose list is still deleted can't come back alone (restore the collection instead). A restored task whose project was deleted meanwhile lands in the inbox (movedToInbox: true). Restores one item per call. If the item is already live the call succeeds with alreadyLive: true. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `type` | `"task" \| "note" \| "collection" \| "collectionItem"` | yes | The entry's type, as list_deleted reported it. | | `id` | `string` | yes | The deleted item's UUID. | **Returns** - `restored` (`boolean`) - `alreadyLive` (`boolean`) - `type` (`string`) - `id` (`string`) - `title` (`string | null`) - `restoredItems` (`number`) - `movedToInbox` (`boolean`) **Example** ```json { "method": "tools/call", "params": { "name": "restore_deleted", "arguments": { "type": "task", "id": "<id>" } } } ``` ## Automations Standing orders Roma runs on a schedule. Read them, read what their runs found, and start one when the user asks. ### list_automations **List automations** · `Read-only` List the user's automations — standing orders Roma runs on its own, on a schedule or on demand. Returns each one's id, name, what it does, status (active / paused / draft), when it runs, the next run, and how the last run ended. Use it when the user asks what runs on its own, when something runs next, or before reading or starting a run. **Parameters** This tool takes no parameters. **Returns** - `count` (`number`) - `automations` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "list_automations", "arguments": {} } } ``` ### get_automation_runs **Get automation runs** · `Read-only` Read one automation's recent runs, newest first: when each ran, how it ended, what it wrote (one line per action), the message it left for the user, and any error. Use it for "what did my morning brief find", "why did it fail", or to read the result of a run started with run_automation once it has finished. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `automationId` | `string` | yes | The automation's id (from list_automations or get_context). | | `limit` | `integer` | no | How many runs, newest first (default 5, max 20). (1 to 20) | **Returns** - `automation` (`object`) - `runs` (`object[]`) **Example** ```json { "method": "tools/call", "params": { "name": "get_automation_runs", "arguments": { "automationId": "<automationId>" } } } ``` ### run_automation **Run automation now** · `Updates or removes existing data` · `Reaches outside Roma` Start one of the user's automations now — the same as Run now on its page in Roma. Use it only when the user asks to run a specific automation. The run happens inside Roma with the user's connected apps and can take a minute; its result lands in the user's Roma chat, not in this conversation. Read the outcome afterwards with get_automation_runs. Refused for a draft, while the same automation is already running, or once the daily run limit is reached. **Parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `automationId` | `string` | yes | The automation's id (from list_automations or get_context). | **Returns** - `id` (`string`) - `name` (`string`) - `runId` (`string`) - `status` (`string`) **Example** ```json { "method": "tools/call", "params": { "name": "run_automation", "arguments": { "automationId": "<automationId>" } } } ``` <!-- https://roma.app/developers/api --> # REST API Plain HTTP for the tools that speak it rather than MCP: Zapier, Make, n8n, a shell script, a scheduled job. 30 operations under `https://api.roma.app/api/v1`, each one of the MCP server's tools wearing an HTTP method, so what you can do here is exactly what an MCP client can do and the parameters are the same. The document that describes it is at [`/api/v1/openapi.json`](https://api.roma.app/api/v1/openapi.json); import it into Zapier, Postman or an OpenAPI client. ## How to call - **Base URL** `https://api.roma.app/api/v1`. JSON in, JSON out. - **Authentication**: a Roma API key or an OAuth access token as a bearer: `Authorization: Bearer roma_…`. Create a key in Roma under Settings, then Connections. [Authentication](/developers/authentication#api-keys) has the details. - **Ids** are UUIDs; get them from `GET /context`, the list routes or `GET /search`. An id that belongs to someone else answers `404`. - **Query parameters** are typed by the schema: `limit=5`, `includeArchived=true`, a list as `taskStatus=todo,inProgress`. - **Errors** are `{ "error": "one sentence", "code": "invalid | notFound | methodNotAllowed | unauthorized | rateLimited | toolError" }` with `400`, `401`, `404`, `405` or `429`. A `429` carries `Retry-After`; a `405` carries `Allow`. - **Rate limit**: 60 requests a minute per key or token, counted before authentication, so a bad key is counted too. - **Status codes**: a route that makes something answers `201` (`POST /tasks`, `POST /quick-add`, `POST /projects`, `POST /notes`, `POST /collections`, `POST /collections/{id}/items`, `POST /uploads`); everything else `200`. Deletes are soft: `GET /trash` and `POST /trash/restore` bring things back for about 30 days. ## Quick add The one call most automations need: a line of text becomes a task. The text is the task's title as it lands; the enrichment pass that runs a beat later tidies the wording and picks up the due date and the project the text names. `POST /tasks` runs the same pass, so a title is tidied either way; what you set yourself (`dueAt`, `projectId`, `priority`) stays as sent. ```bash curl -X POST "https://api.roma.app/api/v1/quick-add" \ -H "Authorization: Bearer roma_…" -H "Content-Type: application/json" \ -d '{"text": "Call the dentist tomorrow at 10"}' ``` ## At a glance - **Context**: `GET /context` - **Tasks**: `GET /tasks`, `POST /tasks`, `POST /quick-add`, `GET /tasks/{id}`, `PATCH /tasks/{id}`, `DELETE /tasks/{id}` - **Projects**: `GET /projects`, `POST /projects`, `PATCH /projects/{id}` - **Notes and search**: `GET /search`, `GET /notes`, `POST /notes`, `GET /notes/{id}`, `PATCH /notes/{id}`, `DELETE /notes/{id}` - **Collections**: `GET /collections`, `POST /collections`, `PATCH /collections/{id}`, `DELETE /collections/{id}`, `GET /collections/{id}/items`, `POST /collections/{id}/items`, `PATCH /collections/{id}/items`, `DELETE /collections/{id}/items`, `POST /uploads` - **Trash**: `GET /trash`, `POST /trash/restore` - **Automations**: `GET /automations`, `GET /automations/{id}/runs`, `POST /automations/{id}/run` ## Context ### GET /context **The workspace in one call** · `Read-only` · MCP tool `get_context` Use this at the start of a conversation that involves the user's Roma workspace, or when you need an overview of it. Returns, in one call: how Roma works; the user (name, timezone, today's date and local time); what they asked the AI to remember about them; their projects; overdue, due-today, in-progress and upcoming tasks; unsorted tasks; recently touched notes; their collections with columns; their automations with the last run; and the last few days of activity. Everything comes with ids for follow-up calls. It is an overview, not the full data: use search, list_tasks, get_note or get_collection_items for depth. Pass focusProjectId to scope the task sections to one project. **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `focusProjectId` | `string` | no | Optional project UUID. Scopes the task sections to that project; the rest stays workspace-wide. | **Returns** `200` - `about` (`string[]`) - `user` (`object`) - `unavailable` (`string[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/context" \ -H "Authorization: Bearer roma_…" ``` ## Tasks ### GET /tasks **List and filter tasks** · `Read-only` · MCP tool `list_tasks` List, filter, and search the user's tasks — the way to find work to pick up. Returns tasks ordered by due date, then priority. Scope to the whole workspace, unsorted tasks only, or a single project. By default completed tasks are excluded. **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `scope` | `string` | no | "all" (default) for everything, "inbox" for unsorted items only (no project — the value keeps its legacy name), or a project UUID for that project. | | `query` | `string` | no | Free-text search across title and description. | | `taskStatus` | `"todo" \| "inProgress" \| "completed"[]` | no | Filter to these statuses. Omit to use the default active set (excludes completed). | | `dueAfter` | `string` | no | ISO 8601 instant — only tasks due on/after this (inclusive). | | `dueBefore` | `string` | no | ISO 8601 instant — only tasks due before this (exclusive). Today = dueAfter today 00:00 local, dueBefore tomorrow 00:00 local; overdue = dueBefore today 00:00 alone. | | `createdAfter` | `string` | no | ISO 8601 instant — only tasks created on/after this. | | `createdBefore` | `string` | no | ISO 8601 instant — only tasks created before this (exclusive). | | `updatedAfter` | `string` | no | ISO 8601 instant — only tasks last changed on/after this. | | `updatedBefore` | `string` | no | ISO 8601 instant — only tasks last changed before this (exclusive). | | `completedAfter` | `string` | no | ISO 8601 instant — only tasks completed on/after this. Implies completed tasks unless taskStatus says otherwise. | | `completedBefore` | `string` | no | ISO 8601 instant — only tasks completed before this (exclusive). | | `sort` | `"due" \| "created" \| "updated" \| "completed"` | no | Row order. Default 'due' (due date, then priority); a timestamp sort returns newest first. | | `limit` | `integer` | no | Max results (default 50). (1 to 200) | **Returns** `200` - `count` (`number`) - `tasks` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/tasks" \ -H "Authorization: Bearer roma_…" ``` ### POST /tasks **Create a task** · `Adds data` · MCP tool `create_task` Create a task. Omit projectId to leave it unsorted (no project); set projectId to file it under a project. The task is enriched by AI in the background after creation, so enriched fields may appear on a subsequent read. Returns the created task. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `title` | `string` | yes | Short, clear task title. | | `description` | `string` | no | Optional longer details. | | `projectId` | `string` | no | Project UUID. Omit to leave it unsorted (no project). | | `priority` | `"low" \| "medium" \| "high"` | no | | | `taskStatus` | `"todo" \| "inProgress" \| "completed"` | no | Defaults to 'todo'. | | `dueAt` | `string` | no | ISO 8601 due timestamp. | **Returns** `201` - `id` (`string`) - `title` (`string | null`) - `description` (`string | null`) - `taskStatus` (`string | null`) - `priority` (`string | null`) - `dueAt` (`string | null`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `updatedAt` (`string | null`) - `editedAt` (`string | null`) - `completedAt` (`string | null`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/tasks" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"title":"<title>"}' ``` ### POST /quick-add **Create a task from one line of text** · `Adds data` · MCP tool `create_task` Create a task. Omit projectId to leave it unsorted (no project); set projectId to file it under a project. The task is enriched by AI in the background after creation, so enriched fields may appear on a subsequent read. Returns the created task. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `text` | `string` | yes | Short, clear task title. | | `description` | `string` | no | Optional longer details. | | `projectId` | `string` | no | Project UUID. Omit to leave it unsorted (no project). | | `priority` | `"low" \| "medium" \| "high"` | no | | | `taskStatus` | `"todo" \| "inProgress" \| "completed"` | no | Defaults to 'todo'. | | `dueAt` | `string` | no | ISO 8601 due timestamp. | **Returns** `201` - `id` (`string`) - `title` (`string | null`) - `description` (`string | null`) - `taskStatus` (`string | null`) - `priority` (`string | null`) - `dueAt` (`string | null`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `updatedAt` (`string | null`) - `editedAt` (`string | null`) - `completedAt` (`string | null`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/quick-add" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"text":"<text>"}' ``` ### GET /tasks/{id} **One task with its subtasks and project** · `Read-only` · MCP tool `get_task_context` Fetch one task with everything needed to start working on it: the full task row (description, status, priority, due date) and the project it belongs to. The equivalent of opening an issue. **Path** - `id`: The task's UUID. **Returns** `200` - `task` (`object`) - `project` (`object | null`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/tasks/<id>" \ -H "Authorization: Bearer roma_…" ``` ### PATCH /tasks/{id} **Update a task** · `Updates or removes existing data` · MCP tool `update_task` Update fields on an existing task — title, body, status, priority, due date, or which project it is in (projectId; null moves it out to unsorted). Use status transitions to report progress (e.g. todo → inProgress → completed). Only the fields you pass are changed. The body merge mode controls how `description` combines with the existing body — adding text appends/prepends; it never replaces unless you explicitly ask it to. **Path** - `id`: The task's UUID. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `title` | `string` | no | | | `description` | `string \| null` | no | Body text to merge in per `mode`. Omit to leave the body unchanged; null to clear it. | | `mode` | `"prepend" \| "append" \| "replace" \| "insertAfter"` | no | How `description` merges: 'append' (default, at the bottom), 'prepend' (at the top), 'insertAfter' (right after the line named in `anchor`), or 'replace' (swap the whole body — only when the user explicitly asked to rewrite it; never to add content; requires confirmReplace: true). | | `confirmReplace` | `boolean` | no | Required (true) for mode 'replace': confirms the user explicitly asked to swap the WHOLE body, deleting everything in it. Never pass it on the user's behalf without that explicit ask. | | `anchor` | `string` | no | For mode 'insertAfter' only: a verbatim snippet of the existing line to insert after. Errors if not found. | | `taskStatus` | `"todo" \| "inProgress" \| "completed"` | no | | | `priority` | `"low" \| "medium" \| "high" \| null` | no | | | `dueAt` | `string \| null` | no | ISO 8601 timestamp, or null to clear. | | `projectId` | `string \| null` | no | Move the task: a project UUID (from list_projects or get_context) files it there; null moves it out to unsorted. Omit to leave it where it is. | **Returns** `200` - `id` (`string`) - `title` (`string | null`) - `description` (`string | null`) - `taskStatus` (`string | null`) - `priority` (`string | null`) - `dueAt` (`string | null`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `updatedAt` (`string | null`) - `editedAt` (`string | null`) - `completedAt` (`string | null`) **Example** ```bash curl -X PATCH "https://api.roma.app/api/v1/tasks/<id>" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{}' ``` ### DELETE /tasks/{id} **Delete a task into the trash** · `Updates or removes existing data` · MCP tool `delete_task` Delete a task (or subtask) by id. This is a SOFT delete — the task is recoverable for ~30 days before it is purged. Deleting a task also removes it from search. Deletes one task per call so each delete is confirmed individually. **Path** - `id`: The task's UUID. **Returns** `200` - `deleted` (`boolean`) - `id` (`string`) - `title` (`string | null`) **Example** ```bash curl -X DELETE "https://api.roma.app/api/v1/tasks/<id>" \ -H "Authorization: Bearer roma_…" ``` ## Projects ### GET /projects **List projects** · `Read-only` · MCP tool `list_projects` List all of the user's projects. Use this to resolve a project name to its UUID before creating or filing a task, or to understand the workspace structure. **Returns** `200` - `count` (`number`) - `projects` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/projects" \ -H "Authorization: Bearer roma_…" ``` ### POST /projects **Create a project** · `Adds data` · MCP tool `create_project` Create a project — a folder that groups the user's tasks and notes. Use it when the user wants a new project, or to give work a home that has none; check list_projects or get_context first so an existing project is not duplicated. Returns the project with its id, ready for create_task / update_task projectId. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | Project name, in the user's own words. | | `emoji` | `string` | no | Optional single emoji for the project. | **Returns** `201` - `id` (`string`) - `name` (`string | null`) - `emoji` (`string | null`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/projects" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"name":"<name>"}' ``` ### PATCH /projects/{id} **Rename a project or set its emoji** · `Updates or removes existing data` · MCP tool `update_project` Rename a project or change its emoji. Only the fields you pass change; emoji null removes it. Does not move or change the project's tasks. **Path** - `id`: The project's UUID (from list_projects or get_context). **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `name` | `string` | no | New name. | | `emoji` | `string \| null` | no | New emoji, or null to remove it. | **Returns** `200` - `id` (`string`) - `name` (`string | null`) - `emoji` (`string | null`) **Example** ```bash curl -X PATCH "https://api.roma.app/api/v1/projects/<id>" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{}' ``` ## Notes and search ### GET /search **Search notes, tasks and list rows** · `Read-only` · MCP tool `search` Search the user's Roma workspace — their notes, tasks, typed lists and the rows inside them — by meaning or keyword (hybrid semantic + keyword retrieval). Use this when the user asks about their own information, plans or anything they may have saved, and before adding something that may already exist. Their wording often won't match their notes' wording; the semantic arm bridges that gap (and across languages). Returns the best-matching passage per item; get_note returns a note's full text. An empty result means nothing relevant is saved. Not for general web knowledge. **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `q` | `string` | yes | Natural-language query — what the user is trying to find, in their words. | | `limit` | `integer` | no | Max results (default 10). (1 to 20) | | `after` | `string` | no | Only items CREATED on/after this ISO 8601 timestamp. Use when scoping by when it was saved ("last week", "in March"). | | `before` | `string` | no | Only items CREATED before this ISO 8601 timestamp (exclusive). Pair with `after` to bound a window. | **Returns** `200` - `count` (`number`) - `results` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/search" \ -H "Authorization: Bearer roma_…" ``` ### GET /notes **List notes** · `Read-only` · MCP tool `list_notes` List the user's notes, newest first. Scope to the whole workspace, unsorted notes only, or a single project. Returns a snippet of each body; use get_note for the full text. This is browse-by-container — use search to find by topic. **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `scope` | `string` | no | "all" (default) for everything, "inbox" for unsorted items only (no project — the value keeps its legacy name), or a project UUID for that project. | | `createdAfter` | `string` | no | ISO 8601 instant — only notes created on/after this. | | `createdBefore` | `string` | no | ISO 8601 instant — only notes created before this (exclusive). | | `updatedAfter` | `string` | no | ISO 8601 instant — only notes last changed on/after this. | | `updatedBefore` | `string` | no | ISO 8601 instant — only notes last changed before this (exclusive). | | `sort` | `"created" \| "updated"` | no | Row order, newest first on that stamp. Default created. | | `limit` | `integer` | no | Max results (default 50). (1 to 200) | **Returns** `200` - `count` (`number`) - `docs` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/notes" \ -H "Authorization: Bearer roma_…" ``` ### POST /notes **Create a note** · `Adds data` · MCP tool `create_note` Capture a note for the user. Omit projectId to leave it unsorted (no project); set projectId to file it under a project. The body is stored as authored within the editor's supported Markdown subset (unsupported constructs degrade to plain text) — no AI cleanup or reclassification runs — and indexed for search. Returns the created note. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `content` | `string` | yes | The note body, as Markdown (headings `#`, lists, task lists `- [ ]`, bold, links — the editor's supported subset). Rendered to rich text; author real Markdown, not literal markers. | | `title` | `string` | no | Short title. Omit to let the body speak for itself. | | `projectId` | `string` | no | Project UUID. Omit to leave it unsorted (no project). | **Returns** `201` - `id` (`string`) - `title` (`string | null`) - `content` (`string`) - `projectId` (`string | null`) - `createdAt` (`string | null`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/notes" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"content":"<content>"}' ``` ### GET /notes/{id} **One note as Markdown** · `Read-only` · MCP tool `get_note` Read one note in full by its id — the companion to search: search returns the matched passage, this pulls the whole document when you need more than the snippet. Returns the note's title and full body as Markdown, and, for a note a meeting was recorded into, the attributed transcript once its recording is done. **Path** - `id`: The note's UUID (from a search result). **Returns** `200` - `id` (`string`) - `title` (`string`) - `content` (`string`) - `projectId` (`string | null`) - `createdAt` (`string | null`) - `transcript` (`string`) - `recording` (`object`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/notes/<id>" \ -H "Authorization: Bearer roma_…" ``` ### PATCH /notes/{id} **Update a note** · `Updates or removes existing data` · MCP tool `update_note` Edit an existing note — its title, body, or which project it's in. Only the fields you pass change. The body merge mode controls how `content` combines with the existing body. Re-indexes for search automatically. **Path** - `id`: The note's UUID. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `title` | `string` | no | New title. Omit to leave unchanged. | | `content` | `string` | no | Markdown body to merge in (headings, lists, bold, links). Omit for a title/project-only edit. | | `mode` | `"prepend" \| "append" \| "replace" \| "insertAfter"` | no | How `content` merges: 'append' (default, at the bottom), 'prepend' (at the top), 'replace' (swap the whole body — only when the user explicitly asked to rewrite it; never to add content; requires confirmReplace: true), or 'insertAfter' (place it right after the line named in `anchor`, matching that line's formatting). | | `confirmReplace` | `boolean` | no | Required (true) for mode 'replace': confirms the user explicitly asked to swap the WHOLE body, deleting everything in it. Never pass it on the user's behalf without that explicit ask. | | `anchor` | `string` | no | For mode 'insertAfter' only: a verbatim snippet of the existing line to insert after. Errors if not found. | | `projectId` | `string` | no | Project UUID to move the note into, or "inbox" to clear its project (unsorted — the value keeps its legacy name). Omit to leave it where it is. | **Returns** `200` - `id` (`string`) - `title` (`string | null`) - `content` (`string`) - `projectId` (`string | null`) **Example** ```bash curl -X PATCH "https://api.roma.app/api/v1/notes/<id>" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{}' ``` ### DELETE /notes/{id} **Delete a note into the trash** · `Updates or removes existing data` · MCP tool `delete_note` Delete a note by id. This is a SOFT delete — the note is recoverable for ~30 days before it is purged. Deleting a note also removes it from search. Deletes one note per call so each delete is confirmed individually. **Path** - `id`: The note's UUID. **Returns** `200` - `deleted` (`boolean`) - `id` (`string`) - `title` (`string | null`) **Example** ```bash curl -X DELETE "https://api.roma.app/api/v1/notes/<id>" \ -H "Authorization: Bearer roma_…" ``` ## Collections ### GET /collections **List collections with their columns** · `Read-only` · MCP tool `list_collections` List the user's collections (typed lists — a reading list, a watchlist, an expenses tracker). Returns each list's id, name, standing instructions, row count, and its COLUMNS with their stable ids — the ids add_collection_items and update_collection_items take. Use it before writing to a collection whose columns you have not read yet. Pass suppressOptions: true when you only need names/counts — select columns can carry hundreds of options (you don't need them to write: a select value may be sent as its label). **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `suppressOptions` | `boolean` | no | true replaces each select column's option list with an optionCount — much smaller payload. | **Returns** `200` - `count` (`number`) - `collections` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/collections" \ -H "Authorization: Bearer roma_…" ``` ### POST /collections **Create a collection** · `Adds data` · MCP tool `create_collection` Create a typed list with its columns. Every column is equal — declare the ones the list actually needs, including a name column if rows have names (a list of measurements or dates may have none). Returns the created list WITH the stable column ids to use when adding rows. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | The list's name. | | `instructions` | `string` | no | Standing rules for how entries should be handled, one rule per line. Max 500 characters — they ride in the prompt on every turn. (up to 500 characters) | | `properties` | `object[]` | no | The columns. Omit to start with one default text column the user can rename, retype or delete. | **Returns** `201` - `id` (`string`) - `name` (`string`) - `columns` (`object[]`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/collections" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"name":"<name>"}' ``` ### PATCH /collections/{id} **Rename a collection or replace its instructions** · `Updates or removes existing data` · MCP tool `update_collection` Rename a collection or edit its standing instructions. `instructions` REPLACES the existing text — read the current value first (list_collections) and carry forward anything still true; null clears them. Column (schema) editing is not available over MCP — new select options mint automatically when adding or updating rows. **Path** - `id`: The collection's UUID (from list_collections). **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `name` | `string` | no | New list name. | | `instructions` | `string \| null` | no | REPLACES the standing rules (max 500 characters — they ride the prompt every turn); null clears them. | **Returns** `200` - `id` (`string`) - `name` (`string`) - `instructions` (`string | null`) **Example** ```bash curl -X PATCH "https://api.roma.app/api/v1/collections/<id>" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{}' ``` ### DELETE /collections/{id} **Delete a collection into the trash** · `Updates or removes existing data` · MCP tool `delete_collection` Delete a collection (typed list) by id. Deleting the list also deletes ALL its rows. SOFT delete — the list and its rows are recoverable for ~30 days via restore_deleted (type "collection"), which brings the rows deleted with it back too. When the list still holds rows the call refuses unless confirmDeleteItems is true, so the blast radius is always named before it happens. **Path** - `id`: The collection's UUID (from list_collections). **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `confirmDeleteItems` | `boolean` | no | Required (true) when the list holds rows: confirms deleting the list AND every row in it. Confirm with the user first. | **Returns** `200` - `deleted` (`boolean`) - `id` (`string`) - `name` (`string`) - `itemsDeleted` (`number`) **Example** ```bash curl -X DELETE "https://api.roma.app/api/v1/collections/<id>" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{}' ``` ### GET /collections/{id}/items **Rows of a collection, paged** · `Read-only` · MCP tool `get_collection_items` Read the rows of one collection. Values are keyed by COLUMN ID (see list_collections); a select column's value is an option id, a multiSelect column's an array of option ids. Each row also lists its image attachments (so you can verify an import's images actually landed); pass includeImageUrls: true for short-lived viewable links. Paginated: the result carries totalCount and hasMore — keep calling with offset until hasMore is false to read a large list completely. Archived rows are left out unless includeArchived: true. Use to check what a list already holds before adding to it. **Path** - `id`: The collection's UUID (from list_collections). **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `includeArchived` | `boolean` | no | true also returns archived rows (hidden from every list by default) so one can be brought back with update_collection_items; each row then says whether it is archived. | | `limit` | `integer` | no | Max rows per page (default 50, newest first). (1 to 200) | | `offset` | `integer` | no | Rows to skip — page through a big list with offset + limit until hasMore is false. (0 to 9007199254740991) | | `includeImageUrls` | `boolean` | no | true additionally returns a short-lived signed `url` per attachment so the images can be viewed. Off by default. | | `createdAfter` | `string` | no | ISO 8601 instant — only rows added on/after this. | | `createdBefore` | `string` | no | ISO 8601 instant — only rows added before this (exclusive). | | `updatedAfter` | `string` | no | ISO 8601 instant — only rows last changed on/after this. | | `updatedBefore` | `string` | no | ISO 8601 instant — only rows last changed before this (exclusive). | **Returns** `200` - `count` (`number`) - `totalCount` (`number`) - `offset` (`number`) - `hasMore` (`boolean`) - `items` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/collections/<id>/items" \ -H "Authorization: Bearer roma_…" ``` ### POST /collections/{id}/items **Add rows to a collection** · `Adds data` · MCP tool `add_collection_items` Add rows to a collection, many in one call (the import path). Values are keyed by column id or name; a select value may be its label or option id, a multiSelect value is an ARRAY of them, and unknown labels become new options. Keys that match no column come back in `unknownKeys`. Images that could not be stored come back in `imageFailures` with the reason; create_image_upload is the reliable path for images. Every row is inserted unless `matchOn` names columns to match on; a matched row keeps every filled cell and only gains what it lacks (empty cells, notes, images), so nothing is overwritten. To change a filled cell use update_collection_items (re-adding without matchOn creates a duplicate). **Path** - `id`: The collection's UUID (from list_collections). **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `matchOn` | `string[]` | no | Column ids or names that identify an existing row. When given, a row whose listed cells all match an existing row is not added again: the existing row gains the incoming row's empty-cell values, notes and images and keeps every cell it already had; matching is exact. Omit to always insert — no column is a natural key, so there is no default dedup. | | `items` | `object[]` | yes | Every row to add, in one call. (up to 200 items) | **Returns** `201` - `added` (`number`) - `itemIds` (`string[]`) - `unknownKeys` (`string[]`) - `imagesAttached` (`number`) - `imageFailures` (`object[]`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/collections/<id>/items" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"items":[{}]}' ``` ### PATCH /collections/{id}/items **Correct rows of a collection** · `Updates or removes existing data` · MCP tool `update_collection_items` Update existing rows of ONE collection, many in one call; pass only what changes. `values` MERGE into the row (only the keys sent change; "" clears a cell), `body` replaces the notes, `imageUrls` add images (clearImages: true drops the old ones first), `archived` sets a row aside or brings it back. A key matching no column is an error; rows earlier in the batch stay updated and are named, so re-send only the rest. Read row ids and current values with get_collection_items first. **Path** - `id`: The collection's UUID (from list_collections). **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `items` | `object[]` | yes | Every row to update, in one call. (up to 200 items) | **Returns** `200` - `updated` (`number`) - `itemIds` (`string[]`) - `imagesAttached` (`number`) - `imageFailures` (`object[]`) **Example** ```bash curl -X PATCH "https://api.roma.app/api/v1/collections/<id>/items" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"items":[{"itemId":"<itemId>"}]}' ``` ### DELETE /collections/{id}/items **Delete rows into the trash** · `Updates or removes existing data` · MCP tool `delete_collection_items` Delete rows from collections by id — up to 50 per call. SOFT delete: every row stays recoverable for ~30 days via restore_deleted (type "collectionItem") before it is purged, and drops out of search. Returns a per-id result — ids that don't resolve are reported and the rest still delete. **Path** - `id` **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `itemIds` | `string[]` | yes | The rows' UUIDs (from get_collection_items). Up to 50 per call. (up to 50 items) | **Returns** `200` - `deleted` (`number`) - `results` (`object[]`) **Example** ```bash curl -X DELETE "https://api.roma.app/api/v1/collections/<id>/items" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"itemIds":["<itemIds>"]}' ``` ### POST /uploads **Pre-signed slots for image bytes** · `Adds data` · MCP tool `create_image_upload` Get pre-signed URLs to upload images DIRECTLY into Roma storage — the reliable alternative to passing third-party links in imageUrls (those must stay fetchable until the server downloads them; short-lived signed links often expire first). For each slot: HTTP PUT the raw image bytes to uploadUrl with the matching Content-Type header (valid ~2 hours), then pass its bucketPath as an imageUrls entry in add_collection_items / update_collection_items. Keep images at or under 10 MB (maxBytes). Up to 10 slots per call. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `mimeType` | `"image/png" \| "image/jpeg" \| "image/heic" \| "image/webp"` | yes | The image type you will upload (all slots in one call share it). | | `count` | `integer` | no | How many upload slots (default 1). (1 to 10) | **Returns** `201` - `uploads` (`object[]`) - `maxBytes` (`number`) - `validForSeconds` (`number`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/uploads" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"mimeType":"image/png"}' ``` ## Trash ### GET /trash **What is in the trash** · `Read-only` · MCP tool `list_deleted` The trash: everything soft-deleted in the last ~30 days — tasks, notes, collections, and collection rows — newest first, each with the moment it is purged forever (purgesAt). Anything listed here can be brought back with restore_deleted. Filter by type, or omit `type` for all kinds. **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `type` | `"task" \| "note" \| "collection" \| "collectionItem"` | no | Only this kind. Omit for all kinds. | | `limit` | `integer` | no | Max entries per kind (default 25). (1 to 100) | **Returns** `200` - `count` (`number`) - `items` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/trash" \ -H "Authorization: Bearer roma_…" ``` ### POST /trash/restore **Restore one item from the trash** · `Adds data` · MCP tool `restore_deleted` Bring back one soft-deleted item from the trash (see list_deleted) — a task, note, collection, or collection row. Restoring a collection also brings back the rows deleted with it; a row whose list is still deleted can't come back alone (restore the collection instead). A restored task whose project was deleted meanwhile lands in the inbox (movedToInbox: true). Restores one item per call. If the item is already live the call succeeds with alreadyLive: true. **Body** (JSON) | Parameter | Type | Required | Description | |---|---|---|---| | `type` | `"task" \| "note" \| "collection" \| "collectionItem"` | yes | The entry's type, as list_deleted reported it. | | `id` | `string` | yes | The deleted item's UUID. | **Returns** `200` - `restored` (`boolean`) - `alreadyLive` (`boolean`) - `type` (`string`) - `id` (`string`) - `title` (`string | null`) - `restoredItems` (`number`) - `movedToInbox` (`boolean`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/trash/restore" \ -H "Authorization: Bearer roma_…" \ -H "Content-Type: application/json" \ -d '{"type":"task","id":"<id>"}' ``` ## Automations ### GET /automations **List automations** · `Read-only` · MCP tool `list_automations` List the user's automations — standing orders Roma runs on its own, on a schedule or on demand. Returns each one's id, name, what it does, status (active / paused / draft), when it runs, the next run, and how the last run ended. Use it when the user asks what runs on its own, when something runs next, or before reading or starting a run. **Returns** `200` - `count` (`number`) - `automations` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/automations" \ -H "Authorization: Bearer roma_…" ``` ### GET /automations/{id}/runs **Recent runs of an automation** · `Read-only` · MCP tool `get_automation_runs` Read one automation's recent runs, newest first: when each ran, how it ended, what it wrote (one line per action), the message it left for the user, and any error. Use it for "what did my morning brief find", "why did it fail", or to read the result of a run started with run_automation once it has finished. **Path** - `id`: The automation's id (from list_automations or get_context). **Query parameters** | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | `integer` | no | How many runs, newest first (default 5, max 20). (1 to 20) | **Returns** `200` - `automation` (`object`) - `runs` (`object[]`) **Example** ```bash curl -X GET "https://api.roma.app/api/v1/automations/<id>/runs" \ -H "Authorization: Bearer roma_…" ``` ### POST /automations/{id}/run **Start an automation now** · `Updates or removes existing data` · MCP tool `run_automation` Start one of the user's automations now — the same as Run now on its page in Roma. Use it only when the user asks to run a specific automation. The run happens inside Roma with the user's connected apps and can take a minute; its result lands in the user's Roma chat, not in this conversation. Read the outcome afterwards with get_automation_runs. Refused for a draft, while the same automation is already running, or once the daily run limit is reached. **Path** - `id`: The automation's id (from list_automations or get_context). **Returns** `200` - `id` (`string`) - `name` (`string`) - `runId` (`string`) - `status` (`string`) **Example** ```bash curl -X POST "https://api.roma.app/api/v1/automations/<id>/run" \ -H "Authorization: Bearer roma_…" ``` <!-- https://roma.app/developers/guides --> # Guides The tool sequences behind the things people ask for. Each guide names the calls in order; the [tool reference](/developers/tools) has every parameter. ## Start every conversation oriented Call `get_context` before anything else that touches the workspace. One call, about a thousand tokens, and the model knows the person's name and timezone, what is due, what is in progress, their projects with ids, their lists with column ids, and what they asked Roma to remember. Every later call can then name an id instead of searching for it. For a conversation that is about one project, pass `focusProjectId` to narrow the task sections. ## Make Roma the list your assistant reaches for No AI chat has a "default task app" setting. What decides where "add this to my list" lands is the tool description the model reads and the instructions the person gave. Two lines do most of the work: 1. Add this to the chat's custom instructions, a Claude project's instructions, or the memory the assistant keeps: > At the start of a conversation about my work, plans or anything I keep, use Roma's get_context to see my tasks, notes and what I asked it to remember. Save tasks and notes to Roma when I ask. 2. In Claude, set Roma's read-only tools to Always allow under Customize, then Connectors, so the orientation call runs without a prompt. In ChatGPT, choose the permission level for the plugin that suits you. After that, "remind me to call the dentist tomorrow" becomes `create_task` with a due time, and "what did I decide about the lease?" becomes `search` followed by `get_note`. ## Save research as a note in a project A person asks the assistant to research something and keep the result. 1. `list_projects` to resolve the project's name to its id. If none fits, leave `projectId` out and the note lands unsorted. 2. `create_note` with a title and the body as Markdown. Headings, lists and links survive; nothing is rewritten. 3. Later additions go through `update_note` with `mode: "append"`, or `insertAfter` with an `anchor` line to place them under a heading. Before creating, `search` the topic once. What the person refers to usually already exists, and a second copy drifts from the first. ## Fill a list from a conversation "Add these five books to my reading list." 1. `list_collections` to find the list and its columns with their ids. 2. `add_collection_items` with one row per book, values keyed by column id or name. A select value may be a label; an unknown label becomes a new option. 3. Pass `matchOn` (the title column, say) when the same batch might be sent twice, so a retry merges instead of duplicating. 4. Corrections go through `update_collection_items`, never a second add: only the keys you send change. ## Work from a task An agent that does the work, not just the reminding. 1. `get_task_context` for the task: its body, its subtasks, its project. 2. `update_task` with `taskStatus: "inProgress"` so the person sees it moving. 3. Do the work. Append findings, links and drafts to the body with `update_task` (append is the default). 4. `update_task` with `taskStatus: "completed"`, or leave it in progress with a note on what is still open. A call that changes nothing returns the row without writing. A conflict with a write Roma made a moment earlier is retried once on the server, so an edit right after a create lands. ## Read what an automation found "What did my morning briefing say today?" 1. `list_automations`, which also rides `get_context`, to find the automation's id. 2. `get_automation_runs` for its recent runs: the closing message each run left, what it did, when. To start one, `run_automation`. The client asks first, because a run can act through the person's connected apps; the result lands in their Roma chat and `get_automation_runs` reads it back a moment later. ## Undo Every delete is soft. `list_deleted` shows the trash with each item's purge day; `restore_deleted` with the item's type and id brings it back. A task whose project was deleted meanwhile returns unsorted. A row whose list is still deleted asks you to restore the list first. ## Troubleshooting | Symptom | Cause | Fix | |---|---|---| | "not found" for an id you just read | The id belongs to another person, or the item was deleted since | Read it again; check `list_deleted` | | `update_note` refused a replace | A whole-body replacement needs `confirmReplace: true` | Prefer `append` or `insertAfter`; confirm only when the person asked to rewrite | | Rows were added twice | `add_collection_items` without `matchOn` inserts every row | Pass `matchOn`; fix the duplicates with `delete_collection_items` | | A select value was rejected | The value is neither an option id nor a label | Read the column's options from `list_collections` | | A task moved to the wrong project | The destination id was guessed | Resolve it with `list_projects` first | | The conversation cannot see a new tool | The client cached the tool list | Refresh the connector, start a new conversation | <!-- https://roma.app/developers/changelog --> # Changelog What changed on the Roma MCP server, newest first. A client that connected before a change keeps the tool list it fetched; refresh the connector and start a new conversation to see new tools. ## 2026-09-30 - **A REST API** at `/api/v1`: the MCP server's operations over plain HTTP for Zapier, Make, n8n and scripts, with `POST /quick-add` and an OpenAPI document at `/api/v1/openapi.json`. [REST API](/developers/api). - **API keys** beside OAuth: a person creates a `roma_` key under Settings, then Connections, and an agent without a browser sends it as a bearer token. Creating a new key replaces the old one; revocable at any time. [Authentication](/developers/authentication#api-keys). - These developer docs. Every page is also Markdown (add `.md` to its address) and listed in [llms.txt](/llms.txt). - The tool reference is generated from the server's live registration. ## 2026-09-23 - Writes made through MCP are recorded in the person's event log as their own actions, marked as coming through the connection. Before this they read as system activity. ## 2026-09-18 - `get_context` replaces `get_hub`. One call returns the person, today in their timezone, memory (read-only), projects, what is due, unsorted tasks, recent notes, collections with columns, automations with their last run and three days of activity. - `create_project` and `update_project` are new. - `update_task` accepts `projectId` to move a task; `null` moves it to unsorted. - `list_automations`, `get_automation_runs` and `run_automation` are new. - Every tool states all three of `readOnlyHint`, `destructiveHint` and `openWorldHint` explicitly, and carries a title. - `update_task` retries once on a write conflict with a background pass, so an edit right after a create lands. ## 2026-09-04 - `list_tasks` and `list_notes` gain time windows (`createdAfter`, `updatedAfter`, `completedAfter` and their upper bounds) and a `sort` parameter. ## 2026-08-14 - `create_image_upload` is new: pre-signed slots for raw image bytes, for rows whose images come from a source without a stable URL. - Row tools report every image that could not be ingested, per URL and with a reason. ## 2026-08-13 - Collections have full create, update and delete. The trash covers collections and rows. - `list_deleted` and `restore_deleted` are new: every soft delete is now recoverable from a client. ## 2026-08-05 - Collections are on the server: `list_collections`, `get_collection_items`, `add_collection_items`, `update_collection_items`. ## 2026-06-29 - Notes and search: `search`, `get_note`, `list_notes`, `create_note`, `update_note`. - Soft deletes: `delete_task`, `delete_note`. - Every tool carries annotations and an output schema; results return both text and structured content. - Tokens are verified locally; a rate limit of 60 requests a minute per token. ## 2026-06 - First release: tasks and projects over OAuth 2.1 at `https://api.roma.app/mcp`.