> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Midaz SDK for Go

> Build Go applications on top of Midaz with the official v4 SDK: typed pagination, structured errors, and OpenTelemetry observability out of the box.

The **Midaz SDK for Go** is the idiomatic v4 client for the Midaz financial-ledger APIs. It gives you typed access to every service (Organizations, Ledgers, Accounts, Transactions, and more) with a single surface for authentication, pagination, errors, logging, and observability.

<Warning>
  **Coming from v2?** v4 is a clean major version with breaking changes across authentication, pagination, errors, and service access. There is no deprecation window. You swap your import from `/v2` to `/v4` and migrate at the same time.

  See [Migrating from v2](#migrating-from-v2) before you upgrade.
</Warning>

## Getting started

***

### Step 1 – Install Go

Before using the SDK, you **must** install Go on your machine. v4 declares **Go 1.26** in `go.mod`. The public API also uses `iter.Seq2` and `log/slog`.

<Steps>
  <Step>
    Go to the [official Go website](https://golang.org/dl/).
  </Step>

  <Step>
    Download the installer for your OS (Windows, macOS, or Linux).
  </Step>

  <Step>
    [**Follow the installation instructions.**](https://go.dev/doc/install)
  </Step>
</Steps>

### Step 2 – Create or use an existing Go project

**Create a Go project:**

To create a Go project, use the following command:

<CodeGroup>
  ```bash Bash theme={null}
  mkdir my-midaz-app
  cd my-midaz-app
  go mod init my-midaz-app
  ```
</CodeGroup>

**Use an existing Go project:**

If you're working in an existing project, make sure there's a `go.mod` file in the root. If not, run the following command to create one:

<CodeGroup>
  ```bash Bash theme={null}
  go mod init your-module-name
  ```
</CodeGroup>

### Step 3 – Add the Midaz SDK

Inside your project directory, run the following command to pull the v4 SDK and add it to your `go.mod` and `go.sum` files:

<Warning>
  **The module path requires the `/v4` suffix.** If you omit it, Go will resolve a stale pre-v4 release that is missing every change shipped in this version. Always import `github.com/LerianStudio/midaz-sdk-golang/v4`.
</Warning>

<CodeGroup>
  ```bash Bash theme={null}
  go get github.com/LerianStudio/midaz-sdk-golang/v4
  ```
</CodeGroup>

<Tip>
  In VS Code or GoLand, your IDE may automatically run `go get` when you import a new package.
</Tip>

### Step 4 – Import the SDK

Create or open a `main.go` file and add the following content. The example below builds a client against your local Midaz stack with anonymous authentication, lists organizations, then creates a new one.

<Note>
  The example below targets a local Midaz stack with auth disabled. If you don't have one running yet, see [Getting started with Midaz](/en/start-here/getting-started) to spin one up before running the snippet.
</Note>

<CodeGroup>
  ```go Go expandable theme={null}
  package main

  import (
  	"context"
  	"fmt"
  	"log"

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

  func main() {
  	// Build a client. v4 requires exactly one auth source — use
  	// midaz.WithAnonymous() for a local stack, or midaz.WithAccessManager(...)
  	// for a development or production environment.
  	c, err := midaz.New(
  		midaz.WithEnvironment(midaz.EnvironmentLocal),
  		midaz.WithAnonymous(),
  	)
  	if err != nil {
  		log.Fatalf("midaz.New: %v", err)
  	}
  	defer c.Shutdown(context.Background())

  	ctx := context.Background()

  	// List the first 5 organizations using a typed list-opts struct.
  	page, err := c.Organizations.ListOrganizations(ctx, 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)
  	}

  	// Create a new organization. Notice that midaz.CreateOrganizationInput is
  	// the same type as models.CreateOrganizationInput — re-exported on the
  	// midaz package so most user code only needs one import.
  	dba := "Example Inc."
  	org, err := c.Organizations.CreateOrganization(ctx, &midaz.CreateOrganizationInput{
  		LegalName:       "Example Corporation",
  		LegalDocument:   "123456789",
  		DoingBusinessAs: &dba, // optional fields are *string — use &local for short literals
  		Address: midaz.Address{
  			Line1:   "123 Main St",
  			City:    "New York",
  			State:   "NY",
  			ZipCode: "10001",
  			Country: "US",
  		},
  	})
  	if err != nil {
  		log.Fatalf("CreateOrganization: %v", err)
  	}

  	fmt.Printf("Organization created: %s\n", org.ID)
  }
  ```
</CodeGroup>

This gives you access to:

* The Midaz client for calling every API service.
* Built-in data models (like `CreateOrganizationInput`).
* Auth via **Access Manager** (production) or **Anonymous** (local development).
* A typed configuration system that fails fast at construction time.

<Tip>
  For the full setup, see the [Authentication](#authentication) section.
</Tip>

### Step 5 – Run the project

Run the following command:

<CodeGroup>
  ```bash Bash theme={null}
  go run main.go
  ```
</CodeGroup>

## SDK architecture

***

Every service is reachable directly on the client. Every list method follows the same trio shape. Every error is structured. Every option fails fast at construction time.

#### Layered design

| Layer                | What it handles                                                                                |
| :------------------- | :--------------------------------------------------------------------------------------------- |
| **Client**           | The main entry point — `midaz.New(...)` wires authentication, retries, and observability.      |
| **Services**         | High-level access to each Midaz domain, exposed as promoted fields on the client.              |
| **Models**           | The core data structures that mirror Midaz's domain logic, re-exported on the `midaz` package. |
| **Utility packages** | Modular helpers for config, errors, observability, retries, idempotency, and more.             |

<Note>
  Services are reached directly on the client: `c.Accounts`, `c.Transactions`, `c.Organizations`. You may still see an embedded `Entity` field in autocomplete, but new code should use the direct service fields shown in this guide.
</Note>

<Tip>
  For the design behind the rewrite, see the [architecture guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/architecture.md).
</Tip>

### Services

The `Services` layer is your access point to every Midaz domain. Each service handles one resource family and ships every method as part of its single interface. There is no `UseAllAPIs()` toggle and no service registration step. Every service is ready to use the moment `midaz.New()` returns.

#### Available services

| Service               | What it does                                           |
| :-------------------- | :----------------------------------------------------- |
| `c.Organizations`     | Manage organizations.                                  |
| `c.Ledgers`           | Create and retrieve ledgers.                           |
| `c.Assets`            | Define and manage assets.                              |
| `c.AssetRates`        | Set up and fetch asset exchange rates.                 |
| `c.Accounts`          | Manage accounts and check balances.                    |
| `c.AccountTypes`      | Manage account type definitions.                       |
| `c.Portfolios`        | Group accounts under portfolios.                       |
| `c.Segments`          | Categorize accounts using segments.                    |
| `c.Transactions`      | Create and search financial transactions.              |
| `c.TransactionRoutes` | Define and manage transaction routing rules.           |
| `c.Operations`        | Drill into the atomic operations inside a transaction. |
| `c.OperationRoutes`   | Define and manage operation routing rules.             |
| `c.Balances`          | Get real-time account balances.                        |
| `c.Holders`           | Manage CRM account holders.                            |
| `c.Aliases`           | Manage CRM aliases for accounts and entities.          |
| `c.MetadataIndexes`   | Manage searchable metadata indexes.                    |

### Models

Each model type is tied closely to a real-world business concept. You'll use them across every service call, from onboarding accounts to recording multi-leg transactions.

In v4, the most common model types are re-exported on the `midaz` package itself. That means `midaz.Account` and `models.Account` are the same type, and most code only needs one import.

#### Common model types

| Model                | What it represents                                                   |
| :------------------- | :------------------------------------------------------------------- |
| `midaz.Organization` | A business entity that owns ledgers and accounts.                    |
| `midaz.Ledger`       | A collection of accounts and transactions.                           |
| `midaz.Asset`        | A unit of value (currency, token, etc.) that can be stored or moved. |
| `midaz.Account`      | An account for tracking assets and balances.                         |
| `midaz.Portfolio`    | A collection of accounts for grouping and management.                |
| `midaz.Segment`      | A categorization unit for granular organization.                     |
| `midaz.Transaction`  | A financial event composed of multiple operations.                   |
| `midaz.Operation`    | An individual debit or credit entry within a transaction.            |
| `midaz.Balance`      | The current state of an account's holdings.                          |

<Tip>
  For a builder, an internal request shape, or a deprecated type, import `github.com/LerianStudio/midaz-sdk-golang/v4/models` directly. Every type lives there, and the `midaz` aliases preserve type identity, so the two import paths interoperate cleanly.
</Tip>

### Utility packages

Inside the `pkg` folder of the SDK, you'll find utility packages for config handling, retry policies, and security primitives. They split into two groups. **Core** packages serve cross-cutting SDK concerns. **Helper** packages provide domain-specific or low-level utilities.

#### Core packages

| Package         | What it solves                                                                      |
| :-------------- | :---------------------------------------------------------------------------------- |
| `auth`          | Access Manager OAuth and token lifecycle. Replaces the v2 `access-manager` package. |
| `config`        | Centralized config handling, env overrides, and custom service URLs.                |
| `concurrent`    | Tools for batching, rate-limiting, and worker pools.                                |
| `errors`        | Structured error types, classifiers, and the canonical `Retryable()` predicate.     |
| `observability` | Tracing, metrics, and logs through one OpenTelemetry provider.                      |
| `retry`         | Retry policy options with exponential backoff and jitter.                           |
| `sdkctx`        | Per-request context flags — idempotency keys, soft vs hard delete, include-deleted. |
| `validation`    | Input validation with clear, structured error messages.                             |

#### Helper packages

| Package       | What it solves                                                    |
| :------------ | :---------------------------------------------------------------- |
| `accounts`    | Account-specific helpers and convenience functions.               |
| `conversion`  | Type conversion helpers between models and external formats.      |
| `data`        | Data utilities and faker helpers for testing and demos.           |
| `format`      | Utilities for formatting data the Midaz way (dates, times, etc.). |
| `generator`   | Demo and mass-data generation for end-to-end scenarios.           |
| `integrity`   | Checksum and integrity verification utilities.                    |
| `performance` | Helpers for tuning bulk operations and high-throughput tasks.     |
| `security`    | Security utilities, including SSRF protection and TLS validation. |
| `stats`       | Processing statistics and metrics aggregation.                    |
| `transaction` | Transaction-building helpers and fluent builders.                 |
| `utils`       | General-purpose helpers used across the SDK.                      |
| `version`     | SDK version metadata and identification.                          |

<Note>
  The v2 `pkg/access-manager` package has moved to `pkg/auth` (so the directory matches the package name). The v2 `pkg/pagination` package was removed. Its surface lives in `models` and on each service today.
</Note>

## Authentication

***

In v4, the SDK requires exactly one authentication source at construction time. Calling `midaz.New(...)` with neither returns a typed configuration error. There are no more silent 401 cascades on the first API call.

You have two choices:

* **`midaz.WithAccessManager(...)`**: production-shape OAuth via the Lerian Access Manager. Recommended for any non-local stack.
* **`midaz.WithAnonymous()`**: opt out of authentication entirely. Suitable only for a local Midaz stack with auth disabled.

The two options are mutually exclusive.

### Production: Access Manager

Plug your Access Manager credentials into `midaz.WithAccessManager`. The SDK eagerly fetches an initial token at construction time, so misconfigurations surface as configuration errors instead of cascading 401s.

<Warning>
  Replace the values in the `// Configure Access Manager` block with your own credentials before running.
</Warning>

<CodeGroup>
  ```go Go expandable theme={null}
  package main

  import (
  	"context"
  	"log"
  	"os"

  	"github.com/LerianStudio/midaz-sdk-golang/v4"
  )

  func main() {
  	// Configure Access Manager. ClientID and ClientSecret are typically
  	// loaded from environment variables — never hardcoded.
  	c, err := midaz.New(
  		midaz.WithEnvironment(midaz.EnvironmentProduction),
  		midaz.WithAccessManager(midaz.AccessManager{
  			Address:      "https://auth.midaz.io",
  			ClientID:     os.Getenv("MIDAZ_CLIENT_ID"),
  			ClientSecret: os.Getenv("MIDAZ_CLIENT_SECRET"),
  		}),
  	)
  	if err != nil {
  		log.Fatalf("midaz.New: %v", err)
  	}
  	defer c.Shutdown(context.Background())

  	// Use c.Organizations, c.Ledgers, c.Accounts, ... as usual.
  }
  ```
</CodeGroup>

The SDK requests a token from your Access Manager, attaches it to every API call, and refreshes it automatically when it expires.

### Local development: Anonymous

For a local Midaz stack with auth disabled, opt out explicitly:

<CodeGroup>
  ```go Go theme={null}
  c, err := midaz.New(
      midaz.WithEnvironment(midaz.EnvironmentLocal),
      midaz.WithAnonymous(),
  )
  ```
</CodeGroup>

### Configure via environment variables

You can also point the SDK at your Access Manager via environment variables. Export them in your shell or your process manager:

<CodeGroup>
  ```bash Bash theme={null}
  export PLUGIN_AUTH_ENABLED=true
  export PLUGIN_AUTH_ADDRESS=https://your-auth-service.com
  export MIDAZ_CLIENT_ID=your-client-id
  export MIDAZ_CLIENT_SECRET=your-client-secret
  ```
</CodeGroup>

<Note>
  `config.FromEnvironment()` reads the **process environment**, not a `.env` file. If you keep variables in a `.env` file during development, load them with a library like `godotenv` before calling `config.NewConfig(config.FromEnvironment())`.
</Note>

Then opt in to environment loading at config time:

<CodeGroup>
  ```go Go theme={null}
  import "github.com/LerianStudio/midaz-sdk-golang/v4/pkg/config"

  cfg, err := config.NewConfig(config.FromEnvironment())
  if err != nil {
      log.Fatalf("config: %v", err)
  }

  c, err := midaz.New(midaz.WithConfig(cfg))
  ```
</CodeGroup>

<Note>
  Environment loading is **explicit** in v4. `config.FromEnvironment()` must be in the option chain. The SDK no longer reads env vars implicitly during construction.
</Note>

<Tip>
  For the full auth walkthrough, see the [Authentication guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/auth.md) in the SDK repo.
</Tip>

## Multi-tenancy

***

**Tenant scope comes from the Access Manager / JWT claims** used to obtain the token. The SDK applies tenant identity automatically from those claims. The client side needs no extra configuration.

To run calls under a different tenant scope, use a separate set of Access Manager credentials, or build a second client with its own token context.

## Listing and iteration

***

Every list endpoint in v4 ships in three flavors. Pick the one that matches your use case. They are consistent across every service.

| Method         | Returns                              | Use when                                                    |
| :------------- | :----------------------------------- | :---------------------------------------------------------- |
| `ListXxx`      | `*models.ListResponse[T]` (one page) | You want exactly one page and decide when to advance.       |
| `ListXxxAll`   | `iter.Seq2[T, error]`                | You want every item; the SDK handles paging internally.     |
| `ListXxxPages` | `iter.Seq2[*ListResponse[T], error]` | You need page-level metadata for checkpointing or batching. |

### Typed list options

Each list method takes a typed opts struct that embeds one of two base structs depending on how the endpoint paginates:

| Pagination shape | Endpoints                                                                                                | Base struct                                                               |
| :--------------- | :------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ |
| **Page-based**   | Organizations, Ledgers, Assets, Portfolios, Segments, Accounts, AccountTypes, Balances, Holders, Aliases | `models.PageListOpts{Limit, Page, SortDirection, StartDate, EndDate}`     |
| **Cursor-based** | Transactions, Operations, OperationRoutes, TransactionRoutes, AssetRates                                 | `models.CursorListOpts{Limit, Cursor, SortDirection, StartDate, EndDate}` |

Each endpoint also exposes a typed `Filters` sub-struct with only the fields that endpoint actually honors. Setting a field on the wrong shape (for example, `Page` on a cursor endpoint) fails at compile time, not silently at runtime.

### Iterate every item with `ListAll`

The most idiomatic shape is a `range` loop over every item across every page. The SDK advances cursors and fetches pages internally.

<CodeGroup>
  ```go Go theme={null}
  import (
      "github.com/LerianStudio/midaz-sdk-golang/v4"
      "github.com/LerianStudio/midaz-sdk-golang/v4/models"
  )

  opts := models.AccountsListOpts{
      PageListOpts: models.PageListOpts{Limit: 100},
      Filters: models.AccountsFilters{
          Status:    "ACTIVE",
          AssetCode: "USD",
      },
  }

  for account, err := range c.Accounts.ListAccountsAll(ctx, orgID, ledgerID, opts) {
      if err != nil {
          return fmt.Errorf("list accounts: %w", err)
      }
      process(account)
  }
  ```
</CodeGroup>

### Iterate page envelopes with `ListPages`

When you need page-level metadata (for checkpointing, batching, or stopping mid-page), iterate over `ListXxxPages` instead. Every entry is a `*ListResponse[T]` with the full `Pagination` block attached.

<CodeGroup>
  ```go Go theme={null}
  opts := models.TransactionsListOpts{
      CursorListOpts: models.CursorListOpts{Limit: 50},
      Filters:        models.TransactionsFilters{Status: "APPROVED"},
  }

  for page, err := range c.Transactions.ListTransactionsPages(ctx, orgID, ledgerID, opts) {
      if err != nil {
          return fmt.Errorf("page iter: %w", err)
      }
      log.Printf("page=%d items=%d next_cursor=%q",
          page.Pagination.Page, len(page.Items), page.Pagination.NextCursor)

      for _, tx := range page.Items {
          process(tx)
      }

      if shouldStop(page) {
          break // The SDK aborts in-flight paging cleanly.
      }
  }
  ```
</CodeGroup>

See the [Pagination guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/pagination.md) in the SDK repo for `HasMore()` semantics, `NextCursor` handling, and the page-vs-cursor decision table.

## Error handling

***

Most errors returned by the SDK service layer are `*pkg/errors.Error` with structured fields:

| Field       | What it carries                                                                  |
| :---------- | :------------------------------------------------------------------------------- |
| `Category`  | The error class (validation, authentication, network, configuration, and so on). |
| `Code`      | A stable string code suitable for branching or telemetry.                        |
| `Operation` | The SDK operation that produced the error (e.g. `Accounts.GetAccount`).          |
| `Resource`  | The resource family the error refers to, when relevant.                          |

You can branch with typed predicates, walk fields with `errors.As`, or use the canonical `Retryable()` method to drive retry policy.

### Branch with typed predicates

The SDK ships with a full set of `Is*` predicates so you can match errors without poking at internals:

<CodeGroup>
  ```go Go expandable theme={null}
  import (
      "errors"
      "fmt"

      sdkerrors "github.com/LerianStudio/midaz-sdk-golang/v4/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):
          // Matches both 401 and 403 — re-authenticate or fix permissions.
          return fmt.Errorf("auth failure: %w", err)
      case sdkerrors.IsValidationError(err):
          return fmt.Errorf("invalid input: %w", err)
      case sdkerrors.IsConflictError(err):
          return fmt.Errorf("already exists: %w", err)
      case sdkerrors.IsRateLimitError(err):
          return fmt.Errorf("rate limited: %w", err)
      case sdkerrors.IsNetworkError(err):
          return fmt.Errorf("transient transport: %w", err)
      case sdkerrors.IsConfigurationError(err):
          return fmt.Errorf("setup mistake: %w", err)
      }

      // Walk the structured fields when you need them.
      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())
      }
  }
  ```
</CodeGroup>

### Drive retry decisions with `Retryable()`

The `Error.Retryable()` method is the canonical retry-policy source. Use it instead of building your own classification.

<CodeGroup>
  ```go Go theme={null}
  var sdkErr *sdkerrors.Error
  if errors.As(err, &sdkErr) && sdkErr.Retryable() {
      // Apply your retry logic — backoff, jitter, max attempts, etc.
  }
  ```
</CodeGroup>

### Wrap raw transport errors

If you're calling lower-level HTTP code outside the SDK and want the same structured error shape, use `ClassifyTransportError`:

<CodeGroup>
  ```go Go theme={null}
  import sdkerrors "github.com/LerianStudio/midaz-sdk-golang/v4/pkg/errors"

  resp, err := httpClient.Do(req)
  if err != nil {
      return sdkerrors.ClassifyTransportError("PaymentService.Charge", err)
  }
  ```
</CodeGroup>

### Local validation: FieldErrors

Local input validation surfaces `*pkg/validation.FieldErrors`, a structured collection of per-field complaints from the SDK before any HTTP call. Use `errors.As` to inspect them when validating user input or builders.

<CodeGroup>
  ```go Go theme={null}
  import (
      "errors"
      "fmt"

      "github.com/LerianStudio/midaz-sdk-golang/v4/pkg/validation"
  )

  var fieldErrs *validation.FieldErrors
  if errors.As(err, &fieldErrs) {
      for _, fe := range fieldErrs.Errs() {
          fmt.Printf("- %s: %s\n", fe.Field, fe.Message)
      }
  }
  ```
</CodeGroup>

<Tip>
  For the full category map, every code, and retry boundary semantics, see the [Error handling guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/errors.md) in the SDK repo.
</Tip>

## Logging

***

In v4, **`*slog.Logger` is the canonical logger surface**. Wire it via `midaz.WithLogger(...)`. The SDK is **silent by default**. It uses `slog.DiscardHandler` until you opt in.

You decide the handler, the level, and the destination.

<CodeGroup>
  ```go Go expandable theme={null}
  package main

  import (
      "context"
      "log"
      "log/slog"
      "os"

      "github.com/LerianStudio/midaz-sdk-golang/v4"
  )

  func main() {
      logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
          Level: slog.LevelInfo,
      }))

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

      // SDK retry diagnostics, slow-call warnings, and other internal
      // log lines now flow through your handler.
  }
  ```
</CodeGroup>

zap, zerolog, and charmbracelet/log all integrate as `slog.Handler` adapters. The SDK works with any backend that speaks slog.

<Tip>
  The [Logging guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/logging.md) has adapter recipes for every common logging library.
</Tip>

## Observability

***

OpenTelemetry is first-class in v4. One observability provider gives you spans, metrics, and OTel-correlated logs through a single wiring point.

Configure observability either by passing a fully-built provider, or by passing options that the SDK assembles for you.

### Wire a provider

<CodeGroup>
  ```go Go expandable theme={null}
  package main

  import (
      "context"
      "log"

      "github.com/LerianStudio/midaz-sdk-golang/v4"
      "github.com/LerianStudio/midaz-sdk-golang/v4/pkg/observability"
  )

  func main() {
      ctx := context.Background()

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

      c, err := midaz.New(
          midaz.WithEnvironment(midaz.EnvironmentProduction),
          midaz.WithAccessManager(midaz.AccessManager{ /* ... */ }),
          midaz.WithObservabilityProvider(provider),
      )
      if err != nil {
          log.Fatalf("midaz.New: %v", err)
      }
      defer c.Shutdown(ctx)
  }
  ```
</CodeGroup>

### Or pass options inline

<CodeGroup>
  ```go Go theme={null}
  import "github.com/LerianStudio/midaz-sdk-golang/v4/pkg/observability"

  c, err := midaz.New(
      midaz.WithEnvironment(midaz.EnvironmentProduction),
      midaz.WithAccessManager(am),
      midaz.WithObservabilityOptions(
          observability.WithServiceName("payments-api"),
          observability.WithComponentEnabled(true, true, true),
      ),
  )
  ```
</CodeGroup>

The SDK emits one HTTP span per outbound request with proper W3C `traceparent` propagation. Business log records carry safe IDs only, never payloads, names, addresses, or auth headers.

You can also wrap a block of business logic in a span via `Client.Trace`:

<CodeGroup>
  ```go Go theme={null}
  err = c.Trace("create-organization", func(ctx context.Context) error {
      _, err := c.Organizations.CreateOrganization(ctx, input)
      return err
  })
  ```
</CodeGroup>

<Warning>
  `WithObservabilityOptions` and `WithObservabilityProvider` use **replacement semantics**, not merge. Each call replaces any previously installed provider. To start from a baseline, include `observability.WithDevelopmentDefaults` or `observability.WithProductionDefaults` as the first option in the chain.
</Warning>

<Tip>
  See the [`10-observability-otel` example](https://github.com/LerianStudio/midaz-sdk-golang/tree/main/examples/10-observability-otel) in the SDK repo for span attributes, metric names, and exporter setup.
</Tip>

## Idempotency

***

Auto-idempotency is **on by default** in v4. The SDK emits an `X-Idempotency: <uuid>` header on every unsafe HTTP request (POST, PUT, PATCH, DELETE). Retries on transient failures then do not double-create resources.

You can override the auto-generated key per-call when you need a stable, caller-supplied key. This is typical for saga steps, outbox rows, or UI-driven submissions.

### Set a stable key per request

<CodeGroup>
  ```go Go theme={null}
  import "github.com/LerianStudio/midaz-sdk-golang/v4/pkg/sdkctx"

  ctx := sdkctx.WithIdempotencyKey(context.Background(), "tx-2026-05-06-001")

  tx, err := c.Transactions.CreateTransaction(ctx, orgID, ledgerID, input)
  ```
</CodeGroup>

### Suppress idempotency for one call

For rare fire-and-forget administrative endpoints, suppress the header per-request:

<CodeGroup>
  ```go Go theme={null}
  ctx := sdkctx.WithoutAutoIdempotency(context.Background())

  err := c.SomeAdminService.DoOneShotThing(ctx, input)
  ```
</CodeGroup>

### Disable globally

If you don't want auto-idempotency anywhere, turn it off at the client level:

<CodeGroup>
  ```go Go theme={null}
  c, err := midaz.New(
      midaz.WithEnvironment(midaz.EnvironmentLocal),
      midaz.WithAnonymous(),
      midaz.WithIdempotency(false),
  )
  ```
</CodeGroup>

You can also disable it via the `MIDAZ_IDEMPOTENCY=false` environment variable when using `config.FromEnvironment()`.

## Environment variables

***

You can configure the SDK with environment variables instead of hardcoded values. Environment loading is **explicit** in v4. Pass `config.FromEnvironment()` in your config option chain to opt in.

| Variable              | Description                                                                       |
| :-------------------- | :-------------------------------------------------------------------------------- |
| `MIDAZ_ENVIRONMENT`   | Target environment (`local`, `development`, `production`).                        |
| `MIDAZ_BASE_URL`      | Base URL for all Midaz services, used when the service-specific URLs are not set. |
| `MIDAZ_LEDGER_URL`    | Ledger API URL. It serves the onboarding and transaction endpoints.               |
| `MIDAZ_CRM_URL`       | CRM API URL. Holders and Aliases use it.                                          |
| `PLUGIN_AUTH_ENABLED` | Enable Access Manager authentication (`true` or `false`).                         |
| `PLUGIN_AUTH_ADDRESS` | Address of the Access Manager service.                                            |
| `MIDAZ_CLIENT_ID`     | Client ID for Access Manager authentication.                                      |
| `MIDAZ_CLIENT_SECRET` | Client secret for Access Manager authentication.                                  |
| `MIDAZ_TIMEOUT`       | HTTP request timeout in seconds.                                                  |
| `MIDAZ_DEBUG`         | Enable debug logs (`true` or `false`).                                            |
| `MIDAZ_MAX_RETRIES`   | Maximum retry attempts for failed requests. Set `0` to disable retries.           |
| `MIDAZ_IDEMPOTENCY`   | Enable automatic idempotency keys for unsafe requests (`true` or `false`).        |

For the full option matrix across `midaz`, `pkg/config`, and `pkg/sdkctx`, see the [Configuration guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/configuration.md) in the SDK repo.

## Example projects

***

<Tip>
  The SDK ships with a numbered tour of its core capabilities (examples 01–10) plus a set of advanced and reference examples. The numbered set is **focused tutorials**: each one teaches exactly one concept with the smallest possible body. Browse them in the [examples directory](https://github.com/LerianStudio/midaz-sdk-golang/tree/main/examples) on GitHub.
</Tip>

<Columns cols={2}>
  | Example                 | What it demonstrates                                           |
  | :---------------------- | :------------------------------------------------------------- |
  | `01-hello-world`        | Minimal init plus the first API call (\~17 body lines).        |
  | `02-auth`               | Access Manager authentication (production setup).              |
  | `03-end-to-end`         | Org → ledger → asset → account → transaction.                  |
  | `04-listing-cursor`     | Cursor-based pagination with `iter.Seq2`.                      |
  | `05-listing-pages`      | Page-based pagination — `List` / `ListAll` / `ListPages`.      |
  | `06-idempotency`        | Auto / explicit / suppressed idempotency modes.                |
  | `07-retries`            | Default policy, custom policy, disabled retries.               |
  | `08-logging-slog`       | `*slog.Logger` integration (v4 logging).                       |
  | `09-testing-with-mocks` | `go.uber.org/mock` for unit testing your code against the SDK. |
  | `10-observability-otel` | Full OpenTelemetry surface (tracing + metrics + logs).         |
</Columns>

The repository also includes specialized and reference examples: `concurrency`, `configuration`, `context`, `tracing`, `tracing-server`, `pkg-validation-demo`, `mass-demo-generator`, and `workflow-with-entities`. They cover advanced patterns (bounded parallelism, OTel context propagation across processes, mass data generation) for when you've outgrown the focused set.

## Migrating from v2

***

v4 is a **clean-cut major version with no deprecation window**. There is no transitional release, no `// Deprecated:` shim, no backward-compatible alias of the old surface. Swap your import from `/v2` to `/v4` and walk the breaking changes below.

The biggest moves to plan for:

* **Module path and package name**: `github.com/LerianStudio/midaz-sdk-golang/v4` (was `/v2`), package `midaz` (was `client`).
* **Authentication**: `WithAuthToken` is gone. Use `WithAccessManager` for production or `WithAnonymous` for local stacks. One auth source is required at construction time.
* **Service access**: `c.Accounts.X` (was `c.Entity.Accounts.X`). The `c.Entity` field still exists for compatibility, but every example uses the short form.
* **Pagination**: `models.ListOptions` and its 30 fluent setters are gone. Use the typed per-endpoint opts (`models.AccountsListOpts`, `models.TransactionsListOpts`, ...) and the new `ListAll` / `ListPages` iterators.
* **Errors**: `*MidazError` is gone. Service-layer errors now use `*pkg/errors.Error` with `Retryable()` as the official retry source. Local validation may surface `*pkg/validation.FieldErrors`.
* **Tenant identity**: `WithTenantID`, the `MIDAZ_TENANT_ID` env var, and the `X-Tenant-ID` header are gone. Tenant scope flows through Access Manager / JWT claims.
* **Logging**: `*slog.Logger` replaces the bespoke `observability.Logger` interface as the canonical surface. The SDK is silent by default.

For the authentication changes in detail, see the [Authentication guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/auth.md) in the SDK repo. The [examples directory](https://github.com/LerianStudio/midaz-sdk-golang/tree/main/examples) shows the v4 API in practice.

## Explore the APIs

***

For more information about the APIs, refer to the following links:

<Columns cols={2}>
  <Card title="External API mapping" icon="link" horizontal href="https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/mapping/external_apis.md" />

  <Card title="Internal API mapping" icon="link" horizontal href="https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/mapping/internal_apis.md" />

  <Card title="Godoc documentation" icon="books" horizontal href="https://pkg.go.dev/github.com/LerianStudio/midaz-sdk-golang/v4" />
</Columns>
