> ## 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 v3 SDK — typed pagination, structured errors, and OpenTelemetry observability out of the box.

The **Midaz SDK for Go** is the idiomatic v3 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.

Whether you're standing up your first ledger or running production payment flows at scale, the SDK keeps you focused on your business logic and out of the boilerplate.

<Warning>
  **Coming from v2?** v3 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 `/v3` and migrate at the same time.

  Read the [Migration guide from v2 to v3](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/migration-v2-to-v3.md) before upgrading.
</Warning>

## Getting started

***

### Step 1 – Install Go

Before using the SDK, you **must** install Go on your machine. v3 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 v3 SDK and add it to your `go.mod` and `go.sum` files:

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

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

<Tip>
  Using 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/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/v3"
  	"github.com/LerianStudio/midaz-sdk-golang/v3/models"
  )

  func main() {
  	// Build a client. v3 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>
  Want to learn more about authentication? Jump to the [Authentication](#authentication) section for the full setup.
</Tip>

### Step 5 – Run the project

Run the following command:

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

## SDK architecture

***

The **Midaz SDK for Go** is built around clarity and predictability. Every service is reachable directly on the client, every list method follows the same trio shape, every error is structured, and 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>
  Want to dive deeper? Check the [v3 design rationale](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/v3-dx-plan.md) for the full architectural story behind the rewrite.
</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 — no `UseAllAPIs()` toggle, no service registration step. Every service is initialized and 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

Models reflect how Midaz thinks about finance, with each type 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 v3, 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>
  Need a builder, an internal request shape, or a deprecated type? Import `github.com/LerianStudio/midaz-sdk-golang/v3/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 that target common dev challenges — from config handling to retry policies and security primitives. They split into two groups: **core** packages that power cross-cutting SDK concerns, and **helper** packages that 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 v3, the SDK requires exactly one authentication source at construction time. Calling `midaz.New(...)` with neither returns a typed configuration error — 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/v3"
  )

  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/v3/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 v3 — `config.FromEnvironment()` must be in the option chain. The SDK no longer reads env vars implicitly during construction.
</Note>

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

## Multi-tenancy

***

**Tenant scope is derived from the Access Manager / JWT claims** used to obtain the token. The SDK applies tenant identity automatically based on those claims — no extra configuration on the client side.

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 v3 ships in three flavors. Pick the one that matches your use case — they're 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: 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/v3"
      "github.com/LerianStudio/midaz-sdk-golang/v3/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/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):
          // 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/v3/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/v3/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>
  Want to dive deeper? Check the [Error handling guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/errors.md) in the SDK repo for the full category map, every code, and retry boundary semantics.
</Tip>

## Logging

***

In v3, **`*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.

That means no surprise log lines in your stdout, and no fighting with the SDK over log format. 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/v3"
  )

  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>

Need zap, zerolog, or charmbracelet/log? They all integrate as `slog.Handler` adapters — the SDK doesn't care which backend produces the records, only that it speaks slog.

<Tip>
  Wiring zap, zerolog, or another backend? 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 v3. 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/v3"
      "github.com/LerianStudio/midaz-sdk-golang/v3/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/v3/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>
  Want to dive deeper? Check the [Tracing guide](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/tracing.md) in the SDK repo for span attribute contracts, metric names, and exporter setup.
</Tip>

## Idempotency

***

Auto-idempotency is **on by default** in v3. The SDK emits an `X-Idempotency: <uuid>` header on every unsafe HTTP request (POST, PUT, PATCH, DELETE), so retries on transient failures don't accidentally double-create resources.

You can override the auto-generated key per-call when you need a stable, caller-supplied key — 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/v3/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 using environment variables, no need to hardcode anything. Environment loading is **explicit** in v3 — pass `config.FromEnvironment()` in your config option chain to opt in.

| Variable                | Description                                                      |
| :---------------------- | :--------------------------------------------------------------- |
| `MIDAZ_ENVIRONMENT`     | Target environment (`local`, `development`, `production`).       |
| `MIDAZ_BASE_URL`        | Single base URL used when service-specific URLs are not set.     |
| `MIDAZ_ONBOARDING_URL`  | Override the onboarding service base URL.                        |
| `MIDAZ_TRANSACTION_URL` | Override the transaction service base URL.                       |
| `MIDAZ_CRM_URL`         | Override the CRM service base URL (used by Holders and Aliases). |
| `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_USER_AGENT`      | Custom User-Agent header sent with every request.                |
| `MIDAZ_DEBUG`           | Enable debug logs (`true` or `false`).                           |
| `MIDAZ_MAX_RETRIES`     | Maximum retry attempts for failed requests.                      |
| `MIDAZ_IDEMPOTENCY`     | Enable automatic idempotency key generation for unsafe requests. |
| `MIDAZ_SKIP_AUTH_CHECK` | Skip Access Manager config validation at startup (testing only). |

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 (v3 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

***

v3 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 `/v3` and walk the breaking changes with the side-by-side examples in the migration guide.

The biggest moves to plan for:

* **Module path and package name**: `github.com/LerianStudio/midaz-sdk-golang/v3` (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 full walk-through with side-by-side v2 / v3 code on every breaking change, read the [Migration guide from v2 to v3](https://github.com/LerianStudio/midaz-sdk-golang/blob/main/docs/migration-v2-to-v3.md).

## 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/v3" />
</Columns>
