Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.myme.so/llms.txt

Use this file to discover all available pages before exploring further.

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

let note = try await client.items.create(
  CreateItemInput(
    type: "core.note",
    properties: ["body": .string("Hello"), "title": .string("First note")],
    tier: .library,
    tags: ["intro"]
  )
)

let fetched = try await client.items.get(id: note.id)

let page = try await client.items.list(
  filters: ListFilters(type: "core.note", state: .active, limit: 50)
)
// page.data: [Item], page.cursor: String?, page.hasMore: Bool

try await client.items.update(
  id: note.id,
  properties: ["body": .string("Updated")],
  options: UpdateOptions(version: note.version)
)

try await client.items.transition(id: note.id, to: "archived")
try await client.items.delete(id: note.id)     // → trashed
try await client.items.restore(id: note.id)    // trashed → active
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.
for try await item in client.items.all(filters: ListFilters(type: "core.note")) {
  // processed page-by-page; no manual cursor handling
}

Edges

let edge = try await client.edges.create(
  source: note.id,
  target: person.id,
  edgeType: "about"
)

let outbound = try await client.edges.listFromSource(sourceId: note.id)
let inbound  = try await client.edges.listToTarget(targetId: person.id, edgeType: "about")

// Global tenant-scoped listing across all items, by edge type.
let allReplies = try await client.edges.list(edgeType: "in-thread")

try await client.edges.delete(id: edge.id)

Batched backrefs

edges.listToTargets(targetIds:edgeType:limit:) resolves inbound edges for many targets in one call:
let backrefs = try await client.edges.listToTargets(
  targetIds: items.map(\.id),
  edgeType: "in-thread"
)
// backrefs: [String: [Edge]] — keyed by target id
backrefs["msg-1"]?.count   // reply count for msg-1
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

let highlight = try await client.items.create(
  CreateItemInput(
    type: "core.highlight",
    properties: [
      "text": .string("important passage"),
      "locator_type": .string("offset"),
      "start_location": .int(123),
      "end_location": .int(189)
    ],
    edges: [
      CreateItemEdge(edgeType: "references", direction: .outbound, otherId: documentId)
    ]
  )
)
Item and edges land in one transaction.

Custom edge types

Mirrors the TypeScript SDK’s client.edges.types.*. Admin-gated server-side.
try await client.edges.edgeTypes.create(
  CreateEdgeTypeInput(
    id: "team.commented-on",
    cardinality: .manyToMany,
    sourceTypeConstraints: ["core.note"],
    targetTypeConstraints: ["core.note"]
  )
)

let types = try await client.edges.edgeTypes.list()
try await client.edges.edgeTypes.delete(id: "team.commented-on")

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.
// Items
let items = try await client.items.bulk(BulkInput(items: payload))

// Edges — sibling endpoint for graph-half migrations
let edges = try await client.edges.bulk(BulkEdgeInput(edges: edgePayload))
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.
let result = try await client.edges.bulkAll(
  largeEdgeSet,
  batchSize: 500,
  progressHandler: { completed, total in
    await progressModel.update(completed: completed, total: total)
  }
)
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.
let result = try await client.items.bulkAction(
  .transition(
    filter: BulkActionFilter(type: "core.note", state: .active, tags: ["stale"]),
    state: .archived,
    options: BulkActionOptions(dryRun: true, maxItems: 1000)
  )
)
// result.matched / .succeeded / .errored / .dryRun
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:):
let result = try await client.items.bulkAction(
  .purge(filter: BulkActionFilter(type: "core.scratch"), options: .init(confirm: "PURGE")),
  options: BulkActionPollOptions(onProgress: { job in
    Task { await viewModel.update(processed: job.processed, of: job.matched) }
  })
)
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

let metadata = try await client.metadata.get(itemId: note.id)
try await client.metadata.addTags(itemId: note.id, tags: ["urgent"])
try await client.metadata.removeTag(itemId: note.id, tag: "urgent")
try await client.metadata.set(itemId: note.id, input: MetadataInput(tags: ["work"]))
try await client.metadata.merge(itemId: note.id, input: MetadataInput(tags: ["q2"]))

// Distinct tags across the tenant, with usage counts.
let tags = try await client.metadata.listTags()
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?).

Consuming per-item metadata in UIs

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.
try await client.extensions.set(
  itemId: note.id,
  namespace: "my-app.reading_progress",
  data: ["offset": .int(1234), "percent": .double(45)]
)

let all  = try await client.extensions.getAll(itemId: note.id)
let one  = try await client.extensions.get(itemId: note.id, namespace: "my-app.reading_progress")
try await client.extensions.delete(itemId: note.id, namespace: "my-app.reading_progress")

Blobs

let response = try await client.blobs.upload(data: imageBytes, mimeType: "image/png")
// response.hash, response.url

let (bytes, contentType) = try await client.blobs.download(hash: response.hash)
let exists = try await client.blobs.exists(hash: response.hash)
let url = client.blobs.url(hash: response.hash)
let signed = try await client.blobs.presignedURL(hash: response.hash, ttl: 300)
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.
let response = try await client.blobs.upload(
  data: imageBytes,
  mimeType: "image/png",
  onProgress: { sent, total in
    print("\(sent) / \(total)")
  }
)
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.
let results = try await client.search(
  query: "orwell",
  filters: SearchFilters(type: "core.bookmark", tier: .library)
)

// Tag filtering — AND semantics, matching ListFilters.tags.
let tagged = try await client.search(
  query: "orwell",
  filters: SearchFilters(tags: ["fiction", "classic"])
)
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.