Namespaces
The client exposes one property per resource, plus top-level search and health:
client.items — item CRUD, transitions, versions, stats
client.metadata — tags, metadata merge operations
client.extensions — namespaced sidecar data
client.edges — edges and custom edge types
client.blobs — upload, download, URLs
client.types — type schema listing and registration
client.keys — API key management (admin)
client.webhooks — webhook registration and deliveries
client.search(query:filters:) — full-text search
client.health() — server reachability check
Items
items.list returns PaginatedResult<Item> with data, cursor, hasMore. Two AsyncSequence helpers walk every page automatically — client.items.all(filters:) yields Item, and client.items.allWithMetadata(filters:) yields ItemWithMetadata pairs for views that render tags or favorite flags alongside the item.
Edges
Batched backrefs
edges.listToTargets(targetIds:edgeType:limit:) resolves inbound edges for many targets in one call:
In synced and pure-local modes this is one SQL query against the local store. In remote mode it fans out via a bounded TaskGroup; cap concurrency via ClientConfiguration.maxBackrefBatchConcurrency (default 8). Empty targetIds short-circuits to [:], duplicate ids are deduplicated, and unknown ids map to an empty array.
Atomic item + edges
Item and edges land in one transaction.
Custom edge types
Mirrors the TypeScript SDK’s client.edges.types.*. Admin-gated server-side.
Bulk writes and chunked iteration
Admin-only bulk surfaces match the bulk operations shape. Pure-local clients iterate through the local store and return per-record outcomes; synced clients enqueue a single replay record; network clients round-trip straight through.
For inputs larger than the server’s 5000 per-call cap, the Swift SDK provides client-side chunking helpers — items.bulkAll and edges.bulkAll. Both clamp batchSize to [1, 5000], aggregate per-record outcomes with absolute indices preserved, and accept an optional progressHandler for UI wiring.
Cross-batch atomicity does not hold — each batch’s atomic: true guarantee stops at its own server transaction. Callers needing strict all-or-nothing semantics across a run larger than one batch reconcile failures out-of-band.
items.bulkAction(_:) is the filter-driven sibling of items.bulk — instead of taking a list of records, it applies one action across every item matching a BulkActionFilter. Use it to archive, trash, restore, purge, retag, flip tier, patch properties, or bump updated_at across a server-side slice without fetching ids first.
BulkActionInput is a discriminated enum — .transition, .purge, .updateTags, .updateLibrary, .updateProperties, .updateTimestamp. Purge requires options.confirm == "PURGE". Synced clients queue the action for replay; pure-local clients apply it in a single local-store transaction.
In remote mode, bulkAction(_:options:) polls the server-side job until it reaches a terminal status — completed, failed, or cancelled. Surface progress to the UI by passing BulkActionPollOptions(onProgress:):
For explicit-control flows (returning a job id to an LLM, driving polling from a long-lived UI), bulkActionAsync(_:) POSTs and returns the BulkActionJob envelope without polling; bulkActionStatus(jobId:) and bulkActionCancel(jobId:) wrap the GET / DELETE endpoints. Cancellation throws BulkJobCancelledError; a failed job throws BulkJobFailedError carrying the worker’s reason.
metadata.listTags() is mode-aware. In synced and pure-local modes the call fetches metadata rows whose parent item is not trashed and buckets the tags in Swift; in remote mode it hits GET /metadata/tags. Either way the return shape is [TagWithCount] ordered count DESC, tag ASC. Local-mode reads short-circuit the network round-trip.
The tier field sits on the item itself, not in metadata. Set it on create via CreateItemInput.tier, or flip it post-creation via items.update(id:properties:options:) with UpdateOptions(tier: .library). Tier-only updates don’t conflict — tier is metadata-axis, last-writer-wins. system.* items have no tier; the field is optional in the SDK shape (Tier?).
Two paths, depending on client mode:
-
Synced / pure-local mode. Apps render lists of items with their tags or favorite flag — a timeline, a grid, a detail view. Use the SDK’s reactive layer rather than polling. In Swift,
store.queryItemsWithMetadata(filters:) returns an @Observable ItemsWithMetadataQuery that re-emits [ItemWithMetadata] pairs whenever any matching item or metadata row changes. The query reads from the local SQLite store, so no network round-trip per view frame. See SwiftUI reactive queries for the full list.
-
Remote-only mode. Fetch pairs in bulk via
client.items.listWithMetadata(filters:) (wire: GET /items?include=metadata). For ad-hoc single-item lookups use client.metadata.get(itemId:).
Apps built against Marfa before the reactive variant existed often hand-rolled a cache that subscribed to the sync engine’s events stream and re-ran listWithMetadata on each mutation. With ItemsWithMetadataQuery available, that bespoke plumbing is redundant — the SDK owns the observation and invalidation.
Extensions
Namespaced sidecar data. Writes route through the mutation queue in synced mode.
Blobs
upload accepts an optional onProgress: (@Sendable (Int64, Int64) -> Void)? for network-only clients that want inline byte-progress updates. The callback fires on the URLSession delegate queue with (bytesSent, totalBytes) — hop to the main actor yourself if you’re driving SwiftUI state from it. Synced-mode clients ignore the callback; progress for queued uploads arrives via BlobUploadProgressQuery on the reactive layer instead.
Blob operations require a live server. A pure-local client throws LocalModeUnsupportedError for every blob method except url(hash:). The same guard applies to client.types, client.keys, and client.webhooks.
Search
SearchFilters.tier is a TierFilter? — pass .library or .feed to narrow, or omit (nil) to return both. There’s no .all synonym; nil already expresses that. SearchFilters.tags requires items to have every listed tag. system.* items are excluded from search by default; opt in with an explicit type: "system.<X>" filter.