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.

An Integration is a publishable, installable description of how Marfa talks to an external service or runs a space-internal automation. It carries everything a space needs to stand up a working integration-kind Connection: the trigger declarations, the OAuth or webhook authentication shape (where applicable), the runtime requirements, the bidirectional handling rules, and the permissions the connector needs against the space’s items. Integrations are persisted as system.integration items — one item per (name, version) pair. A space installs one specific version into a Connection; later versions are sibling items, not edits.

The manifest contract

The manifest is the canonical description. The platform validates it on registration and on every install. Required fields:
FieldMeaning
namePublisher-namespaced identifier — acme.calendar-sync. Same shape as a publisher type identifier.
versionSemver. Used to dedupe (name + version is the registration key).
publisherHuman-readable publisher name shown to users at install time.
descriptionOne-line summary shown to users at install time.
manifest_schema_versionSemver of the manifest contract itself. The server rejects manifests whose major exceeds the supported major.
directionread, write, or both — the data flow this connector implements.
target_typesItem types the connector reads or writes.
triggersArray of discriminated { type, config? } objects. type is one of schedule, webhook, item-event, manual. Schedule triggers carry config: { cron }; the others have no config payload today.
runtime_compatibilityOne or more of hosted, self-hosted, local — which runtime tiers can host this connector.
bidirectional_handlingThe four positions: echo_ttl_seconds, lag_window_seconds, tombstone_mapping (`state-trashedprompt-userignore), partial_write_mode (all-or-nothingaccept-partial). Required even when direction !== “both”`; defaults absorb the read-only case.
oauth_requirementsPer-capability map — proxy (route through Marfa’s OAuth proxy) or leased (issue a short-TTL bearer for the connector’s own use).
token_requirementsPer-capability map for connectors authenticated by a static upstream API token (rather than OAuth). Each entry declares required; the install pipeline gates on a matching kind: api_token credential being supplied as credential_ref.
webhook_verificationDiscriminated on method: hmac-sha256, slack, stripe, github, google-channel, or cloudflare-email. The platform’s adapter for each method lives in @withmarfa/webhooks and is consumed by both the Cloudflare control plane and the server.
permissions (optional)Per-axis grant — extension and edge maps. The install pipeline translates these onto the runtime credential.
Full schema and each field’s accepted values live in the IntegrationManifestSchema Zod schema in @withmarfa/shared — published as a JSON Schema artefact alongside the SDK.

Runtime tiers

A connector runs on one of three runtime tiers. The manifest’s runtime_compatibility declares which tiers it supports; a space’s deployment chooses among them. The enum values are hosted | self-hosted | local.
Tier (runtime_compatibility value)When it applies
hostedThe default for TypeScript connectors. Each Integration deploys as a Cloudflare Worker; per-Connection state lives in a Durable Object; queues handle scheduled polls, webhook receipts, and reactive runs.
self-hostedThe connector runs inside a self-hosted Marfa deployment’s own runtime — a Node.js process or container the operator manages directly.
localThe connector runs on the user’s machine, not in hosted infrastructure. Used by the sync agent and by users who prefer to keep the runtime under their own control.
Self-hosted Marfa deployments can mix self-hosted and local tiers; the manifest’s compatibility list determines what’s available where.

Install flow

The platform install pipeline is uniform across runtime tiers. The user installs an Integration through the console; the server then:
1

Validates the manifest

The Zod schema check, plus type-identifier and scope-grammar validation. Rejected manifests never produce a Connection.
2

Renders the consent screen

Mirrors the /auth/authorize consent precedent — shows the user the scopes the connector wants, the runtime tier, the manifest’s declared OAuth requirements. The user approves, modifies, or cancels.
3

Persists the credential

The credential shape follows the connector’s authentication model. For OAuth-backed connectors, the user supplies the OAuth provider config (client_id, client_secret); the server encrypts the secret and creates a kind: oauth_token system.credential. For token-authenticated connectors (manifests declaring token_requirements), the user supplies an upstream API token; the server encrypts the bearer and creates a kind: api_token credential carrying the upstream base URL and the auth_scheme it expects (Bearer, Token, or Basic). Both shapes are referenced by the Connection via credential_ref and resolved by the proxy at request time.
4

Mints a runtime credential

A short-TTL system.credential of kind: api_key, scoped to the manifest’s declared permissions and bound to the connection_id. The runtime broker refreshes this transparently as the connector runs.
5

Creates the Connection

A system.connection item with kind: integration, integration_ref pointing at the manifest, credential_ref pointing at the persisted credential, and the manifest-derived configuration, direction, triggers, and runtime_compatibility fields.
6

Bootstraps OAuth (when applicable)

For connectors whose manifest declares oauth_requirements, the consent flow continues into an OAuth Authorization Code dance against the upstream service. Tokens land in encrypted storage; the proxy reads them at request time.
The result: a Connection ready to receive triggers and emit system.activity rows.

OAuth proxy and leased tokens

Connectors don’t hold OAuth tokens directly. The platform offers two patterns, declared per-capability in oauth_requirements:
  • proxy — the connector calls a Marfa endpoint with its runtime credential; Marfa injects the upstream OAuth bearer and forwards the request. Tokens never leave the platform.
  • leased — the connector requests a short-TTL bearer for direct calls (used when the proxy can’t carry the request shape — multipart uploads, streaming, non-HTTP protocols).
Both routes audit every call. Refresh-on-401 with refresh-token rotation is handled centrally; replay detection trips the Connection into runtime_status: reauth_required and surfaces an action_required activity row.

Token-authenticated connectors

Not every upstream uses OAuth. A connector targeting an API authenticated by a long-lived bearer token declares token_requirements in its manifest; the install pipeline gates on a kind: api_token credential supplied as credential_ref. The credential carries the encrypted bearer, the upstream base URL, and the auth_scheme to stamp on the request header (Bearer, Token, or Basic — the wire format some services demand instead of Bearer). The proxy stamps the configured auth_scheme on the upstream call uniformly with the OAuth path. From the connector’s perspective the call shape is identical — it issues a request against the runtime credential and the platform substitutes the upstream auth. The substantive difference is the absence of a refresh dance: static tokens don’t expire and have no refresh primitive. A 401 from the upstream surfaces as action_required and trips the Connection into runtime_status: reauth_required — the user updates the token to recover. A single kind: api_token credential can be shared across Connections that target the same upstream base. When a connector’s upstream lives on a different host than the credential’s default, the Connection sets properties.configuration.upstream_base_url_override and the proxy consults it before falling back to the credential’s URL. This lets a family of related connectors share one credential row across distinct upstream hosts.

Reactive runs and cycle detection

Connectors that declare triggers: item-event subscribe to changes on target_types. The platform routes events through a per-Connection queue with backpressure; cycle metadata (originating connection, hop count) flows on every event so a connector that writes back into Marfa cannot infinite-loop. Per-space hop budgets are configurable; the default ceiling of five steps protects against runaway chains. Self-events are filtered upstream — a Connection never receives events its own writes produced.

Inbound webhooks

When a connector declares triggers: webhook, the platform exposes a public receipt endpoint per subscription, verifies the signature using the adapter named in webhook_verification.method, deduplicates on the sender’s delivery id, and enqueues for the per-Integration Worker. See Inbound webhooks for the full surface — the four verification adapters, dedup semantics, the per-Connection DLQ, and the manual replay endpoint.

Versioning and upgrades

Manifests are versioned by semver. A patch or minor bump produces a new sibling system.integration item; existing Connections stay pinned to the version they installed. Major bumps are effectively new Integrations — explicit upgrade required. The runtime credential’s permissions are stamped from the manifest at install time. A consenting reader will see exactly what each version requires.

A minimal example — the RSS Watcher

The simplest connector possible is a schedule-driven reader against a public source. withmarfa.rss-watcher ships in-tree and polls an Atom or RSS feed on a cron, creating a core.bookmark per new entry. No OAuth, no token, no webhook — just the manifest declaring its shape and a handler that runs once per tick.
export const RSS_WATCHER_MANIFEST: IntegrationManifest = {
  name: "withmarfa.rss-watcher",
  version: "0.1.0",
  manifest_schema_version: "1.0.0",
  publisher: "withmarfa",
  description:
    "Polls an Atom or RSS feed on a schedule and creates a core.bookmark per new entry.",
  direction: "read",
  runtime_compatibility: ["hosted", "local"],
  target_types: ["core.bookmark"],
  triggers: [{ type: "schedule", config: { cron: "0 * * * *" } }],
  bidirectional_handling: {
    echo_ttl_seconds: 60,
    lag_window_seconds: 60,
    tombstone_mapping: "ignore",
    partial_write_mode: "accept-partial",
  },
  oauth_requirements: {},
  webhook_verification: { method: "hmac-sha256" },
  permissions: {
    extension: { "connection.runtime": "write" },
    edge: {},
  },
};
Three details worth calling out:
  • oauth_requirements: {} — the connector authenticates against the upstream by virtue of the feed being public. No OAuth provider, no token credential required at install. token_requirements is similarly omitted.
  • webhook_verification — required by the schema for shape-uniformity, but the value is informational for a connector that doesn’t carry a webhook trigger.
  • bidirectional_handling — required for every connector, even read-only ones. The defaults absorb the read-only case; tombstone mapping is ignore because there’s nothing to retract.
The handler is a single registerScheduleHandler call against @withmarfa/runtime-sdk, fetching the feed, diffing against the connection’s cursor, and emitting CreateItemInput per new entry. The same dist/local.js entry runs on both runtime substrates; the connector author writes the handler once. Use this shape as a starting point — every other in-tree connector layers more onto the same skeleton (OAuth proxy for the Google family, token credentials for Todoist / Readwise / Raindrop, an item-event trigger for bidirectional sync, a webhook trigger for push-driven sources).