anytype-api
Call Anytype's local HTTP API v2 directly — search, read, create and edit objects, queries, collections and chats over REST. Use when writing scripts, SDK code or curl against the API. For interactive note/task work prefer the `anytype` CLI and its skill (cmd/anytype/SKILL.md); this guide is the raw-HTTP layer beneath it.
معلومات المصدر
- المستودع
- anyproto/anytype-api
- آخر نشاط في المصدر
- ٢٢ سبتمبر ٢٠٢٦ في ٢١:٣٥
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ١١٣
- التفرعات
- ٢١
خيارات التثبيت
يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.
مراجعة ملفات المصدر
اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.
مستكشف الملفات
2 ملفاتعرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط- name
- anytype-api
- description
- Call Anytype's local HTTP API v2 directly — search, read, create and edit objects, queries, collections and chats over REST. Use when writing scripts, SDK code or curl against the API. For interactive note/task work prefer the `anytype` CLI and its skill (cmd/anytype/SKILL.md); this guide is the raw-HTTP layer beneath it.
# Anytype API v2 — HTTP guide for agents
Local REST API at `http://127.0.0.1:31009` (the Anytype app must be
running). Every call sends `Authorization: Bearer <key>`; keys are created
in the app (Settings → API keys) — **the API mints none**. Bodies are
compact JSON, and every name this API owns is `snake_case` — params,
fields and op names alike. (Inside an object's `blocks` and `properties`
you are reading the AnyBlock format's content; property and type keys
there are served as slugs by default, as display names under
`?keys=name` — see the properties bullet below.) Every list takes
`?offset=&limit=` (default 25, max 1000) and returns
`{data, total, offset, limit, has_more}`.
**First call: `GET /v2/auth/whoami`.** A key may be scoped to particular
spaces and to read-only. `grant.scoped: false` means the whole account;
otherwise `grant.spaces` lists what you may touch and `grant.permission`
whether you may write. Ask this instead of discovering limits through 403s
(`space_not_granted` / `write_not_granted` — the message names the grant).
## The data model in six ideas
- **Spaces** contain everything; nearly every route is
`/v2/spaces/{space_id}/…`. `GET /v2/spaces` lists them.
- An **object** = `properties` (typed key-values) + `blocks` (document
content). `type` is served as a type **key** (`page`, `task`) — never an
id; on input a type NAME (`"Meeting note"`) resolves too.
- **Properties** are addressed by key — a snake_case slug minted once at
creation and frozen, so it survives renames (`due_date`, `icon_emoji`,
`manual_property`). `GET …/properties` spells them that way, and served
documents spell them that way **by default**; `?keys=name` on a read
serves the display-name vocabulary instead (`"Due date"`) — pick slugs
for anything long-lived, names for showing a user their own words.
Input is forgiving everywhere and accepts BOTH: `dueDate`, `DueDate`,
`due-date`, the display name `Due date` and even `Дата выполнения` all
resolve to the one property they name; an input matching two properties
is a 400 listing both, and an unknown one never silently creates
anything. (One exception: the compact `filter` STRING validates before
folding — spell a multi-word name with underscores there, `Due_date`.)
Select/multi_select values are option **names** (`"In progress"`,
case-sensitive) — never option ids. A name the property does not already
hold is **refused** — check it against `GET …/properties/{key}/options`,
or resend with **`?create_missing_options=true`** to create it (a PATCH caps that
at 64). Unknown property keys are rejected with a did-you-mean.
- **Blocks** are a FLAT array in pre-order with an integer `indent`
(absent = 0) — no `children` key. Inline formatting is markdown inside
`text`. Use block ids exactly as a read served them.
- Title and description are **not blocks** — they live in `properties`
(`name`, `description`). A fresh object has zero blocks.
- A **query** is a live query over a type (its type key is still `set`, the
name it carries internally); a **collection** is a hand-curated
list (edited via `add_items`/`remove_items`). **Chats** store messages
outside blocks, paged by order-id cursors.
## Which operation
| Intent | Call |
|---|---|
| find objects | `POST …/{space_id}/search` (or `POST /v2/search` across spaces — rows then carry `space_id`). Search with filters; don't enumerate `GET …/objects` |
| read one object | `GET …/objects/{id}` — start with `?outline=true` |
| change property values | PATCH op `set_properties` — `add`/`remove` for list values, `set` for scalars |
| complete a task object | `set_properties` (`"set":{"done":true}` or the status option) — a property, not a block edit |
| change a word/phrase | op `replace_text` `{find, replace}` — `id` optional; never retype the block |
| toggle a checkbox block | op `update_block` `{"match":"Draft timeline","set":{"checked":true}}` — merge; text untouched. `match` or `id`, never both |
| add content | op `insert_blocks` with a `markdown` payload — write markdown, the server parses it |
| restructure | ops `move_block` / `replace_subtree` / `delete_block` (`delete_block` takes `match` too) |
| one table cell | op `set_cell` — never rewrite the table |
| show/hide a view column, edit a view | op `update_view` — works on queries, collections and a type's default view (PATCH the type OBJECT id from `GET …/types/{key}`) |
| add / reorder / remove a view | ops `insert_view` (`copy_from` duplicates one) · `move_view` (`position:"first"` = default tab) · `delete_view` |
| create an object | `POST …/objects` — shortcut `{type, name, properties, markdown}` covers most cases |
| delete an object you created | `DELETE …/objects/{id}` — archives (Bin, reversible in the app). Only works on objects THIS key created after provenance shipped; anything else → 403 `not_created_by_this_key`, permanently — don't retry, archive in the app instead. Ownership is matched on the app name EXACTLY (byte-for-byte — re-pair under the identical name to keep delete rights). User content only: system objects 403. Probe first with `?dry_run=true` |
| curate a collection | PATCH ops `add_items` / `remove_items` on the collection object |
| read a query / collection | `GET …/queries/{id}/objects` · `…/collections/{id}/objects` (`?view=`, `?fields=`) |
| new type / property | `POST …/types` · `POST …/properties`; select options ride the property, or `?create_missing_options=true` mints them from values |
| upload a file | `POST …/files` (multipart or `{"url":…}`) → the id file blocks and chat attachments need |
| download a file or icon | `GET …/files/{file_id}/content`; use a file id or a space/member's `icon_image`. Optional `?width=` selects an image size. Supports ranges, conditional reads, and `HEAD`. |
| chat | `GET/POST …/chats/{id}/messages`, `POST …/read` — see Chats |
| put an object in the sidebar | `POST …/widgets` `{target, scope}` — `scope:"personal"` is this account's own sidebar (any writer); `scope:"space"` is the shared one (owner/admin; not offered in one-to-one spaces). `GET …/widgets` lists both; `PATCH`/`DELETE …/widgets/{id-or-target}` change or remove one. Not `is_favorite` |
| comment on an object | `POST …/objects/{id}/discussion` → `{id}`, then the chat routes with that id — see Chats |
## Read cheaply
- `GET …/objects/{id}?outline=true` → every block's `{indent, id, type}`
plus its text truncated to 80 runes — structure + addressable ids at a
fraction of the tokens. Follow up with `?block={id}` for one subtree, or
PATCH directly: **editing needs no prior full read once you know the
ids** (but copy exact text from a full read — outline text may be cut).
- When the request already quotes the text to change, skip the read
entirely: `replace_text {find, replace}` locates the block itself, and
`update_block`/`delete_block` take `match` for the same job (one match, or
a refusal listing the candidates).
- `?include=properties` or `?include=blocks` reads half the object.
`?format=md` is a read-only markdown rendering.
- Echo block ids back exactly as a read served them; if one is rejected as
unknown, re-read and use the fresh ids. `?ids=full` is the backup/export
shape — the read to archive or clone from, not needed for editing.
- List/search rows are minimal `{id, name, type}`; add columns with
`fields=` (property keys) instead of GETting each object.
- Every object read returns an `etag` (envelope + `ETag` header).
## Edit: PATCH ops
`PATCH …/objects/{id}` body `{"ops":[…]}` — one atomic batch (≤512 ops,
≤256 blocks per op): any invalid op rejects the whole PATCH with
`ops[i]`-addressed issues. Fourteen ops:
```json
{ "ops": [
{ "op": "set_properties", "set": {"status": ["Done"]}, "unset": ["oldKey"],
"add": {"tags": ["urgent"]}, "remove": {"assignee": ["bafy…"]} },
{ "op": "update_block", "match": "Draft timeline", "set": {"checked": true} },
{ "op": "replace_text", "find": "Q3 report", "replace": "Q4 report" },
{ "op": "insert_blocks", "after": "b3", "markdown": "## Notes\n- first\n- second" },
{ "op": "move_block", "id": "b9", "inside": "b2", "position": "last" },
{ "op": "delete_block", "id": "b4", "recursive": true },
{ "op": "set_cell", "table_id": "t1", "row": "r2", "col": "c1", "value": "done" },
{ "op": "update_view", "columns": {"status": {"hidden": false}} },
{ "op": "insert_view", "name": "Board", "copy_from": "viewAll1",
"set": {"type": "kanban", "groupBy": "status"} }
] }
```
- **`set_properties`**: a key appears in at most one of
`set`/`unset`/`add`/`remove`. `add`/`remove` are per-entry list edits
(select/multiSelect/objects/files) — appending one tag never rewrites
the array. `remove` never creates the option it names. `set: {"k": []}`
= present-but-empty; `unset` removes presence.
- **`update_block`** is THE block-field op (merge; explicit `null` clears a
field) — checkbox, color, language, retype, or full text rewrite.
- **`match` addresses the block by its TEXT** on `update_block` and
`delete_block` — the `id` alternative: give one or the other, **never
both** (and never neither). The text must appear in exactly ONE block or
the op refuses: zero → read the outline, several → the error lists
candidate ids to retry with. Repeats inside the one matched block are
fine — `match` names a block, not an occurrence. It reads the document as
the ops before it in the batch left it.
- **`replace_text`**: `id` is optional — omitted, `find` locates the block
and must appear in exactly ONE block (zero or several matching blocks
refuse; the ambiguity error lists candidate ids to retry with). Within
the matched block `find` must match exactly once ("found 2 matches —
provide more context"); `replace_all: true` is the escape, within that
one block only. Preferred over `update_block` for word-level edits.
`replace_subtree {id, blocks}` swaps a block plus descendants.
- **`insert_blocks`**: `blocks` (flat array) or `markdown` — mutually
exclusive, same targeting. Target with one of `after`/`before`/`inside`
(+`position: first|last` inside that container); omit all three and
`position` picks an end of the DOCUMENT — `last` (or absent) appends,
`first` inserts at the start, both on an empty object too. Payload
`indent: 0` = the anchor's level (`after`/`before`) or the container's
child level (`inside`). `move_block` targets the same way, so
`{"op":"move_block","id":"b9","position":"first"}` moves a block to the top
of the document.
- **Author new content without ids** — an `id` names an EXISTING block, so
`insert_blocks` takes none anywhere in its payload (rows and columns
included); the server mints them and returns them in `created_blocks`,
keyed by the payload path that produced each — `ops[0].blocks[0]`,
`ops[0].blocks[0].rows[1]`, `ops[0].blocks[0].columns[0]`. The same holds
wherever you leave an id out of an existing-content payload (a new row in
`update_block set.rows`, a block inside a `set_cell` array), so you never
have to re-read to learn an id you just created.
- **`update_view`** edits ONE dataview view — never resend the views array.
`block`/`view` are optional when the object has one dataview and it one
view (types, queries, collections usually do). `set` merges view fields
(`name`, `type`, `groupBy`, `sorts`, `filters` — arrays replace whole;
`filter` takes the compact string; null clears a field); `columns` merges
per property key: `{"hidden": false}` shows a column, `null` removes it,
a new key appends one. Works on Blocks-restricted objects — view config
is not a block edit.
- **`insert_view`/`move_view`/`delete_view`** complete the family (same
addressing, same channels; insert_view's name is its own required field —
not in `set`). insert_view needs only `name` — bare default: every listed
property visible, newest first; `copy_from` duplicates a view (then
`set`/`columns` override); the minted id returns in `created_views`,
keyed `ops[i]`. move_view REQUIRES one of `after`/`before`/`position`
(`"first"` = default tab). delete_view refuses the last view — insert the
replacement first (one atomic batch swaps a bad default view).
- Response: new `etag`, `created_blocks` (payload position → real id;
nested row/column/cell slots included), `created_views` (same, for minted
view ids), `created` (options minted under `?create_missing_options=true`),
`diff_stats {blocks_added, blocks_removed, blocks_changed, blocks_moved,
properties_changed}`, `warnings` (advisory, e.g. an unguarded date filter).
- **There is no whole-document replace** — never read a document,
regenerate it and write it back. Replace a section with
`replace_subtree`; start over by batching `delete_block`s with the new
`insert_blocks`.
## Query
`POST …/search` body: `{query?, type?, filter?|filters?, sorts?, fields?}`.
Pagination is the query params — a body `limit` is rejected. Search is a
read: no `Idempotency-Key`, `dry_run` ignored.
```json
{ "query": "report", "type": "task",
"filter": "done = false AND (due_date < currentWeek() OR due_date IS EMPTY)",
"sorts": [ { "property": "due_date", "direction": "asc" } ],
عرض على GitHubملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub