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

# Migration from v0

> Migrate your application to the new Apitally SDK for .NET.

The .NET SDK now uses OpenTelemetry to collect and send metrics, logs, and traces.

<Warning>
  Request logging, tracing, and application log capture are now enabled by default. If you previously used the SDK for metrics only, set `SampleRate = 0` to keep that behavior.
</Warning>

## Installation and setup

The updated [setup guide](/sdk-reference/dotnet/v1/setup-guides/aspnet-core) provides the installation steps and initialization code. Follow it to replace your existing SDK integration.

The SDK now requires .NET 8 or later. If your application references OpenTelemetry packages directly, upgrade them to version 1.19.0 or later.

### Write tokens replace client IDs

The SDK now authenticates with a **write token** instead of a client ID. Your existing app's token (`apt_...`) is available under *Setup instructions* in the [Apitally dashboard](https://app.apitally.io/apps).

Use this token as the `WriteToken` option in place of `ClientId`, or set the `APITALLY_WRITE_TOKEN` environment variable. A missing or invalid token no longer fails application startup. Instead, the SDK logs an error and disables itself.

### `UseApitally()` has been removed

`AddApitally()` now registers everything the SDK needs, including its middleware. Remove the `app.UseApitally()` call. The SDK adds its middleware at the start of the request pipeline automatically.

```csharp theme={null}
// Before
builder.Services.AddApitally(options =>
{
    options.ClientId = "your-client-id";
});

var app = builder.Build();
app.UseApitally();

// After
builder.Services.AddApitally(options =>
{
    options.WriteToken = "your-write-token";
});

var app = builder.Build();
```

If you use a `Startup` class, keep calling `services.AddApitally()` in `ConfigureServices` and remove `app.UseApitally()` from `Configure`.

## Configuration changes

The `RequestLoggingOptions` class and `RequestLogging` property have been removed. Their settings are now properties directly on `ApitallyOptions`, with the option changes listed below.

The same applies to the `Apitally` section in `appsettings.json`:

```jsonc theme={null}
// Before
{
  "Apitally": {
    "ClientId": "your-client-id",
    "RequestLogging": {
      "Enabled": true,
      "IncludeRequestBody": true,
      "IncludeResponseBody": true,
      "HeaderMaskPatterns": ["^X-Internal-"]
    }
  }
}

// After
{
  "Apitally": {
    "WriteToken": "your-write-token",
    "CaptureRequestBody": true,
    "CaptureResponseBody": true,
    "MaskHeaders": ["^X-Internal-"]
  }
}
```

### Changed options

The following options have been changed:

<table>
  <colgroup>
    <col width="40%" />

    <col width="60%" />
  </colgroup>

  <thead>
    <tr>
      <th style={{ minWidth: "240px" }}>Option</th>
      <th style={{ minWidth: "300px" }}>Change</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>ClientId</code></td>
      <td>Replaced by <code>WriteToken</code>, which requires a new credential.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>Env</code></td>
      <td>Defaults to the lowercased ASP.NET Core environment name instead of <code>default</code>, with <code>Production</code> shortened to <code>prod</code> and <code>Development</code> to <code>dev</code>. Remove the option if the derived name fits your setup.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.CaptureLogs</code></td>
      <td>Moved to <code>CaptureLogs</code>. Default changed from <code>false</code> to <code>true</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.IncludeRequestHeaders</code></td>
      <td>Renamed to <code>CaptureRequestHeaders</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.IncludeRequestBody</code></td>
      <td>Renamed to <code>CaptureRequestBody</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.IncludeResponseHeaders</code></td>
      <td>Renamed to <code>CaptureResponseHeaders</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.IncludeResponseBody</code></td>
      <td>Renamed to <code>CaptureResponseBody</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.QueryParamMaskPatterns</code></td>
      <td>Renamed to <code>MaskQueryParams</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.HeaderMaskPatterns</code></td>
      <td>Renamed to <code>MaskHeaders</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.BodyFieldMaskPatterns</code></td>
      <td>Renamed to <code>MaskBodyFields</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.MaskRequestBody</code></td>
      <td>Moved to <code>MaskRequestBody</code> with new arguments.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.MaskResponseBody</code></td>
      <td>Moved to <code>MaskResponseBody</code> with new arguments.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.ShouldExclude</code></td>
      <td>Replaced by <code>SampleOnRequest</code> or <code>SampleOnResponse</code> with new arguments and return values.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.PathExcludePatterns</code></td>
      <td>Renamed to <code>ExcludePaths</code>. Matches actual request paths instead of matched route patterns.</td>
    </tr>
  </tbody>
</table>

### Removed options

These options have been removed:

<table>
  <colgroup>
    <col width="40%" />

    <col width="60%" />
  </colgroup>

  <thead>
    <tr>
      <th style={{ minWidth: "240px" }}>Removed option</th>
      <th style={{ minWidth: "300px" }}>Migration</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging</code></td>
      <td>Set its properties directly on <code>ApitallyOptions</code>, applying the changes above. Remove the <code>Enabled</code> flag.</td>
    </tr>

    <tr>
      <td>
        <code style={{ whiteSpace: "nowrap" }}>RequestLogging.Enabled</code> and<br />
        <code style={{ whiteSpace: "nowrap" }}>RequestLogging.CaptureTraces</code>
      </td>

      <td>Previously defaulted to <code>false</code>. Request logging and tracing are now enabled by default. Use <code>SampleRate = 0</code> to disable request logs and traces.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.IncludeQueryParams</code></td>
      <td>Query parameters are now always captured. To mask all values, use <code>MaskQueryParams = \[".\*"]</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>RequestLogging.IncludeException</code></td>
      <td>Unhandled exceptions are now always captured in request traces.</td>
    </tr>
  </tbody>
</table>

The [configuration reference](/sdk-reference/dotnet/v1/configuration) lists all available options.

## Consumer identification

The SDK now provides the `IApitally` service with a `SetConsumer()` method. Setting the `ApitallyConsumer` item in `HttpContext.Items` no longer has any effect, and the `ApitallyConsumer` class has been removed.

Call `SetConsumer()` where the consumer is known, such as in your authentication code. Inject `IApitally` into your controllers, Minimal API handlers, or middleware, or resolve it from `HttpContext.RequestServices`:

```csharp theme={null}
// Before
context.Items["ApitallyConsumer"] = new ApitallyConsumer
{
    Identifier = user.Identifier,
    Name = user.Name,
    Group = user.Group,
};

// After
var apitally = context.RequestServices.GetRequiredService<IApitally>();
apitally.SetConsumer(
    user.Identifier,
    name: user.Name, // optional
    group: user.Group, // optional
    attributes: new Dictionary<string, string?> { ["plan"] = user.Plan } // optional
);
```

The identifier is now always a `string`. Convert numeric identifiers with `ToString()`.

## Body masking callbacks

`MaskRequestBody` and `MaskResponseBody` now both receive `(span, body)`, rather than the `Request` and `Response` objects. The body is passed as a `byte[]` after decompression.

Callbacks may run later on another thread against an ended span snapshot. Request metadata is available through [`span.Attributes`](/sdk-reference/dotnet/v1/attributes).

For example, a callback that masks bodies for admin routes becomes:

```csharp theme={null}
// Before
options.RequestLogging.MaskRequestBody = request =>
    request.Path?.StartsWith("/admin/") == true ? null : request.Body;

// After
options.MaskRequestBody = (span, body) =>
    span.Attributes.GetValueOrDefault("http.route") is string route && route.StartsWith("/admin/")
        ? null
        : body;
```

## Request exclusion

Use sampling callbacks to exclude requests: `SampleOnRequest` for early decisions based on the request, or `SampleOnResponse` for decisions based on the response status or consumer. Both receive the span as their only argument.

The callbacks should return `1.0` to capture the request, and `0.0` to exclude it. Callbacks can also return any probability between 0 and 1. Returning `null` preserves a previously made sampling decision.

For example, to capture only error responses:

```csharp theme={null}
// Before
options.RequestLogging.ShouldExclude = (request, response) => response.StatusCode < 400;

// After
options.SampleOnResponse = span =>
    span.Attributes.GetValueOrDefault("http.response.status_code") is long statusCode
    && statusCode >= 400
        ? 1.0
        : 0.0;
```

Replace the `ShouldExclude` option with the appropriate sampling callback. Note that captured headers and bodies are not available in sampling callbacks.

Sampling affects request logs and traces, but not metrics.

See [sampling](/sdk-reference/dotnet/v1/sampling) for details.

### Path exclusions

`ExcludePaths` now matches request paths rather than matched route patterns. If a pattern contains route parameters, update it to match concrete values. For example, replace `"^/users/\\{id\\}$"` with `"^/users/[^/]+$"` to match `/users/123`.

## Existing OpenTelemetry setups

If your application registers a tracer provider via `AddOpenTelemetry().WithTracing(...)`, the SDK automatically adds its span processor. No manual registration is required.

If you build a tracer provider separately, for example with `Sdk.CreateTracerProviderBuilder()`, add the ASP.NET Core instrumentation and the `apitally.otel` source to it, and register it with dependency injection so the SDK can find it:

```csharp theme={null}
builder.Services.AddSingleton<TracerProvider>(tracerProvider);
builder.Services.AddApitally();
```

Review these settings when upgrading:

* **Sampling:** Previously, your provider's sampler affected traces but not Apitally's request logs. It now affects both, so review its sampling rate when upgrading. Metrics remain unsampled.
* **Sources:** Previously, the SDK captured activities from any `ActivitySource` during requests. It now captures only the activities your provider is configured for. Register your own sources with `AddSource()`, and add `AddHttpClientInstrumentation()` to keep tracing outgoing HTTP calls.

## Other changes

* **Integration tests:** The SDK is automatically disabled in tests using the in-memory `TestServer`, including the default `WebApplicationFactory`. For tests running against a real Kestrel server, set `Disabled = true` or the `APITALLY_DISABLED` environment variable.
* **Network access:** The SDK now sends data to `otlp.apitally.io` instead of `hub.apitally.io`. Update firewall allowlists if necessary.
* **Removed types:** The `Apitally.Models` namespace, `ApitallyConsumer`, `RequestLoggingOptions`, `ValidationErrorFilter`, and `ValidationError` have been removed from the public API.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.