SDKs
Node
@mattermode/node — TypeScript-first Matter SDK. Branded ID types, discriminated unions on expand, async iterators on streams and pagination, Edge-safe.
Last updated
@mattermode/node Beta
The TypeScript-first Matter SDK. Branded ID types, discriminated unions on expand,
async iterators on streams and pagination, Edge-safe transport. Targets Node 20+,
Bun, Deno, and Workers.
TL;DR. npm i @mattermode/node, construct with new MatterClient({ apiKey, version }),
call matter.entities.create({ ... }). Mutations return a typed pending resource; await
the terminal outcome via webhook. Idempotency keys auto-generate. Errors throw typed
exceptions extending MatterError.
Installation
npm i @mattermode/nodepnpm add @mattermode/nodebun add @mattermode/nodeyarn add @mattermode/node| Requirement | Version |
|---|---|
| Node | >= 20.0.0 |
| TypeScript | >= 5.0 (optional but recommended) |
| Module system | ESM and CJS — both shipped |
| Edge runtimes | Cloudflare Workers, Vercel Edge, Deno Deploy |
The package ships .d.ts declarations generated directly from the OpenAPI spec. There
is no separate @types/mattermode-node package — types are first-class.
Authentication
The client requires apiKey and version at construction. Both are required. The SDK
refuses to start without version — there is no implicit "latest."
import { MatterClient } from "@mattermode/node";
const matter = new MatterClient({
apiKey: process.env.MATTER_API_KEY!,
version: "2026-05-01",
});For agent runtimes, pass a tok_ instead:
const agent = new MatterClient({
apiKey: "tok_4Kj2m8pQNq3KcAbc...",
version: "2026-05-01",
});Optional construction flags:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | sk_live_, sk_test_, pk_live_, pk_test_, or tok_. |
version | string | — | Dated version, e.g. 2026-05-01. Required. |
baseUrl | string | https://api.mattermode.com/v1 | Override for staging or self-hosted. |
timeout | number | 30_000 | Per-request ms before aborting. |
maxRetries | number | 4 | 429 and 5xx only. |
fetch | typeof fetch | global fetch | Inject a custom fetch (Workers, undici, etc.). |
logHook | (req, res) => void | undefined | Opt-in request log. See logging. |
First request
Form a Delaware C-Corp. The pending entity arrives in the response; the terminal
entity.active event arrives via webhook minutes later.
import { MatterClient } from "@mattermode/node";
const matter = new MatterClient({
apiKey: process.env.MATTER_API_KEY!,
version: "2026-05-01",
});
const entity = await matter.entities.create({
jurisdiction: "US-DE",
legal_name: "Waypoint Systems, Inc.",
founders: [
{ name: "Ada Lovelace", email: "ada@waypoint.dev", equity_pct: 60 },
{ name: "Grace Hopper", email: "grace@waypoint.dev", equity_pct: 40 },
],
});
console.log(entity.id); // ent_Nq3KcAbc — branded as EntityId
console.log(entity.status); // "draft"
console.log(entity.livemode); // falseThe response type is Entity & { status: "draft" } — the SDK narrows the discriminated
union by HTTP status. For the M&A, dissolution, and other custom verbs, see
exit.
Idempotency
Every POST is idempotent on the server side for 24 hours. The SDK auto-generates a UUID
v4 per call. Override with idempotencyKey when retrying across process restarts:
const grant = await matter.grants.create(
{
entity: "ent_Nq3KcAbc",
stakeholder: "stk_4Kj2m8pQ",
share_class: "cls_Common",
quantity: 100_000,
},
{ idempotencyKey: "grant-2026-q2-ada-lovelace" },
);Behavior:
- Caller-provided keys are passed verbatim. Up to 255 chars.
- Auto-generated keys are UUID v4, not stored. If you need durable retries, supply your own.
- Retries inside a single call (across
maxRetries) reuse the same key. - Token-scoped: keys collide only within the credential that issued them.
Pagination
List endpoints return an async iterator that walks every page transparently:
for await (const entity of matter.entities.list()) {
if (entity.status === "active") {
console.log(entity.legal_name);
}
}For backpressure or large lists, walk pages manually:
let cursor: string | undefined;
do {
const page = await matter.entities.list({ limit: 100, startingAfter: cursor });
for (const e of page.data) handle(e);
cursor = page.next_cursor;
} while (cursor);The page envelope mirrors the API: { object: "list", data, has_more, url, next_cursor? }.
Errors
Every non-2xx becomes a typed exception. The class corresponds to the RFC 7807 type
URI — https://mattermode.com/errors/invalid_request becomes MatterValidationError.
import {
MatterError,
MatterValidationError,
MatterAuthorizationError,
MatterRateLimitError,
MatterIdempotencyReplayError,
MatterUpstreamUnavailableError,
} from "@mattermode/node";
try {
await matter.entities.create({ jurisdiction: "US-XX" });
} catch (err) {
if (err instanceof MatterValidationError) {
// 400 — fix the request, do not retry
console.error(err.errors); // field-level details
} else if (err instanceof MatterRateLimitError) {
// 429 — SDK already retried up to maxRetries; surface to caller
console.error(`retry after ${err.retryAfter}s`);
} else if (err instanceof MatterError) {
console.error(err.code, err.requestId, err.type);
}
}| Class | HTTP | RFC 7807 code |
|---|---|---|
MatterValidationError | 400 | invalid_request |
MatterAuthenticationError | 401 | authentication_required |
MatterAuthorizationError | 403 | authorization_required |
MatterNotFoundError | 404 | resource_not_found |
MatterStateConflictError | 409 | invalid_state_transition |
MatterIdempotencyReplayError | 409 | idempotency_key_replay |
MatterRateLimitError | 429 | rate_limit_exceeded |
MatterUpstreamUnavailableError | 503 | upstream_unavailable |
Full taxonomy: error handling.
Streaming
The events firehose is exposed as an async iterator wrapping the SSE endpoint
GET /v1/events/stream. The SDK handles reconnection, last-event-id replay, and
heartbeat ticks transparently.
const stream = matter.events.stream({
entities: ["ent_Nq3KcAbc"],
types: ["entity.state_changed", "filing.completed"],
});
for await (const event of stream) {
console.log(event.type, event.sequence, event.data.id);
}To cancel:
const ac = new AbortController();
const stream = matter.events.stream({ signal: ac.signal });
setTimeout(() => ac.abort(), 60_000);Strict per-entity ordering is preserved across reconnects via Last-Event-Id.
Webhooks
Verify signatures in your receiver. Use the raw request body — re-serialized JSON will not verify. The helper does a constant-time compare and validates the timestamp window (5 min default).
import express from "express";
import { MatterClient } from "@mattermode/node";
const matter = new MatterClient({ apiKey: "...", version: "2026-05-01" });
const app = express();
app.post(
"/webhooks/matter",
express.raw({ type: "application/json" }),
(req, res) => {
try {
const event = matter.webhooks.verifySignature(
req.body, // Buffer — raw body
req.header("Matter-Signature")!,
process.env.MATTER_WEBHOOK_SECRET!,
);
handle(event);
res.status(204).end();
} catch (err) {
res.status(401).end();
}
},
);verifySignature returns the typed Event on success and throws MatterSignatureError
on tampering, replay (>5 min skew), or secret mismatch.
Agent tokens
Create a tier-3 token. Tier-3 means the agent prepares mutations but a human signs the
Authorization resource before execution.
const token = await matter.tokens.create({
tier: 3,
scopes: [{ allow: ["entities.read", "filings.create", "documents.write"] }],
principal: { human_id: "usr_4Kj2m8pQ", agent_id: "agt_Nq3KcAbc" },
api_version: "2026-05-01",
});
console.log(token.id); // tok_…
console.log(token.secret); // shown once — store securelyWhen a tier-3 agent attempts a write, the SDK surfaces a MatterAuthorizationPendingError
carrying the Authorization resource ID:
try {
await agent.filings.create({ entity: "ent_Nq3KcAbc", form: "DE-AnnualReport" });
} catch (err) {
if (err instanceof MatterAuthorizationPendingError) {
console.log("awaiting human signature:", err.authorizationId);
}
}The dashboard or POST /v1/authorizations/{id}/approve releases the action. See
agents for the full pattern.
Logging and observability
Opt in to a per-request log hook:
const matter = new MatterClient({
apiKey: "...",
version: "2026-05-01",
logHook: (req, res) => {
console.log({
method: req.method,
path: req.path,
status: res.status,
requestId: res.headers.get("x-request-id"),
durationMs: res.durationMs,
});
},
});The hook fires post-response (including failures). It does not see request bodies
unless you opt in via logHook: { includeBody: true, ... } — bodies may contain PII.
Resource catalog
Every API resource maps to an SDK namespace. Cross-link to the API reference for fields and constraints.
| SDK class | API resource | ID prefix |
|---|---|---|
matter.entities | Entity | ent_ |
matter.intents | Intent | int_ |
matter.incorporatorReceipts | IncorporatorReceipt | rcp_ |
matter.stakeholders | Stakeholder | stk_ |
matter.equityPlans | EquityPlan | plan_ |
matter.shareClasses | ShareClass | cls_ |
matter.shareLedger | ShareLedgerEntry | led_ |
matter.grants | Grant | grt_ |
matter.valuations | Valuation | val_ |
matter.documents | Document | doc_ |
matter.resolutions | Resolution | res_ |
matter.filings | Filing | flg_ |
matter.qualifications | Qualification | qal_ |
matter.registeredAgents | RegisteredAgent | ra_ |
matter.taxProfiles | TaxProfile | tax_ |
matter.corporateTransactions | CorporateTransaction | ctx_ |
matter.tokens | Token | tok_ |
matter.authorizations | Authorization | auth_ |
matter.auditEntries | AuditEntry | aud_ |
matter.events | Event | evt_ |
matter.webhookEndpoints | WebhookEndpoint | whe_ |
matter.portfolios | Portfolio | pf_ |
matter.requests | Request | req_ |
matter.capTable | CapTable view | (computed) |
matter.compliance | Compliance view | (computed) |
Migration notes
The SDK is pre-1.0. Until 1.0, breaking changes ship in minor versions and are called out
in /changelog. Pin a minor (^0.7.0) and review the diff on each bump.
Anticipated breaking changes for 1.0:
- Rename
MatterClient→Matter(default export). Both names will be exported during the deprecation window. - Remove the synchronous
createSyncfamily — async-only. - Inline
expandparameter shape will switch from string array to object literal for full type narrowing. See expand.
What's next
Error handling
Typed exception hierarchy, retry policy by class, side-by-side with Python and Go.
Webhooks
Express, Fastify, and Next.js route-handler patterns for verified delivery.
Pagination
Async iterators vs manual cursor. Memory and backpressure considerations.
Expand
The Node SDK's signature feature — compile-time response narrowing on expand.