Overview
AI agents and scripts can read and write your Anota notes and folders with an API key. There are two ways to connect.
| Type | Endpoint | Best for |
|---|---|---|
| REST API | https://api.anota.info/v1 | Scripts, and AI agents that can make HTTP calls |
| MCP | https://api.anota.info/mcp | AI tools that support MCP, such as Claude |
The OpenAPI 3.1 schema is available at https://api.anota.info/openapi.json (no authentication needed).
Request and response bodies are JSON (UTF-8). Dates are ISO 8601 strings.
Authentication
Issue an API key under "API keys" on your account page. You can show and copy the key again at any time with "Show" in the key list, together with the endpoints and instructions for an AI agent.
Send this header with every request:
Authorization: Bearer anota_xxxxxxxx
- The key identifies your workspace (personal or team), so no other header is needed.
- Keys in the URL query (such as
?api_key=) are not accepted. - Revoked or expired keys get
401.
Scopes
| Scope | Allows |
|---|---|
read | GET requests (reading notes and folders, search) |
write | POST, PUT, and DELETE requests (create, update, delete) |
A write-only key cannot read, so you will usually want both. Missing scopes return 403 forbidden.
What an API key can access
An API key can only reach notes in folders where "Allow access from API keys" is on. You turn this on for all notes or per folder in the app (it cannot be changed through the API).
- Requesting a note in a folder that is not allowed returns
403 api_key_access_disabled. - Lists and search results silently leave out notes in folders that are not allowed, so a page of results may have fewer items than
limit. - Folders created with an API key are allowed from the start.
Note and folder visibility (only me / members) applies just as it does in the app. If your team uses an IP allowlist, it applies to the API too (403 ip_restricted).
Notes
| Method and path | Description |
|---|---|
GET /v1/pages | List notes |
POST /v1/pages | Create a note |
GET /v1/pages/{id} | Get one note, including its body |
PUT /v1/pages/{id} | Update a note |
DELETE /v1/pages/{id} | Move a note to the trash |
POST /v1/pages/{id}/move | Move a note to another folder |
GET /v1/pages/{id}/tags | Get a note's tags |
PUT /v1/pages/{id}/tags | Replace a note's tags |
GET /v1/pages/{id}/backlinks | Notes that link to this note |
GET /v1/pages/{id}/versions | Version history |
POST /v1/pages/{id}/comments | Add a comment (team edition) |
Note object
Endpoints that return a note return an object with these fields (empty fields may be omitted).
| Field | Description |
|---|---|
page_id | Note ID |
title / body | Title and body (Markdown) |
folder_id / folder_name | The folder it is in |
tags | Tags (comma-separated string) |
permission | private (only me) or member (follow the folder) |
version | Version number; increases with every update |
deleted | true if the note is in the trash |
user_id / user_name | Author |
created_at / updated_at | Created and updated times |
GET /v1/pages
Query: folder_id (filter by folder), offset (default 0), limit (default 50, max 100).
{"pages": [ {note}, ... ], "offset": 0, "limit": 50}
No total count is returned. Keep increasing offset until no notes come back.
POST /v1/pages
{
"title": "Meeting notes", // required
"body": "# Agenda\n- ...", // Markdown
"folder_id": "…", // default folder if omitted
"permission": "member", // "private" for only me
"tags": ["meeting", "2026"] // array or comma-separated string
}
Returns the new note with 201. If the free plan's note limit is reached, you get 403 page_limit_reached.
PUT /v1/pages/{id}
Send only the fields you want to change. An empty title or body keeps the current value. Sending folder_id moves the note to that folder.
If you include version, the update succeeds only if nobody has updated the note since that version (otherwise 409 version_conflict). We recommend sending the version you read, to avoid overwriting someone else's changes.
{"body": "# Agenda\n- Updated", "version": 3}
DELETE /v1/pages/{id}
Moves the note to the trash and returns {"success": true}. Notes cannot be permanently deleted through the API (do that from the trash in the app). Notes in the trash cannot be updated (409 page_in_trash).
Other endpoints
POST /v1/pages/{id}/move: body{"folder_id": "…"}.GET /v1/pages/{id}/tags:{"page_id": "…", "tags": ["…"]}.PUTwith body{"tags": [...]}replaces all tags (up to 64 tags, 128 bytes each).GET /v1/pages/{id}/backlinks:{"page_id": "…", "backlinks": [{"page_id": "…", "title": "…"}]}.GET /v1/pages/{id}/versions:{"page_id": "…", "versions": [{"version", "user_name", "created_at", "change_type", ...}]}.POST /v1/pages/{id}/comments: body{"body": "…", "thread_id": "…"}(thread_idonly when replying to a thread).
Search & tags
GET /v1/search?q=keyword: searches titles and the start of note bodies and returns{"pages": [...], "query": "…"}(up to 50 notes).GET /v1/tags: tags in use,{"tags": ["…"]}.
Folders
| Method and path | Description |
|---|---|
GET /v1/folders | List folders, {"folders": [...]} |
POST /v1/folders | Create a folder. Body {"name": "…", "description": "…", "permission": "private"} |
GET /v1/folders/{id} | Get one folder. Add ?include_pages=true to include its notes |
PUT /v1/folders/{id} | Change the name, description, or visibility |
DELETE /v1/folders/{id} | Delete a folder. Its notes move to ?move_pages_to= (or the default folder) |
Main folder fields: folder_id, name, description, permission, is_default, page_count, created_at, updated_at.
Only admins can create, change, or delete folders (for personal use, you are the admin). Folder names must be unique (409 conflict). The default folder cannot be deleted.
Errors
Errors return an HTTP status and a body like this:
{"error": {"code": "version_conflict", "message": "…"}}
| Status | code | Common cause |
|---|---|---|
| 400 | invalid_request | A required field is missing, or the folder does not exist |
| 401 | unauthorized | Missing, wrong, revoked, or expired key |
| 403 | forbidden | Missing scope or permission |
| 403 | api_key_access_disabled | The folder does not allow API key access |
| 403 | page_limit_reached | Free plan note limit reached |
| 403 | ip_restricted | IP address not allowed |
| 404 | not_found | The note, folder, or path does not exist |
| 409 | version_conflict | Conflicts with another update. Read the note again, then update |
| 409 | page_in_trash | Tried to update or delete a note in the trash |
| 500 | internal_error | Server problem. Try again later |
MCP
AI tools that support MCP (Model Context Protocol) can register https://api.anota.info/mcp as an MCP server. It uses Streamable HTTP (one JSON-RPC 2.0 message per POST) and the same Authorization: Bearer API key as REST.
| Tool | Description | Main arguments |
|---|---|---|
search_notes | Search notes by keyword | query |
get_note | Read a note with its body | page_id |
list_folders | List folders | none |
create_note | Create a note | title, body, folder_id |
update_note | Update a note (the body is replaced entirely) | page_id, title, body |
add_comment | Add a comment (team edition) | page_id, body |
Example for Claude Code:
claude mcp add --transport http anota https://api.anota.info/mcp --header "Authorization: Bearer anota_xxxxxxxx"
Access rules (scopes and folder permissions) are the same as for REST.
Examples
List notes:
curl "https://api.anota.info/v1/pages?limit=20" \
-H "Authorization: Bearer anota_xxxxxxxx"
Create a note:
curl -X POST https://api.anota.info/v1/pages \
-H "Authorization: Bearer anota_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"title": "Today", "body": "# Notes\n- Buy milk"}'
Update with the version you read:
curl -X PUT https://api.anota.info/v1/pages/PAGE_ID \
-H "Authorization: Bearer anota_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"body": "# Notes\n- Bought milk", "version": 1}'