anytype
An ergonomic Anytype API client in Rust.
Home | Documentation | Examples
Overview
anytype provides a fluent Rust client for Anytype. It
supports listing, search, and CRUD operations for objects, properties, spaces,
tags, types, members, views, files, and chats, with credential storage and
client-side caching. REST is preferred when it has equivalent functionality;
gRPC supplies capabilities that REST does not expose or represents with less
fidelity.
HTTP calls require an access token. gRPC calls require an account key or
session token. The library can generate and store both credential families in
a KeyStore.
Call AnytypeError::is_authentication() when an embedding application needs
stable authentication guidance. The predicate recognizes direct HTTP and
configuration failures plus structurally typed nested gRPC authentication
failures without exposing or parsing response messages, URLs, or credentials,
and callers do not need a direct anytype-rpc dependency.
The first grpc_client() call selects a nonempty stored session token before
falling back to an account key and initializes one cached channel. Concurrent
first callers share that initialization. find_grpc() discovers a local
Anytype listener on Linux and macOS by filtering lsof listeners and probing
candidate ports in order. Each candidate gets one two-second local budget for
both connection and the unauthenticated AppGetVersion probe; unsupported
platforms and unavailable discovery return None.
Features
- Broad coverage of the Anytype REST API 2025-11-08: nearly every documented REST operation is called directly over HTTP (see Status and Compatibility for the known exceptions)
- gRPC back end provides rich file operations, structured chat messages and streams, typed body blocks, archived-object cleanup, space backup, and process watching
- Paginated responses and async Streams
- Integrates with OS Keyring for secure storage of credentials (HTTP + gRPC)
- HTTP middleware with secret-safe metadata logging, retries, and rate limit handling
- Client-side caching (spaces, properties, types)
- Space administration through typed APIs for chat-space creation, deletion, invitations, and sharing controls
- Deterministic name and ID resolution for spaces, types, templates, chats, views, properties, and tags, with bounded scans and actionable ambiguity errors
- Typed, bounded body-block reads (
bodymodule): validated block trees with exact IDs and order over gRPCObjectShow, plus verified typed create, append, update, delete, move, and bounded non-transactional batch operations - Nested filter expression builder
- Parameter validation
- Metrics
- Used by anyr for Anytype automation from the command line and any-edit for editing Anytype documents as Markdown
Numeric filters support eq, ne, lt, lte, gt, and gte; checkbox
filters support eq and ne. Typed values pass through unchanged in search
expressions and become canonical number text or lowercase boolean text only
where a list endpoint requires URL query values. The client does not coerce
strings to numbers or booleans, accept checkbox 1/0 aliases, or emulate
server filtering after pagination. When typed list filters include one positive
type filter, the client maps it to search's dedicated type selector instead of
sending it as a generic property condition.
REST model fidelity
Type, Property, Tag, and Member retain the REST response's object
discriminator. Responses that omit it use the model's expected discriminator
for compatibility, while an observed value is preserved. Member.icon uses
the typed Icon model.
gRPC file details accept an integral numeric addedDate as Unix seconds as
well as the established RFC 3339 string form. FileObject::target_object_id
is populated only from targetObjectId. createdInContext remains upload
context.
Bounded HTTP responses
Buffered REST responses have finite byte ceilings. Ordinary JSON defaults to
8 MiB, single-object/document JSON to 64 MiB, bounded error bodies to 64 KiB,
and raw file downloads to a separate 256 MiB policy. Truthful oversized
Content-Length responses are rejected before their body is read; responses
without a usable length are stopped at the first byte over the ceiling. SSE
chat events remain incremental rather than buffered as JSON, but each pending
event (including its delimiter) has a separate 1 MiB default ceiling. Incoming
transport chunks are consumed without copying them into the event buffer, and
one chunk may contain several independently bounded events. Overflow terminates
the stream with AnytypeError::ChatSseEventTooLarge before the one-over byte is
appended. Stream space and chat IDs are validated as path-safe before URL
construction or diagnostic logging.
Applications can lower or raise the defaults within the library's hard maxima. An individual object read can choose a smaller ceiling but cannot exceed the configured document allowance:
use *;
# async
The 64 MiB document default accommodates worst-case JSON escaping of a valid
10 MiB outgoing markdown body. The hard maxima are 64 MiB for ordinary and
document JSON and chat SSE events, 1 MiB for error bodies, and 1 GiB for raw
files. AnytypeError::ResponseTooLarge contains only
the selected ceiling and optional declared length; it never retains a response
body, URL, request payload, or credential.
Retry safety
Automatic response, rate-limit, and transport retries are restricted to HTTP
reads: GET, HEAD, and OPTIONS. POST, PATCH, mutation DELETE, and
PUT without documented endpoint-specific replay approval are sent exactly
once. A logical deadline or transport failure after dispatch returns an
indeterminate mutation outcome. A 408, 429, 504, or other server failure is
also indeterminate because the server may have applied the write before
returning or losing the response. Observe fresh server state before deciding
whether to retry. Connection-establishment and request-construction failures
occur before any possible dispatch, keep their typed transport error and
reqwest source, and are safe to retry immediately.
The client disables reqwest's lower-level retry and redirect handling so every
additional send passes through this method-aware policy and its metrics. A 3xx
response is returned as an API error without forwarding the bearer credential
or request body to the Location. Consequently, redirect or retry policies
set on a ClientBuilder passed to AnytypeClient::with_client are intentionally
overridden; timeout, proxy, DNS, TLS, and user-agent customization is retained.
ClientConfig::rate_limit_max_retries continues to control consecutive 429
retries for replay-safe requests; zero disables that rate-limit-specific cap.
Independently, one cumulative ceiling permits at most six physical attempts
across 429, retryable-status, and connection failures, and the counter never
resets when the failure class changes. Caller transport timeouts terminate the
logical request so the shorter caller boundary wins. Retry count does not opt
mutation requests into replay. HTTP metrics expose independent
logical_operations and physical_attempts counters; the existing
total_requests field retains its physical-request meaning.
HTTP deadlines
Each REST request has one absolute logical deadline that covers every physical
send, retry wait, rate-limit delay, response header, buffered body, and JSON
decode. Ordinary requests default to 120 seconds. File and multipart requests
default to 600 seconds. Each paginated page receives a fresh ordinary deadline.
AnytypeClient::with_config also installs a fixed 30-second connection timeout.
AnytypeClient::with_client preserves the caller's connection and request
timeouts while applying the logical policy.
Set an explicit policy when an embedding application owns different request or stream boundaries:
use *;
use Duration;
#
Finite values range from one through 3,600 seconds. None in an explicit
policy disables that boundary. Without an explicit policy,
ANYTYPE_HTTP_TIMEOUT_SECS=1..3600 replaces the four buffered/open defaults
with one value, while 0 disables those four logical boundaries. Malformed,
non-Unicode, signed, whitespace-bearing, overflowed, and larger values reject
client construction. The environment never enables established SSE idle or
lifetime limits.
Successful SSE headers disarm the open deadline before the response body is
returned. Non-success bodies receive a fresh error-body deadline. Established
streams have no idle or total-lifetime deadline by default; when configured,
any nonempty transport chunk resets the idle timer and the lifetime timer never
resets. AnytypeError::HttpTimeout and error.diagnostic() report a closed
class and outcome, sanitized method and path, elapsed time, and physical
attempt count. Timeout metrics are available through http_metrics().timeout,
transport_timeouts, and timeout_outcome_count.
http_credential_generation() exposes only a monotonic process-local number.
It advances whenever the in-memory HTTP key is set or cleared, allowing
principal-bound caches to invalidate entries without reading, retaining, or
hashing the credential itself. Credential replacement and generation advance
share one synchronization boundary; no observer can see a mixed pair.
gRPC deadlines
ClientConfig::grpc_timeouts configures the logical gRPC policy used by the
client's cached AnytypeGrpcClient; the fluent grpc_timeouts(...) builder
sets an explicit policy. With no explicit policy,
ANYTYPE_GRPC_TIMEOUT_SECS=1..3600 supplies one inherited credential,
ordinary, long-operation, and stream-setup value, while 0 disables those
four. The environment does not enable established-stream idle or lifetime
limits and does not alter the five-second cleanup default. Without either
setting, the defaults are 120 seconds for credential, ordinary, and stream
setup, 30 minutes for long operations, and five seconds for cleanup.
An explicit policy ignores the environment. None disables an individual
boundary; finite values are validated before keystore or network side effects.
Credential, ordinary, setup, idle, and lifetime values may be at most one
hour, long operations two hours, and cleanup 30 seconds. Invalid programmatic
or environment policy rejects client construction with AnytypeError::Validation.
Ordinary and long reads return an aborted-read outcome on expiry. A mutation
that may have been dispatched returns mutation_indeterminate; inspect fresh
server state before deciding whether retry is safe. Cleanup uses its own short
bound and is also indeterminate after possible dispatch. Runtime deadline and
stream-control failures remain structurally available below
AnytypeError::Grpc without placing peer status text in standard diagnostics.
Deadline-service transport failures consume and discard the original error
value after deriving a closed tonic status code. The generic error-type marker
remains for source compatibility, while standard source traversal exposes only
a synthetic status with that code and fixed redacted text.
Each request uses the earliest policy duration, absolute enclosing workflow
deadline, or existing tighter grpc-timeout, including time spent waiting for
service readiness. The library's StreamSetup profile stops at successful
response headers and is deliberately not propagated as grpc-timeout, which
tonic treats as a whole-stream limit. For this class, only an explicit caller
whole-call timeout is propagated, reduced to its remaining absolute budget
after readiness; the library setup and enclosing budgets remain local.
Applications that compose several client operations can call
scope_grpc_deadline with one absolute Tokio instant. Every nested generated
gRPC call observes the remaining budget and propagates only that remainder
where the method profile permits it. Channel connection keeps its separate
fixed 30-second boundary. The scope does not change the one-way dependency:
callers use the public anytype API rather than depending on anytype-rpc
directly.
Configured stream idle is reset by raw nonempty transport progress before
message decoding; total lifetime and enclosing deadlines never reset.
chat_stream keeps capped exponential reconnect backoff, resubscription, and
watermark catch-up inside those bounds. Once a raw event has been decoded, its
chat events enter a private pending queue and are delivered before an
already-ready close, transport, or saturation boundary is handled. Output
backpressure therefore does not discard them. An idle expiry can interrupt
delivery; retained items drain first after reconnect, while a lifetime or
enclosing expiry terminates the workflow. Watermarks advance only for delivered
items. The reconnect-attempt counter resets after exactly two delivered decoded
events. An interrupted control mutation is not replayed and may be
indeterminate. ProcessWatcher logs only the status code for stream-read
failures and numeric progress counters, not peer status text, process IDs, or
progress messages.
grpc_client().client_commands() is deadline-aware. Callers that deliberately
obtain the underlying raw channel() bypass these logical boundaries; use
deadline_channel() when constructing another generated tonic client. The
dependency direction is unchanged: anytype uses anytype-rpc, while
anytype-rpc remains independent of this crate.
Secret-safe HTTP diagnostics
The library-owned HTTP diagnostics remain metadata-only at every RUST_LOG
level. The anytype::http target reports stable error variants with an HTTP
status, validated method, and bounded path-only context when available.
anytype::http_json=trace adds request/response byte counts and query-field
counts, but never logs request or response bodies.
No directive for those two HTTP targets enables query values, headers, full
URLs, bearer tokens, credential-bearing URL components, or Anytype document
content. This guarantee is HTTP-specific: other anytype tracing targets are
outside its scope, so applications enabling them need an appropriate filter.
Standard AnytypeError Display and Debug output and its error source chain
exclude all free-form messages, identities, candidate values, last errors,
paths from malformed targets, and typed upstream sources that could contain
request or document content. Use error.diagnostic() for structured
application logs. Raw public fields, including ApiError::message,
RateLimitExceeded::header, validation messages, resolver identities, and
typed sources, remain available through explicit variant matching and must not
be logged without an application policy.
Quick start
use *;
# async
Search pagination limits must be between 1 and 1000 inclusive. Both global and
space-scoped search reject 0 or larger values with AnytypeError::Validation
before sending an HTTP request.
See the Examples folder for more code samples.
Universal object links are constructed locally and do not call Heart's retired
ObjectShareByLink RPC. Use object.get_link() for an object returned by the
API, or client.get_share_link(space_id, object_id)? when both validated IDs
are already known. object.get_link_shared(cid, key)? adds an existing space
invite to the link.
For soft-delete workflows that reconcile uncertain responses themselves,
client.object(space_id, object_id).delete_once() sends exactly one HTTP
request attempt. Ordinary delete() retains the client's replay-safe DELETE
retry policy.
Anytype's canonical Markdown read representation is not always safe to send
back unchanged: for example, a literal underscore in a plain line is returned
escaped. objects::plain_markdown_representation provides separate wire()
and canonical() forms for the deliberately closed subset of empty bodies and
single plain lines containing alphanumeric characters, internal ASCII spaces,
and underscores. It accepts either raw or already-canonical values and is
idempotent on replay. It returns None for punctuation, multiline Markdown,
and ambiguous backslash forms; callers must reject or separately verify those
forms rather than guess at Markdown equivalence or blindly replay export bytes.
The ignored disposable-space matrix in
tests/test_markdown_fidelity.rs characterizes the current server's narrower
export/replacement behavior with two stable REST reads and two fresh
ObjectShow reads on each side of an exact exported-Markdown replacement.
Representative headings, bullet/numbered lists, checkboxes, a one-line quote,
a link, Unicode, and multiline paragraphs retain byte-identical exports.
Consecutive quote lines, fenced code, tables, literal underscores, and explicit
backslash escapes drift at the byte and typed-block-content levels; they have
no replay-stability contract. Every tested PATCH also replaces block IDs, even
when exported bytes stay identical, so exported-Markdown replacement never
promises block identity. The matrix currently establishes no intermediate
typed-semantic-only cohort.
Archived Object Cleanup
use *;
# async
count_archived retains its exhaustive behavior. count_archived_bounded
returns a count only after proving exhaustion within max_pages; each logical
page can make two gRPC requests while probing the supported archive-relation
key, and an exact multiple of 500 rows needs one additional empty probe page.
Offset scans assume archive membership and ordering remain stable for the
duration of the count. The archived search adapter validates ID-only type
metadata but cannot construct the complete key required by Type, so listed
archived objects leave r#type unset instead of constructing a partial type.
Files
Simple uploads, byte downloads, and deletion use REST. File listing, search, metadata, preload, URL upload, and uploads with style/context options use gRPC.
let space_id = "space_id";
let file_id = "file_object_id";
let bytes = client.files.download_bytes.await?;
write.await?;
For image variants, byte ranges, cache validators, or response metadata, use
the configurable request API. It preserves 206 Partial Content,
304 Not Modified, 412 Precondition Failed, and 416 Range Not Satisfiable
statuses for the caller to handle:
let response = client
.files
.download_request
.width
.byte_range
.response_limit_bytes
.error_limit_bytes
.header_evidence_limit_bytes
.max_attempts
.if_none_match
.download
.await?;
println!;
These controls are per request: they never widen or mutate the configured
global response limits. Successful GETs require one canonical Content-Length
that matches the buffered body. Partial responses additionally require one
canonical Content-Range consistent with the requested range and body.
Content-Type, ETag, Last-Modified, and Accept-Ranges are parsed and
validated; duplicates, non-UTF-8 values, contradictions, truncation, and
allowlisted header evidence over the selected ceiling fail with typed,
secret-safe errors. The header ceiling is checked independently before body or
retry processing on every physical response, including intermediate 429 and
retryable-status responses. The attempt ceiling is cumulative across 429,
retryable status, and connection replays.
Use files().metadata(space_id, file_id) for a simple HEAD request. File
deletion moves the object to the bin by default; permanent deletion is explicit:
client
.files
.delete_request
.permanently
.delete
.await?;
Server compatibility, verified against anytype-cli 0.3.6 (API 2025-11-08):
the file endpoint advertises Accept-Ranges: bytes and returns 206, 412, and
416, but supplies neither ETag nor Last-Modified, so 304 Not Modified
cannot be triggered there. File requests use the 600-second long-operation
deadline by default; permanent deletion's independent live regression guard
remains 180 seconds.
files().upload(space).from_path(path).upload() selects REST for a simple
path upload and returns a normalized FileObject. Adding file_type, style,
details, or creation-context options selects the richer gRPC upload.
Callers that already hold an authorized asynchronous reader can stream it without reopening a path or buffering the complete payload:
let file = from_std;
let uploaded = client
.files
.upload
.reader
.mime
.multipart_limit_bytes
.upload
.await?;
The declared length participates in the complete multipart ceiling. Reader uploads fail if the source ends early or yields an extra byte, use REST, and reject gRPC-only rich options.
REST uploads can apply request-local ceilings without changing the client configuration:
let file = client
.files
.upload
.bytes
.mime
.multipart_limit_bytes
.response_limit_bytes
.error_limit_bytes
.upload
.await?;
The multipart ceiling includes the complete boundary and part headers and is checked before authentication or network I/O. The successful and error-body ceilings are independent, and the REST upload POST is sent at most once.
Call resolve_space_id_bounded(reference, page_limit) when a workflow needs a
request-local ceiling on every name-resolution page. Stable space IDs still
return without I/O; names retain the normal finite scan and ambiguity rules.
files().preload(space) accepts either from_path(path) or from_url(url) as
its source and always runs over gRPC, returning the preload file id.
Attached Discussions (REST + gRPC)
Pages and notes can own one derived discussion object. This is not an ordinary
space chat: scope begins with the exact parent, and successful discovery proves
the derived object's space, discussion smart-block type, discussion layout, and
deterministic discussion-<parent_id> unique key.
use *;
# async
get returns the closed AttachedDiscussion::Absent or
AttachedDiscussion::Attached state after a cache-independent REST parent
preflight and bounded gRPC reads. The exact REST wire requires an explicit
layout, and only Basic- and Note-layout parents are accepted. ensure reads
first and never calls the upstream attachment RPC for an already attached
parent. When absent, it dispatches at most one mutation and then rereads the
parent and independently verifies the derived discussion; transport errors,
malformed evidence, and an unconfirmed final state are not retried. Once
dispatch begins, reconciliation continues in an owned task even if the caller
cancels its future. Each gRPC call has a finite deadline capped at five seconds,
the whole operation has a caller-adjustable absolute deadline capped at thirty
seconds. A show that returned a usable view or has an indeterminate dispatch
outcome owns a separate bounded close; a definitive pre-acceptance
authentication or permission rejection returns directly without manufacturing
a close that could mask the original error.
The total budget reserves time for each owned close and, once a write is
admitted, for one fresh reconciliation read.
AttachedDiscussionErrorKind provides closed, payload-free classifications for
unsupported layouts, malformed identity evidence, RPC and operation deadlines,
cleanup failure, upstream failure, owned-task failure, and indeterminate
mutation outcomes. gRPC unauthenticated and permission-denied statuses remain
structural authentication errors without retaining status text. Use
client.attached_discussion_metrics() to inspect cumulative parent GET, show,
accepted-show, close, successful-close, write-dispatch, and reconciliation
counters.
Chats
Space-scoped chat listing, creation, plain-message CRUD, lookup/search, reactions, read state, and per-chat SSE streams use REST:
use StreamExt;
let chats = client.chats.in_space;
let page = chats
.list
.filter
.limit
.list
.await?;
let message_id = chats
.add_message
.send
.await?;
let first_history = chats.older_messages.limit.get.await?;
if let Some = first_history.next_before
let edit = chats
.edit_message
.send_verified
.await?;
assert!;
let mut events = chats
.message_stream
.limit
.heartbeat_seconds
.open
.await?;
while let Some = events.next.await
Structured message blocks, full-fidelity reads, cross-chat previews, reconnect watermarks, and dynamic subscription control remain available as gRPC extensions because the REST representation omits blocks and per-user state.
ChatClient::read_all_account is account-global. Heart's ChatReadAll request
has no space or chat field, and its handler traverses every chat known to the
current session. Only run this operation when the account's complete chat
inventory is safe to mark read. The deprecated read_all(space_id) form
validates its argument but does not send it on the wire. A separate live tier
runs the global mutation alone against a fresh account and tears down its
server process tree afterward.
Older REST history uses a typed page with a 1 through 12 item limit. Its
next_before value is an equality-only opaque server token limited to 256
ASCII graphic bytes. Pass it only to the next older_messages request; do not
parse or sort it. Each returned window preserves Heart's oldest-to-newest
order, while continuation moves to an older window. Message timestamps fail
closed when the server value is out of range and format canonically with UTC
millisecond precision through canonical_chat_timestamp. send_verified
performs GET, PATCH, and an independent GET and fails when the supported edit
does not strictly advance modified_at.
Rich Chat Streaming (gRPC)
use *;
use StreamExt;
// print chat messages as they arrive
async
Body Blocks (gRPC)
The body module reads the rich body of an object (paragraphs, headings,
lists, callouts, tables, bookmarks, LaTeX/Mermaid/YouTube embeds) as a typed,
bounded tree with exact block IDs and exact child order:
use *;
async
Reads are fail-closed: duplicate, cyclic, orphaned, dangling, oversized, or
malformed block graphs fail whole with a typed AnytypeError::BodyGraph
error. A partial or truncated tree is never returned. Per-request
BodyLimits can tighten (never widen) the hard ceilings on block count,
depth, fanout, text size, and mark count. Content the typed layer does not
model (dataviews, widgets, unknown styles or marks from newer servers) reads
as an explicit Unsupported marker carrying only a content-free structural
summary, so trees from newer hearts stay complete, ordered, and honest.
Every possibly accepted ObjectShow owns bounded cleanup established before
the show is polled. A complete foreground ObjectClose is required for
success; cancellation or drop may start at most one bounded fallback close on
the current Tokio runtime. Cleanup failure takes precedence over the show or
application response.
BodyRpcConfig supplies one absolute deadline, a per-RPC timeout, decoder
limits, and a cloneable BodyRpcMetrics observer. ObjectShow is capped at
4,194,304 decoded bytes and every mutation and close response at 65,536 bytes;
callers may tighten but never raise those limits. Reuse one configuration for
the body read and editor when a workflow needs one deadline and one exact set
of payload-free counters:
use Duration;
use *;
async
The write counter advances immediately before the one write future is first
polled. A zero counter therefore proves that validation, authentication,
acquisition, deadline, or cancellation stopped the operation before dispatch.
Higher-level workflows whose steps need independent absolute deadlines can
attach clones of one BodyRpcMetrics observer with
BodyRpcConfig::with_metrics; its snapshot accounts for every configured step
without retaining payloads or identifiers.
After it advances, transport failure, timeout, malformed or oversized response,
cleanup failure, and exhausted verification are
BodyMutationIndeterminate; callers must reread before considering a retry.
Inline emoji marks and callout emoji are 1..64 UTF-8 bytes and control-free.
Mark start and end values are independently validated as ordered, in-bounds
UTF-16 offsets at Unicode scalar boundaries.
Downstream contract suites may opt into the disabled-by-default
test-fixtures Cargo feature. It exposes narrow, production-validated typed
snapshot constructors for exact block-count, read-restriction, and
canonical-table boundary tests. The same feature provides a boolean-only
keystore check that proves a test-owned byte buffer contains none of the
configured HTTP or gRPC credential bytes without returning those credentials.
It does not add deserialization or a general snapshot-forging API and must not
be enabled by production dependents.
Downstream HTTP contract suites may instead opt into scripted-http-fixture.
It provides a finite loopback HTTP script that records bounded method, path,
and body bytes in arrival order. Each script has fixed request, header, path,
body, and response ceilings; its errors and Debug implementations report
only categories and sizes, leaving payload access explicit. This feature is
also disabled by default and must not be enabled by production dependents.
Mutations start from a snapshot and accept only typed constructors and targets
that belong to that snapshot. Every write is sent once, then a bounded fresh
ObjectShow read must prove the exact ID, rich state, and sibling/parent
position before success is returned:
use *;
async
apply_all is explicitly non-transactional: it returns verified receipts for
the completed prefix, the first failure, and the untouched suffix. Timeout,
transport uncertainty, or verification exhaustion returns
BodyMutationIndeterminate with the last complete snapshot when available;
callers must reread before retrying. Bookmark creation has an SSRF-safe policy:
it validates and stores an unfetched absolute HTTP(S) URL but never invokes the
server's URL-fetch RPC. YouTube embeds accept only canonical-izable HTTPS
youtube.com/youtu.be video URLs. Divider style and the complete link-card
appearance (card style, icon size, description mode, and bounded relation-key
list) are typed updates. System singleton, file, table-structural, unsupported,
and operation-restricted targets are rejected before dispatch.
That fail-closed anchor policy also applies to a sibling target's parent and
the existing first child used to encode a first-child insertion. Verified
table creation proves the canonical ordered columns/rows layout regions,
direct column and row membership, dimensions, exact first-row header state,
and Heart's sparse initial cells: no cells without a header, or one ordered
empty paragraph leaf with grey background per column under the header row
only. Missing, extra, misplaced, nonempty, nested, structurally typed, or
noncanonical-presentation cells fail receipt verification; aggregate
descendant counts are never accepted as table evidence.
Cache-independent Space Reads
Use client.space(space_id).get_direct() when an exact mutation preflight or
read-after-write check must bypass the process space cache. It performs one
scoped REST GET, rejects a response carrying a different space ID, and returns
the exact result without reading or priming the cache.
Property and tag mutation builders also provide no_cache_refresh(). The
default behavior continues to refresh a primed property cache, including all
tag pages for select properties. The cache-independent mode performs no hidden
tag reads after the write and instead invalidates that space's property cache;
use property(...).get_direct() and an explicitly limited tags(...).limit(n)
page for bounded semantic readback.
Space Description Updates
client.update_space(id) keeps three operations distinct. Not calling
description(..) omits the field and leaves the description untouched;
description("text") replaces it; clear_description() sends
"description": "", the only wire form that clears on current servers (a JSON
null is silently ignored upstream and is never sent). Servers always return
description as a string: a cleared description and a never-set one both read
back as Some(""), so callers should treat None and Some("") identically
or use Space::description_text(), which maps both to None. The live test
test_space_description keeps this normalization verified against a real
server (anytype-cli v0.3.6, API 2025-11-08).
Type Property Classification (REST + gRPC)
Type.properties is the REST server's flattened visible list: featured
properties appear before ordinary recommended properties, but the wire model
does not expose the boundary and may omit system-featured definitions. Do not
infer replaceability from list position or known property keys. Use the
source-backed classification read when preparing or verifying an exact type
property replacement:
use *;
# async
The read does not inspect or prime the all-types or all-properties caches. It
combines one cache-independent REST type GET with one gRPC ObjectShow of the
same type and reconciles the REST definitions against Heart's authoritative
recommendedFeaturedRelations and recommendedRelations source lists. The
returned recommended list is the complete non-featured set replaced by
UpdateTypeRequest::properties and cleared by clear_properties.
ObjectShow and its exact matching ObjectClose both carry tonic deadlines
and outer timeouts. A close guard is armed before show dispatch; cancellation
or timeout during either boundary starts at most one detached five-second
close fallback. classify_properties() uses the five-second Show maximum,
while classify_properties_with_deadline() accepts a nonzero Show budget of at
most five seconds. Every explicit or detached close owns a fresh independent
five-second window, even when a caller's readback budget has expired. Public
counters expose Show, Close, fallback, and confirmed cleanup success/failure
work without retaining payloads. Cleanup failures take precedence over Show
response errors.
The source lists are capped at 1,000 combined links. Duplicate, overlapping,
malformed, missing, extra, or cross-source-inconsistent evidence fails the
whole read rather than truncating or guessing. The transports are not an
atomic snapshot, so a concurrent edit or eventual-consistency window may
require rereading. gRPC credentials are required. featured_ids preserves the
exact source list; featured contains only definitions visible on the REST
type. Hidden and file recommendation lists are separate Heart concepts and are
not part of this replaceable-property model.
Members
List members with client.members(space_id).list() and read one exact member
with client.member(space_id, member_id).get(). The exact-read builder accepts
the REST API's object-shaped IDs, _participant IDs, and network identities;
the value must remain a URL-unreserved path segment of at most 256 bytes.
Direct Collection Membership
Saved collection views can hide members through filters and pagination. Use
observe_collection_membership when a workflow needs bounded evidence about
one exact object in one exact manual collection:
use *;
# async
The read exact-checks the REST collection and object identities and rejects
Set/query lists. It runs an independent unscoped exact-object query before the
collection-scoped query; an absent result also requires the same unscoped proof
afterward. This control/scoped/control sequence prevents a transient missing
index row from being misreported as absence. Saved view filters and sorts are
never consulted. Each app-global Heart subscription has a unique client-owned
ID, a finite deadline, and cancellation-resilient bounded cleanup. Missing
counters, malformed identities, cleanup failures, or incomplete control
evidence return an error rather than Absent. After a mutation has been
dispatched, callers must treat every such error as an indeterminate mutation
outcome and perform a fresh read before deciding whether retry is safe.
Use collection_member_add when a workflow must add exactly one member and
classify a completed HTTP rejection conservatively:
use *;
# async
The method sends one POST attempt, never follows a redirect, and returns the
exact completed non-success status without reading or exposing its response
body. HTTP 408, 429, 504, and all server failures are indeterminate and require
a fresh membership observation before retry. A transport failure, incomplete
or oversized success response, or malformed success body remains an error for
the same reason. view_add_objects remains the general multi-object API and
does not provide this status-preserving contract.
Use collection_membership_page to enumerate the same canonical direct
membership scope without consulting a selected or saved view:
use *;
# async
Public pages contain at most 61 validated 1..256-byte safe entity IDs in
Heart's direct collection order. Collection scopes ignore an id sort, so the
request carries no sort and the client preserves the returned order without
post-sorting. Each page performs one cache-independent logical HTTP
GET (one through six physical attempts through the shared no-seventh-send
pipeline), one non-replayed Heart subscribe, and one foreground unsubscribe;
an interrupted or failed cleanup can arm only one bounded drop fallback.
A continuation reads one private overlap row to prove its prior boundary and
total are unchanged, then discards that row. Real Heart offset windows report
the complete total while leaving both relative counters at zero, so checked
total/offset/row arithmetic determines whether another page exists. Changed
totals or boundaries, overlap-only results, malformed or nonzero relative
counters, unexpected dependencies, cleanup failure, and Set/query targets fail
closed instead of producing an empty or truncated page. Separate pages are not
snapshot-isolated; restart from the first page after concurrent membership
changes.
client.collection_membership_metrics() returns cumulative, payload-free
counters for validated direct-observer query phases, membership query rounds,
subscribe attempts, foreground close attempts and successes, fallback close
attempts, and collection add/remove dispatches. The observer count starts only
after the exact REST collection and object identities pass validation, so a
Set/query rejection can be distinguished from a canonical membership query.
Cloned clients share the same counters; the snapshot never retains collection,
object, or subscription identifiers.
Status and Compatibility
The crate targets the Anytype REST API dated 2025-11-08. Coverage is described in two parts, because the two transports do not cover the same ground:
objects(space).filter(...).list() keeps ordinary filters on the documented
object-list endpoint. Requests containing number or checkbox filters use the
space-scoped REST search endpoint internally, because the object-list query
parser in anytype-cli 0.3.6 rejects those typed values after URL decoding.
The public builder, AND composition, HTTP-only authentication, pagination, and
archived-object behavior remain unchanged.
- Direct REST coverage - operations the crate performs over HTTP against the documented REST surface. Nearly every documented operation is covered directly, including auth, spaces, types, properties, tags, objects, templates, views, members, search, basic file transfer (upload, byte download, metadata, ranges, conditional requests, delete), and space-scoped chats (list/create, plain message add/edit/get/list/search/delete, reactions, read state, and the single-chat Server-Sent Events stream). No exact percentage is published, because the upstream operation list changes with each Anytype release, and a few surfaces (such as cross-space chat discovery) are deliberately reached only through gRPC.
- gRPC-equivalent coverage - capabilities reached through anytype-heart's gRPC service where REST has no operation or returns less information. These are additional coverage, not a substitute for a missing REST call, and they require gRPC credentials at runtime.
The current transport mapping - which method uses which transport, and why - is recorded in API surface.
Plus:
-
View Layouts (grid, kanban, calendar, gallery, graph) implemented in the desktop app but not in the api spec 2025-11-08.
-
gRPC back-end provides API extensions for features not available in the REST api:
- File metadata, listing/search, preload, URL upload, and rich upload options.
- Structured chat blocks, full-fidelity message reads, chat-object search, name resolution, cross-chat previews, and reconnecting subscriptions.
- Exact featured versus replaceable type-property classification.
Apis not covered
The current Anytype http backend api does not provide access to some data in Anytype vaults.
FilesUpdate: REST supports basic transfer; gRPC supplies richer file operations.Chats and MessagesUpdate: REST supports chat management and plain message operations; gRPC supplies structured messages and richer streams.- Blocks. Pages and other document-like objects can be exported as markdown, but markdown export is somewhat lossy, for example, in tables, markdown export preserves table layout, with bold and italic styling, but foreground and background colors are lost.
- Relationships - only a subset of relation types are available in the REST api.
Cargo features
The crate has no default features (default = []), and there is no grpc
Cargo feature. anytype-rpc is an unconditional dependency, so every
gRPC-backed method is always compiled and callable; what a gRPC-backed method
needs is gRPC credentials in the keystore at run time, not a build-time flag.
Building with --no-default-features therefore changes nothing.
Both optional features are disabled by default and reserved for tests:
test-fixtures exposes narrow typed snapshot constructors and a boolean-only
credential-leak check, while scripted-http-fixture exposes the finite
loopback HTTP script. Production dependents must not enable either feature.
Keystore
A Keystore stores authentication tokens for http and grpc endpoints. Various implementations store keys in memory, on disk, or in the OS Keyring
GrpcCredentials::from_cli_config reads account credentials from the Anytype
CLI's default ~/.anytype/config.json, or from an explicit path, without
storing them. A missing file is reported separately from malformed or
unreadable configuration so account-bootstrap callers can fail safely.
See the keystore reference for backend selection, environment credentials, and encrypted file storage.
Known issues & Troubleshooting
See Troubleshooting
For keystore-related issues, see the keystore reference.
Eventual Consistency
Anytype servers have "eventual consistency" (This is a feature of practical distributed systems, not a bug!). How you might encounter this in your programs:
- Create a new property and then immediately create a type with the property, and get an error that the property does not exist.
- Create a new type and then create an object with the type, and get an error that the type does not exist.
- Delete an object, then immediately search for it, and find it.
The amount of time needed for "settling" seems to be 1 second or less.
anytype can perform validation checks after creating objects (objects, types, properties, and spaces) to ensure they are present before create() returns. Since this verification can cause delays, it's opt-in. While there are some knobs you can tune to adjust backoff time and number of retries, the easiest way to add verification is to call ensure_available() before create for critical calls:
let obj = client.new_object.name.ensure_available.create.await?;
For mutation workflows that must confirm more than availability, use
verify_semantic with a predicate over a freshly fetched value. It retries
successful-but-stale values as well as transient not-found, transport, retry,
and server failures. Verification always has both a wall-clock deadline and a
validated nonzero attempt cap no larger than MAX_VERIFY_ATTEMPTS; legacy zero
and oversized values safely clamp to that hard ceiling, and zero-delay
configurations remain finite and cancellation-safe. Fetched values and upstream
error text are never retained in the terminal verification timeout.
To enable verification for all new objects, types, and properties, add .ensure_available(VerifyConfig::default()) to the config when creating the client. Setting this in the client configuration is not recommended except for an environment like unit tests where you're hammering the server and need to get results immediately. If verification is enabled in the client config, it will be applied to all create calls, unless disabled on a per-call basis by using .no_verify():
let obj = client.new_object.name.no_verify.create.await?;
Building
Requirements:
- protoc (from the protobuf package) in your PATH. On macos,
brew install protobuf - libgit2 in your library path.
Testing
The maintained HTTP/gRPC coverage inventory separates direct unit and live coverage from cross-crate integration evidence and records the remaining blocked or deferred gaps.
Set environment flags for unit and integration tests. You'll also need a running Anytype server (CLI or desktop).
# HTTP endpoint. Default: http://127.0.0.1:31012
# Headless cli default port is 31012. Desktop app uses port 31009
# Set the same for ANYTYPE_TEST_URL
# optional: set keystore to custom path
# required: prefix for uniquely named, cleanup-owned integration-test spaces
# optional: enable debug logging. Default "info"
# optional: disable rate limits. If not disabled, tests will take longer to run
Keystore modifiers use :key=value boundaries. Path values may contain a
Windows drive colon or ordinary colons that are not followed by another
modifier key and =.
Test helpers honor ANYTYPE_KEYSTORE when it is set and use the in-memory
env keystore otherwise. Set the required HTTP and optional gRPC credentials
in the environment when tests need authenticated server access.
Each shared integration-test context creates a fresh space whose name starts
with ANYTYPE_TEST_SPACE_PREFIX, then deletes that exact space after the test,
including callback error and panic paths. Reserve the prefix for automated
tests. A missing or invalid prefix fails setup before authentication with a
configuration error; no ambient space-ID environment variable is consulted.
The disposable-space recovery harness stores its ledgers in a private runtime
directory: Unix ownership and permissions are verified from open handles. On
Windows, the owner and every access-granting ACL entry must name the process
user, LocalSystem, or Built-in Administrators. Links and reparse points fail
closed.
Unauthenticated control tests explicitly use unique empty temporary file
keystores, so ambient env credentials cannot change their expected result.
Run smoke test
Run all tests
cargo test -- --nocapture
When the real server's mutation rate limit remains enabled, use
cargo test -- --test-threads=1 to keep the full live suite from flooding its
shared endpoint. Pagination coverage owns a uniquely filtered, cleanup-tracked
object cohort and does not depend on unrelated ambient-space objects.
Space-creation requests validate a nonempty bounded name before HTTP; validation
coverage never probes this rule by creating an untracked unnamed space.
Empty-filter coverage likewise owns its expected object rather than depending
on pre-existing content in the configured test space.
Integration tests require a running Anytype server and environment variables. See src/client.rs for details.
On anytype-cli 0.3.6, DELETE ...?skip_bin=true can take about 154 seconds
to return 204 No Content. The permanent-delete live test keeps the request
under a finite 180-second wall-clock ceiling, matching the CLI live-test command
budget while still preventing an unresponsive endpoint from wedging the suite.
The crate no longer ships a semantic gRPC mock server. Successful gRPC behavior is covered with cleanup-owned resources against the configured real Anytype server. Protocol and reducer edge cases use scripted transport handlers or constructed values without pretending to implement Anytype semantics. Disconnect, latency, and other connection-fault scenarios require the reviewed external fault-injection harness and are not emulated by an in-process gRPC service.
Chat resolver integration tests create cleanup-owned chats and messages in a
fresh prefix-authorized space on the configured real HTTP and gRPC endpoints.
Supporting REST reads and the REST SSE test use the same disposable tier so the
resolver and stream files remain runnable when the server has no ambient
spaces. Broader pre-existing REST CRUD, search, reaction, and read-state cases
remain in the ambient test_chats tier and are not part of the mock migration.
Every created message is registered immediately, before stream waits or
assertions, and the gRPC stream worker is shut down before teardown.
Body reader integration tests create cleanup-owned objects in a fresh prefix-authorized space on the configured real HTTP endpoint, then verify typed reads, ordering, close-safe repeat reads, tightened limits, and missing-object failures through the configured gRPC endpoint. The adjacent dataview test was not formerly mock-backed, but shares the disposable tier so the body test file does not require ambient inventory.
The required tier also creates a disposable collection and a source-backed Set, then proves both server-created views and their object listings without reading or registering ambient list objects. The Set fixture uses the authenticated Heart creation RPC because REST object creation cannot supply its internal source. Run every required case through the admitted serial driver:
The checked-in live-gate manifest assigns every ignored test to the required, manual soak, or excluded tier. The manual workflow selector can run either live tier or both. Verify that closed inventory without a server:
The driver runs every admitted entry in its own process and rejects zero-test
and skip results. The required tier uses a sync-isolated server. The small soak
tier uses a connected disposable server because Heart's space-sharing command
calls its coordinator service; every created resource remains cleanup-owned.
Sharing enablement retries only Heart's definitive NO_SUCH_SPACE response
while a newly REST-created space enters the administration service.
With the same protected environment loaded, reproduce the two focused Set/view entries exactly:
Process watcher import-finish coverage uses a real Markdown import in the fresh
cleanup-owned space created by with_disposable_space_context. The watcher
subscribes and unsubscribes from the configured gRPC server, accepts empty-space
fallback events only for import requests that explicitly enable the fallback,
and applies fixed timeouts to every live stage. The test is ignored under
ordinary runs because it requires a configured real server and explicit
disposable-process admission. The real server may complete the ordinary import
process before publishing the import-finish event; the test uses the same
subscription for a second bounded wait and proves that no new process was
correlated while observing that fallback.
Tests that need a custom collection can use the hidden
TestContext::create_collection_type_fixture helper. Anytype's REST type
create/update contract rejects collection layout, so this test-only helper uses
the narrow heart RPC, registers the returned type for cleanup before any
follow-up read, and verifies it through the ordinary scoped REST getter.
Tests must create the object through
TestContext::create_collection_fixture; ordinary cleanup registration does
not grant view-mutation authority. This helper accepts only a collection type
owned by the context, takes a complete type-scoped pre-create snapshot, and
atomically records its cleanup dispatch and exact (space, object, type)
provenance. Any collision with an authoritative cleanup ID or existing private
claim is rejected without changing either registry.
TestContext::create_collection_view_fixture then requires that provenance,
requires the REST object to retain the exact proven type ID, and cross-checks
every REST-visible default-view field against the exact
ObjectShow root and dataview block, clones the full proto, and issues one
BlockDataviewViewCreate RPC. It requires exactly one matching view-set event,
a distinct server-assigned ID, and complete nested-view equality before a
finite exact two-view REST verification. Collection teardown owns the added
view; there is no general view-create production API.
TestContext::add_collection_name_filter_fixture may then add one exact-name
filter only to that cleanup-owned view. It accepts initially unfiltered REST
and ObjectShow evidence, sends one authenticated filter-add RPC, and requires
the assigned filter ID and complete value to reread identically through both
surfaces. Collection teardown owns the filter with the view; this remains test
infrastructure, not a production view-filter API.
Representative Kanban tests can use TestContext::create_kanban_fixture inside
with_disposable_space_context. The helper creates and immediately registers a
custom card type, its Select grouping property and two status options, a
collection, an existing server view converted to Kanban, and three cards. It
adds the grouping relation through heart before setting the layout, rejects
pre-existing filters, resolves Heart's internal relation key separately from
the REST property key, and independently rereads the exact relation format,
view grouping key, tags, membership, and card values. Membership verification
uses two-item pages so pagination is exercised rather than bypassed.
move_kanban_item_fixture performs an ordinary object Select-property update
and requires the moved card and complete board to reread exactly. Missing or
wrong-format relations, removed options, filtered views, malformed pagination,
or unregistered resources fail closed. Collection deletion owns view cleanup;
property cleanup owns its options.
Tests that need disposable spaces should use
TestContext::create_space_fixture. It creates through the authenticated REST
API after taking a complete bounded inventory whose pagination, IDs, names, and
uniqueness are validated. A response is registered exactly once only when its
valid ID was absent from that inventory, its name exactly matches the request,
and it is a regular space distinct from the context space. The private registry
retains that exact ID/name provenance. Untrusted or ambiguous responses are
allowed to leak rather than authorize deletion of ambient state. Registration
occurs before follow-up verification. Teardown revalidates exact ID/name/model
provenance through the same strict inventory before Anytype's irreversible
SpaceDelete RPC, then requires complete bounded REST evidence that the ID is
gone even when the delete response is uncertain. The test-only ownership
registry remains separate from the explicit AnytypeClient::delete_space API,
which callers must protect with their own confirmation policy.
Whole live suites should prefer with_disposable_space_context. It creates a
fresh cleanup-owned space under the mandatory ANYTYPE_TEST_SPACE_PREFIX.
That ASCII prefix is an explicit authorization to delete every space whose
current name starts with it, case-insensitively; reserve it exclusively for
tests. Missing or invalid configuration returns a typed DisposableRun::Skipped
before credential access or filesystem I/O. One same-host backend-wide file
lease serializes participating runs. An owner-private durable ledger and
disk-backed enumerate-before-delete offset-pagination plans recover interrupted
matching runs without a count ceiling or an in-memory inventory. Each fixed
pagination window shares one deadline; a changing total discards the plan and
restarts at offset zero. New names use 128 bits of operating-system randomness.
Readiness has a hard 20-second and 50-attempt budget. It resolves the exact
@page key without a cache, then direct-GETs that returned type through the same
validated space path and requires identical ID, page key, and non-archived
state. A failure reports only its final closed stage/category and completed
attempt count. Create failures likewise expose only a closed setup stage and
category, distinguishing rejected or indeterminate requests from invalid ID,
model, name, or ambient-identity evidence without rendering response values.
The numeric/checkbox acceptance callback similarly reports only a closed
fixture or comparison stage and a payload-free TestError/API diagnostic
category, which proves whether execution crossed the callback boundary without
exposing fixture identities, queries, endpoints, or upstream bodies. Its
ignored compatibility matrix executes all eleven fixed cases independently on
both endpoints even when an earlier check fails, then reports all 22 static
endpoint/case pairs in canonical order with only their closed categories and
validated HTTP status/classes when available. The regression assertion is
evaluated only after disposable cleanup completes.
All three diagnostic paths store exhaustive enums rather than caller-provided
strings, so Display, Debug, and accessors can render only the documented
closed vocabulary. The filter fixture resolves its prerequisite due_date
property through the bounded, cache-independent property resolver because the
disposable client intentionally disables cache state.
The two immediate pre-delete checks and final absence proof also
use cache-disabled direct exact-ID reads. The helper cleans registered children first and retains callback,
cleanup, deletion, absence, ledger, and panic outcomes; an unproven absence is
always dominant without discarding the original typed error or simultaneous
cleanup evidence. Remote backends require an equivalent scheduler lease and are
otherwise rejected. Operators must not create, rename, or delete spaces through
another client while the helper holds its lease.
Disposable runs require ANYTYPE_KEYSTORE=env, an explicit
ANYTYPE_KEYSTORE_SERVICE, a nonempty ANYTYPE_KEY_HTTP_TOKEN, and at least
one nonempty gRPC session token or account key. They must run in a dedicated
single-threaded integration-test process admitted with
ANYTYPE_DISPOSABLE_TEST_PROCESS=1; the process must not mutate its environment.
File, keyring, implicit, unknown, malformed, and over-budget credential forms
skip before authentication, private state, or mutation. The helper creates no
credential file. For a spawned production child, call
ctx.disposable_child_environment().unwrap().configure(&mut command) before
spawn, then register an idempotent stop-and-wait handle with
ctx.spawn_owned_child(...). Configuration uses env_clear, reconstructs only
the approved endpoints, finite limits, MCP settings, and exact accepted
credential names, and rechecks the whole environment/argument block budget.
The helper records child-running state before invoking the spawn closure and
stops all registered children before resource cleanup and space deletion.
Recovery refuses every cleanup plan and prefix sweep while a prior ledger says
its child may still run. The first refusal durably records that the operator
must prove the child stopped or is gone. Only after that proof may the operator
set ANYTYPE_DISPOSABLE_RECOVER_STOPPED_RUN to the exact recorded .json run
handle for one invocation; the helper persists the stopped transition before
applying that ledger's plan, and rejects stale or repeated confirmations.
Destructive execution is enabled only where owner and owner-only permissions
can be proved for the runtime directory and every recovery target. Unix opens
and removes exact components relative to verified directory handles with
no-follow semantics. Windows creates protected ACLs, admits only the process
user, LocalSystem, or Built-in Administrators as owner and access-granting
principals, and rejects reparse points before recovery I/O. Recovery files are
flushed before publication; NTFS supplies directory-entry persistence because
Windows rejects FlushFileBuffers on directory handles.
Tests that need templates can use the hidden
TestContext::create_template_fixtures helper with one to sixteen source
names. It creates a private custom type and source object for each requested
template, invokes the authenticated heart template-from-object RPC exactly once
per source, and verifies the returned IDs through a finite complete type-scoped
list plus exact GETs. Complete bounded type, space-wide active/archived object,
and global template inventories prove create responses did not reuse
pre-existing IDs; the global inventory also proves the new template is owned
only by the expected type, while list and GET generic-template identities must
agree. The helper registers every created ID before classifying
the RPC response or reading it back. Teardown issues each template, source, and
type archive request once in reverse dependency order, then proves the
templates absent and the sources and type archived. Production consumers do
not gain a template mutation API.
License
Apache License, Version 2.0
Contributing
Feedback, Issues and Pull Requests are welcome.