Skip to main content
The TypeScript SDK is an ES module, published on npm as @withmarfa/sdk. Works in Node.js, Bun, Deno, and modern browsers (with a compatible fetch). Depends on @withmarfa/shared for wire types and validation primitives. Electron consumers: see Using the SDK in Electron for the externalizeDeps config the main-process bundler needs.

Install

Connect

Configuration

The callback strategy pairs with a per-call resolver — see Conflict handling.

Namespaces

The client exposes one namespace per resource:
  • client.items — create, read, list, update, delete, transition
  • client.edges — create, update properties, delete, list, edge types
  • client.metadata — tags, tier, state mutations
  • client.blobs — upload, download, presigned URL, exists
  • client.types — list, register, describe, delete (custom types)
  • client.keys — create, list, get, update, revoke (admin scope)
  • client.webhooks — create, list, get, update, delete, deliveries
  • client.tenants — current, config
  • client.connections — list, get, configure, pause, resume, uninstall
  • client.admin — operator surface (system-tenant admin)
  • client.auth — sign-in flows plus account.{requestDelete, confirmDelete, cancel} post-sign-in lifecycle
  • client.profile — current user profile

Items

Natural-key upsert

When create() is called with both source_id and a credential-stamped source that already resolve an existing non-trashed row in the tenant, the server short-circuits to update — properties shallow-merge, tags replace if provided, edges replace per-type if provided. The HTTP status flips from 201 to 200; the SDK returns the updated item either way. See Items — natural-key upsert for the wider semantics.

Update signature

items.update(id, properties, options?). The second argument is the property bag itself — { body: "..." } — not a wrapping object with a properties key. The optional third argument carries flow control: version for optimistic concurrency, conflict to override the strategy per call, resolve for the callback resolver.
Conflict-resolution behavior is unchanged. A 409 from the server still resolves through the configured conflictStrategy; the lazy type fetch on keep_both_copies paths still happens automatically. Pass type in options only if you also want to skip that specific lazy fetch.

Edges

Atomic item + edges

Item and edges land in a single transaction.

Bulk writes

client.items.bulk and client.edges.bulk are admin-only. The server stamps source from the credential, so only platform admins can bulk-write across sources — they’re the ingest / migration primitive, not the every-day integration path. Third-party integrations writing many items should call client.items.create with source_id per item; (source, source_id) is the natural upsert key, so re-posts merge in place. See Natural-key upsert above. client.items.bulkAction is a filter-driven mass-mutation primitive — purge is admin-only; the other five verbs require write access on every matched type.
Behind the scenes, bulkAction POSTs, gets a 202 + job envelope from the server, and polls until the worker reaches a terminal status — then resolves with the same BulkActionResult shape callers got from the historical synchronous endpoint. Pass options to surface progress to a UI:
Need explicit control? client.items.bulkActionAsync(input) returns the queued envelope without polling; bulkActionStatus(jobId) and bulkActionCancel(jobId) are thin wrappers over the GET / DELETE endpoints. Semantics and outcome shape are documented on Bulk operations; the SDK methods are thin wrappers.

Metadata

The tier field sits on the item itself, not in metadata — set it on create with tier: "library" or tier: "feed", or flip it post-creation via items.update(id, {}, { 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 wire shape.

Blobs

Conflict handling

The TypeScript SDK exposes the three modes as conflictStrategy on the client config and UpdateOptions.conflict per call. See Conflicts for the resolution model, keep-both semantics, and per-core-type defaults; Errors — version_conflict for the 409 payload.
The callback strategy invokes a resolver with the three-way context plus the type’s resolved mergePolicy; auto (default) and manual need no resolver. A client-level conflictStrategy sets the default; UpdateOptions overrides it per call. When auto runs the SDK emits a conflictAutoMerged event:
Apps typically toast on conflictedCopyId being present — the user’s in-flight edit was preserved as a sibling rather than discarded.

Errors

Typed error classes you can instanceof-check:
All error classes extend MarfaError, which carries code, message, status, and optionally details. Branch on err.code (stable string) rather than message wording.

OAuth helpers (@withmarfa/sdk/auth)

A separately-exported subpath for apps that authenticate users through Marfa rather than holding an API key. Mirrors the OAuth 2.1 + Device Authorization Grant flows documented under Authentication.

MarfaAuth

The high-level helper for the Authorization Code + PKCE flow. Generates the PKCE verifier, builds the authorize URL, exchanges the redirected code for tokens, and refreshes on demand. Pair it with a TokenStorage to persist the resulting token pair.

startDeviceFlow

For headless and CLI contexts. Implements RFC 8628 — initiate, display the verification_uri + user_code, poll until the user approves.

Token storages

MemoryTokenStorage (per-process; default), LocalStorageTokenStorage (browser), and SecureStorageTokenStorage (Keychain on macOS, Credential Locker on Windows, libsecret on Linux — when running under Node) are bundled. Custom backends implement the TokenStorage interface — read, write, clear.

OAuthError

Thrown by every helper. Carries the OAuth error code (invalid_grant, slow_down, expired_token, access_denied, etc.) and the optional error_description. Apps branch on err.code to decide between user-facing messaging and re-flow logic.

PKCE primitives

generatePkcePair() returns { verifier, challenge, method: "S256" } for callers that need to drive the flow themselves rather than through MarfaAuth. The verifier is a 128-byte URL-safe random string.

Webhook signature verification

The top-level verifyWebhookSignature helper validates an incoming inbound-webhook receipt against the subscription’s secret. Wraps the same canonical X-Marfa-Signature header check the server runs — handy for any consumer that wants to verify a forwarded payload (gateway, CI runner) without pulling in the full server.
Returns a { verified: true; deliveryId?: string } shape on success and { verified: false; reason: string } on failure.

Realtime

The TypeScript SDK doesn’t wrap SSE — consumers connect to GET /events directly with the browser’s EventSource API (in a browser) or with fetch + a streaming body reader (in Node.js / Bun / Deno). The realtime page documents the event catalog, Last-Event-ID reconnection, filtering parameters, and the catch-up strategy.

Versioning

The TypeScript SDK follows semver. Major-version bumps mean breaking wire or API changes. Minor bumps add capabilities. Patch bumps are bug fixes. The SDK’s major version tracks the server it was built against.

Browser use

The SDK works in browsers that support fetch, TextDecoder, and ReadableStream. No polyfills needed for evergreen browsers. For older targets, bring your own polyfill. When making requests from a browser, ensure the Marfa server’s CORS_ORIGINS includes your origin (see Configuration).