Skip to content

LerianStudio/midaz-sdk-golang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

439 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

banner

Latest Release Go Report Discord Go Version License

Midaz Go SDK

The Midaz Go SDK is the idiomatic v3 client for the Midaz financial-ledger APIs. v3 is a clean major version: typed list-opts, iter.Seq2-based pagination, structured errors with retry classification, *slog.Logger canonical logging, OpenTelemetry observability, and a single canonical auth surface (Access Manager OAuth or anonymous local-stack mode).

What's new in v3

v3 is the result of a 9-track DX overhaul. Highlights:

  • One auth source, enforced: WithAccessManager for production OAuth, WithAnonymous for local stacks. Calling New() with neither returns a typed configuration error at construction time.
  • Typed pagination opts at the type system: page-based and cursor-based endpoints have separate opts types. Wrong-shape opts don't compile.
  • iter.Seq2[T, error]: every list method ships in a trio — List (one page) / ListAll (every item) / ListPages (every page envelope).
  • Structured errors: every error is a *pkg/errors.Error with Category, Code, Operation, Resource, and a canonical Retryable() method. Real network/timeout/auth/validation classification.
  • Canonical logging: *slog.Logger is the canonical client/application logger surface. The SDK is silent by default (slog.DiscardHandler); opt in with WithLogger. A separate Provider.Logger (OTel-correlated) is also exposed by pkg/observability for span-aware logging from inside SDK callbacks.
  • OpenTelemetry first-class: spans + metrics + logs through one observability.Provider wired by WithObservabilityProvider.
  • Idempotency by default: auto-generated X-Idempotency per unsafe request. Override with sdkctx.WithIdempotencyKey for stable caller-supplied keys; suppress per-call with WithoutAutoIdempotency.
  • Mocks via go.uber.org/mock: pre-generated mocks for every service ship under entities/mocks/. Regenerate with go generate ./entities/....

Historical planning artifact — see docs/v3-dx-plan.md for the original design rationale (note: file:line refs may be stale).

Installation

go get github.com/LerianStudio/midaz-sdk-golang/v3

Requires Go 1.26+ — the toolchain pinned in go.mod. The SDK uses iter.Seq2 (Go 1.23+) and log/slog (Go 1.21+) in its public API; the 1.26 floor matches the rest of the Lerian Go stack.

Quick start

The minimum-viable shape — local stack, anonymous auth, list 5 organizations:

package main

import (
    "context"
    "fmt"
    "log"

    midaz "github.com/LerianStudio/midaz-sdk-golang/v3"
    "github.com/LerianStudio/midaz-sdk-golang/v3/models"
)

func main() {
    c, err := midaz.New(
        midaz.WithEnvironment(midaz.EnvironmentLocal),
        midaz.WithAnonymous(),
    )
    if err != nil {
        log.Fatalf("midaz.New: %v", err)
    }
    defer c.Shutdown(context.Background())

    page, err := c.Organizations.ListOrganizations(context.Background(),
        models.OrganizationsListOpts{
            PageListOpts: models.PageListOpts{Limit: 5},
        })
    if err != nil {
        log.Fatalf("ListOrganizations: %v", err)
    }
    for _, org := range page.Items {
        fmt.Printf("- %s (%s)\n", org.LegalName, org.ID)
    }
}

For Access Manager auth (production) and the full client-construction matrix see docs/auth.md and docs/configuration.md.

Core surfaces

Service access

Every public service is a promoted field on *midaz.Client. The canonical shape is c.<Service>.<Method>:

orgs, err := c.Organizations.ListOrganizations(ctx, opts)
ledger, err := c.Ledgers.CreateLedger(ctx, orgID, input)
account, err := c.Accounts.GetAccount(ctx, orgID, ledgerID, accountID)
balance, err := c.Balances.GetBalance(ctx, orgID, ledgerID, balanceID)
tx, err := c.Transactions.CreateTransaction(ctx, orgID, ledgerID, input)

The full service list: Organizations, Ledgers, Assets, AssetRates, Accounts, AccountTypes, Balances, Holders, MetadataIndexes, Operations, OperationRoutes, Portfolios, Segments, Transactions, TransactionRoutes.

Pagination

Every list method ships in three flavors:

// One page, you decide when to advance.
page, err := c.Accounts.ListAccounts(ctx, orgID, ledgerID, opts)

// iter.Seq2 over every item across every page (SDK handles paging).
for acc, err := range c.Accounts.ListAccountsAll(ctx, orgID, ledgerID, opts) {
    if err != nil { return err }
    process(acc)
}

// iter.Seq2 over page envelopes (with metadata for checkpointing).
for batch, err := range c.Accounts.ListAccountsPages(ctx, orgID, ledgerID, opts) {
    if err != nil { return err }
    log.Printf("page %d: %d items", batch.Pagination.Page, len(batch.Items))
}

Page-based and cursor-based endpoints use separate opts types. See examples/05-listing-pages/ and examples/04-listing-cursor/.

Idempotency

Auto-on by default. The SDK emits X-Idempotency: <uuid> on every unsafe request. Override per-call:

import "github.com/LerianStudio/midaz-sdk-golang/v3/pkg/sdkctx"

// Stable key for at-least-once producers (saga steps, outbox rows, UI submissions):
ctx := sdkctx.WithIdempotencyKey(ctx, "tx-2026-05-06-001")

// Suppress for one call (rare — fire-and-forget administrative endpoints):
ctx := sdkctx.WithoutAutoIdempotency(ctx)

Disable globally with midaz.WithIdempotency(false). See examples/06-idempotency/.

Errors

Every error is a *pkg/errors.Error. Use the typed predicates or errors.As for structured field access:

import sdkerrors "github.com/LerianStudio/midaz-sdk-golang/v3/pkg/errors"

acc, err := c.Accounts.GetAccount(ctx, orgID, ledgerID, accountID)
if err != nil {
    switch {
    case sdkerrors.IsNotFoundError(err):
        return fmt.Errorf("account not found: %w", err)
    case sdkerrors.IsAuthError(err):
        return fmt.Errorf("re-authenticate: %w", err)
    case sdkerrors.IsValidationError(err):
        return fmt.Errorf("input invalid: %w", err)
    case sdkerrors.IsNetworkError(err):
        return fmt.Errorf("transient transport: %w", err) // retry-safe
    }
}

Or walk fields:

var sdkErr *sdkerrors.Error
if errors.As(err, &sdkErr) {
    log.Printf("op=%s resource=%s code=%s retryable=%v",
        sdkErr.Operation, sdkErr.Resource, sdkErr.Code, sdkErr.Retryable())
}

Retryable() is the canonical retry-policy source — derived from Category. Use it instead of re-implementing a category switch in consumer code.

Logging

Inject a *slog.Logger:

import "log/slog"

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
c, err := midaz.New(
    midaz.WithEnvironment(midaz.EnvironmentLocal),
    midaz.WithAnonymous(),
    midaz.WithLogger(logger),
)

Adapters for zap, zerolog, logrus all go through slog.Handler. See docs/logging.md and examples/08-logging-slog/.

Retries

Default policy: 3 retries, exponential backoff with 25% jitter, retryable on transport errors + 5xx + 408 + 425 + 429. Customize:

Unsafe methods (POST, PUT, PATCH, DELETE) retry only when X-Idempotency is present. The SDK auto-generates this header by default; WithoutAutoIdempotency or WithIdempotency(false) disables automatic unsafe retries unless the caller supplies X-Idempotency explicitly.

import "github.com/LerianStudio/midaz-sdk-golang/v3/pkg/retry"

c, err := midaz.New(
    midaz.WithEnvironment(midaz.EnvironmentLocal),
    midaz.WithAnonymous(),
    midaz.WithRetryOptions(
        retry.WithMaxRetries(5),
        retry.WithInitialDelay(200*time.Millisecond),
    ),
)

Or WithCustomRetryPolicy(func(*Response, error) bool) for arbitrary predicates. Disable with WithoutRetries(). See examples/07-retries/.

Observability (OpenTelemetry)

import "github.com/LerianStudio/midaz-sdk-golang/v3/pkg/observability"

provider, err := observability.New(ctx,
    observability.WithServiceName("payments-api"),
    observability.WithEnvironment("production"),
    observability.WithComponentEnabled(true, true, true), // tracing, metrics, logs
)
if err != nil { return err }
defer provider.Shutdown(ctx)

c, err := midaz.New(
    midaz.WithEnvironment(midaz.EnvironmentProduction),
    midaz.WithAccessManager(am),
    midaz.WithObservabilityProvider(provider),
)

The SDK emits one HTTP span per outbound request with proper W3C traceparent propagation. Business logs carry safe IDs only — never payloads, names, addresses, or auth headers. See docs/tracing.md and examples/10-observability-otel/.

Multi-tenancy

c, err := midaz.New(
    midaz.WithEnvironment(midaz.EnvironmentProduction),
    midaz.WithAccessManager(am),
)

Tenant scope is derived from the Access Manager/JWT claims used to obtain the token. The SDK does not expose tenant configuration and does not send X-Tenant-ID; use separate Access Manager credentials/token context when tenant scope differs.

See docs/multi-tenancy.md.

Testing with mocks

Every service has a generated mock under entities/mocks/:

import (
    "github.com/LerianStudio/midaz-sdk-golang/v3/entities/mocks"
    "go.uber.org/mock/gomock"
)

func TestMyHandler(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockSvc := mocks.NewMockAccountsService(ctrl)
    mockSvc.EXPECT().
        GetAccount(gomock.Any(), "org-1", "ledger-1", "acc-1").
        Return(&models.Account{ID: "acc-1"}, nil)
    // ... use mockSvc as entities.AccountsService in your code under test
}

Mocks are regenerated via go generate ./entities/... (each service file has a //go:generate mockgen directive). See examples/09-testing-with-mocks/.

Environment variables

The SDK reads these only when config.FromEnvironment() is in the option chain — there is no implicit env-var loading:

  • Service URLs: MIDAZ_ENVIRONMENT, MIDAZ_BASE_URL, MIDAZ_ONBOARDING_URL, MIDAZ_TRANSACTION_URL, MIDAZ_CRM_URL
  • Auth: PLUGIN_AUTH_ENABLED, PLUGIN_AUTH_ADDRESS, MIDAZ_CLIENT_ID, MIDAZ_CLIENT_SECRET
  • Behavior: MIDAZ_TIMEOUT, MIDAZ_USER_AGENT, MIDAZ_DEBUG, MIDAZ_MAX_RETRIES, MIDAZ_IDEMPOTENCY, MIDAZ_ERROR_EXPOSE_BODY

See docs/configuration.md for the full matrix.

Documentation

Generate docs locally:

make docs       # static HTML to docs/godoc/
make godoc      # interactive server at http://localhost:6060

Examples

See examples/README.md for the full numbered list with a Start-Here / Common workflows / Behavior & resilience / Testing & observability / Reference structure. Highlights:

Run the demo data generator:

make demo-data
# or
DEMO_NON_INTERACTIVE=1 go run ./examples/mass-demo-generator --org-locale=br

Build all examples:

make examples-test

Testing

make test                    # unit tests
make coverage                # HTML coverage report under artifacts/
make verify-sdk              # API compatibility + parity checks
make examples-test           # build every example, run example tests
make ci                      # full local pipeline: tidy + fmt + lint + gosec + test + verify-sdk

Contributing

Contributions are welcome. See CONTRIBUTING.md for guidelines.

License

This project is licensed under the Apache License, Version 2.0. See LICENSE.md for details.

Copyright 2025 Lerian Studio

About

A Go client library for the Midaz financial ledger APIs. This SDK provides a flexible, and idiomatic way to interact with the Midaz platform, enabling developers to build robust financial applications with ease. Features include complete API coverage, observability integration, concurrency support, robust error handling, etc.

Resources

License

Contributing

Stars

Watchers

Forks

Contributors

Languages