SDKs
Go
matter-go — context-first Go SDK. Defined ID types, errors.As-friendly error tree, idiomatic option functions. Targets Go 1.22+.
Last updated
matter-go Beta
Context-first Go SDK. context.Context on every call, defined ID types, idiomatic
option functions, error wrapping that plays well with errors.As. Targets Go 1.22+.
TL;DR. go get github.com/matterhq/matter-go, construct with
matter.NewClient(matter.WithAPIKey(...), matter.WithVersion(...)), call
client.Entities.Create(ctx, &matter.EntityCreateParams{...}). Mutations return a
pending struct; terminal outcomes arrive via webhook. Idempotency keys auto-generate.
Errors are typed and wrap-friendly.
Installation
go get github.com/matterhq/matter-go| Requirement | Version |
|---|---|
| Go | >= 1.22 |
net/http | standard library — no extra transport deps |
| Modules | required (go.mod) |
The package ships with full Go types generated from the OpenAPI spec. Every resource is
a struct with explicit fields; every union is modelled as a discriminated struct with a
Type field; every enum is a defined string type.
Authentication
The client requires an API key and a version, set via option functions. Both are required.
The constructor returns an error if WithVersion is omitted — there is no implicit "latest."
package main
import (
"log"
"os"
"github.com/matterhq/matter-go"
)
func main() {
client, err := matter.NewClient(
matter.WithAPIKey(os.Getenv("MATTER_API_KEY")),
matter.WithVersion("2026-05-01"),
)
if err != nil {
log.Fatal(err)
}
_ = client
}For agent runtimes, pass a tok_ instead:
agent, err := matter.NewClient(
matter.WithAPIKey("tok_4Kj2m8pQNq3KcAbc..."),
matter.WithVersion("2026-05-01"),
)Available options:
| Option | Default | Description |
|---|---|---|
WithAPIKey(string) | — | sk_live_, sk_test_, pk_live_, pk_test_, or tok_. |
WithVersion(string) | — | Dated version, e.g. "2026-05-01". Required. |
WithBaseURL(string) | https://api.mattermode.com/v1 | Override for staging or self-hosted. |
WithTimeout(time.Duration) | 30 * time.Second | Per-request before the underlying http.Client aborts. |
WithMaxRetries(int) | 4 | 429 and 5xx only. |
WithHTTPClient(*http.Client) | &http.Client{} | Inject a custom http.Client (for tracing, mTLS, etc.). |
WithLogHook(func(*http.Request, *http.Response, time.Duration)) | nil | Opt-in per-request log. |
First request
Form a Delaware C-Corp. The pending Entity arrives in the response; the terminal
entity.active event arrives via webhook minutes later.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/matterhq/matter-go"
)
func main() {
client, err := matter.NewClient(
matter.WithAPIKey(os.Getenv("MATTER_API_KEY")),
matter.WithVersion("2026-05-01"),
)
if err != nil {
log.Fatal(err)
}
entity, err := client.Entities.Create(context.Background(), &matter.EntityCreateParams{
Jurisdiction: "US-DE",
LegalName: "Waypoint Systems, Inc.",
Founders: []matter.FounderParams{
{Name: "Ada Lovelace", Email: "ada@waypoint.dev", EquityPct: 60},
{Name: "Grace Hopper", Email: "grace@waypoint.dev", EquityPct: 40},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(entity.ID) // ent_Nq3KcAbc — typed as matter.EntityID
fmt.Println(entity.Status) // matter.EntityStatusDraft
fmt.Println(entity.LiveMode) // false
}entity.ID is matter.EntityID — a defined string type. Passing a matter.DocumentID
where an EntityID is expected is a compile-time error.
Idempotency
Every POST is idempotent on the server side for 24 hours. The SDK auto-generates a UUID
v4 per call. Override with IdempotencyKey on the params struct when retrying across
process restarts:
grant, err := client.Grants.Create(ctx, &matter.GrantCreateParams{
Entity: "ent_Nq3KcAbc",
Stakeholder: "stk_4Kj2m8pQ",
ShareClass: "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
WithMaxRetries) reuse the same key. - Token-scoped: keys collide only within the credential that issued them.
Pagination
List endpoints return an iterator that walks every page transparently. Go 1.23+ supports range-over-func, which the SDK targets:
for entity, err := range client.Entities.List(ctx, &matter.EntityListParams{}) {
if err != nil {
log.Fatal(err)
}
if entity.Status == matter.EntityStatusActive {
fmt.Println(entity.LegalName)
}
}For backpressure or large lists, walk pages manually:
var cursor string
for {
page, err := client.Entities.ListPage(ctx, &matter.EntityListParams{
Limit: 100,
StartingAfter: cursor,
})
if err != nil {
return err
}
for _, e := range page.Data {
handle(e)
}
if !page.HasMore {
break
}
cursor = page.NextCursor
}The page envelope mirrors the API: Page[Entity]{Object: "list", Data, HasMore, NextCursor}.
Errors
Every non-2xx becomes a typed error. The concrete type corresponds to the RFC 7807 type
URI — https://mattermode.com/errors/invalid_request becomes *matter.ValidationError.
import (
"errors"
"github.com/matterhq/matter-go"
)
_, err := client.Entities.Create(ctx, &matter.EntityCreateParams{Jurisdiction: "US-XX"})
if err != nil {
var verr *matter.ValidationError
var rerr *matter.RateLimitError
var merr *matter.Error // base type
switch {
case errors.As(err, &verr):
// 400 — fix the request, do not retry
fmt.Println(verr.Errors) // field-level details
case errors.As(err, &rerr):
// 429 — SDK already retried up to MaxRetries; surface to caller
fmt.Printf("retry after %s\n", rerr.RetryAfter)
case errors.As(err, &merr):
fmt.Println(merr.Code, merr.RequestID, merr.Type)
default:
// network error, context cancellation, etc.
return err
}
}| Type | HTTP | RFC 7807 code |
|---|---|---|
*matter.ValidationError | 400 | invalid_request |
*matter.AuthenticationError | 401 | authentication_required |
*matter.AuthorizationError | 403 | authorization_required |
*matter.NotFoundError | 404 | resource_not_found |
*matter.StateConflictError | 409 | invalid_state_transition |
*matter.IdempotencyReplayError | 409 | idempotency_key_replay |
*matter.RateLimitError | 429 | rate_limit_exceeded |
*matter.UpstreamUnavailableError | 503 | upstream_unavailable |
All concrete errors embed *matter.Error and answer to errors.As(err, &merr). Full
taxonomy: error handling.
Streaming
The events firehose is exposed as a channel-backed stream wrapping the SSE endpoint
GET /v1/events/stream. The SDK handles reconnection, last-event-id replay, and
heartbeat ticks transparently. Pass a context.Context to cancel.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stream, err := client.Events.Stream(ctx, &matter.EventStreamParams{
Entities: []matter.EntityID{"ent_Nq3KcAbc"},
Types: []string{"entity.state_changed", "filing.completed"},
})
if err != nil {
return err
}
defer stream.Close()
for ev := range stream.C() {
fmt.Println(ev.Type, ev.Sequence, ev.Data.ID)
}
if err := stream.Err(); err != nil {
return err
}Strict per-entity ordering is preserved across reconnects via Last-Event-Id.
Cancelling the context closes the stream cleanly; stream.Err() returns nil on a
context-driven shutdown.
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).
package main
import (
"io"
"net/http"
"os"
"github.com/matterhq/matter-go"
"github.com/matterhq/matter-go/webhook"
)
func main() {
secret := os.Getenv("MATTER_WEBHOOK_SECRET")
http.HandleFunc("/webhooks/matter", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "bad body", http.StatusBadRequest)
return
}
event, err := webhook.Verify(
body, // []byte — raw body
r.Header.Get("Matter-Signature"),
secret,
)
if err != nil {
http.Error(w, "bad signature", http.StatusUnauthorized)
return
}
handle(event)
w.WriteHeader(http.StatusNoContent)
})
http.ListenAndServe(":8080", nil)
}
func handle(ev *matter.Event) { /* ... */ }webhook.Verify returns a typed *matter.Event on success and a typed
*matter.SignatureError 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.
token, err := client.Tokens.Create(ctx, &matter.TokenCreateParams{
Tier: 3,
Scopes: []matter.ScopePolicy{{
Allow: []string{"entities.read", "filings.create", "documents.write"},
}},
Principal: matter.Principal{
HumanID: "usr_4Kj2m8pQ",
AgentID: "agt_Nq3KcAbc",
},
APIVersion: "2026-05-01",
})
if err != nil {
return err
}
fmt.Println(token.ID) // tok_…
fmt.Println(token.Secret) // shown once — store securelyWhen a tier-3 agent attempts a write, the SDK surfaces a
*matter.AuthorizationPendingError carrying the Authorization resource ID:
_, err := agent.Filings.Create(ctx, &matter.FilingCreateParams{
Entity: "ent_Nq3KcAbc",
Form: "DE-AnnualReport",
})
var pending *matter.AuthorizationPendingError
if errors.As(err, &pending) {
fmt.Println("awaiting human signature:", pending.AuthorizationID)
}The dashboard or client.Authorizations.Approve(ctx, authID) releases the action. See
agents for the full pattern.
Logging and observability
Opt in to a per-request log hook:
client, err := matter.NewClient(
matter.WithAPIKey("..."),
matter.WithVersion("2026-05-01"),
matter.WithLogHook(func(req *http.Request, res *http.Response, dur time.Duration) {
log.Printf("%s %s -> %d (req_id=%s, %s)",
req.Method,
req.URL.Path,
res.StatusCode,
res.Header.Get("X-Request-Id"),
dur,
)
}),
)The hook fires post-response (including failures). It does not see request bodies. For
OpenTelemetry, inject a traced *http.Client via WithHTTPClient.
Resource catalog
Every API resource maps to an SDK service struct. Cross-link to the API reference for fields and constraints.
| SDK service | API resource | ID prefix |
|---|---|---|
client.Entities | Entity | ent_ |
client.Intents | Intent | int_ |
client.IncorporatorReceipts | IncorporatorReceipt | rcp_ |
client.Stakeholders | Stakeholder | stk_ |
client.EquityPlans | EquityPlan | plan_ |
client.ShareClasses | ShareClass | cls_ |
client.ShareLedger | ShareLedgerEntry | led_ |
client.Grants | Grant | grt_ |
client.Valuations | Valuation | val_ |
client.Documents | Document | doc_ |
client.Resolutions | Resolution | res_ |
client.Filings | Filing | flg_ |
client.Qualifications | Qualification | qal_ |
client.RegisteredAgents | RegisteredAgent | ra_ |
client.TaxProfiles | TaxProfile | tax_ |
client.CorporateTransactions | CorporateTransaction | ctx_ |
client.Tokens | Token | tok_ |
client.Authorizations | Authorization | auth_ |
client.AuditEntries | AuditEntry | aud_ |
client.Events | Event | evt_ |
client.WebhookEndpoints | WebhookEndpoint | whe_ |
client.Portfolios | Portfolio | pf_ |
client.Requests | Request | req_ |
client.CapTable | CapTable view | (computed) |
client.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 (v0.7.x) and review the diff on each bump.
Anticipated breaking changes for 1.0:
- Drop the
ListPagefamily in favor of range-over-func only (Go 1.23+ becomes the minimum supported version). - Promote
webhookpackage types to the rootmatterpackage. - Tighten
IDsto defined types across the board — there are still a handful ofstringfields on params structs that will becomeEntityID,TokenID, etc.
What's next
Error handling
Typed error tree, retry policy by class, side-by-side with Node and Python.
Webhooks
net/http, Echo, and Gin receiver patterns for verified delivery.
Pagination
Range-over-func iterators, manual cursor walks, context cancellation.
Agents
Tier-3 / tier-4 token creation and the Authorization pause pattern.