> ## 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 Python.

The Python 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 `sample_rate=0` to keep that behavior.
</Warning>

## Installation and setup

The updated [setup guides](/sdk-reference/python/v1/overview#supported-frameworks) provide the installation steps and initialization code for each framework. Follow these to replace your existing SDK integration.

### 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 `write_token` argument in place of `client_id`, or set the `APITALLY_WRITE_TOKEN` environment variable.

## Configuration changes

The `RequestLoggingConfig` class and `request_logging_config` argument have been removed. Their settings are now keyword arguments passed directly when initializing the SDK, with the option changes listed below.

### 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" }}>client\_id</code></td>
      <td>Replaced by <code>write\_token</code>, which requires a new credential.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>capture\_logs</code></td>
      <td>Default changed from <code>False</code> to <code>True</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>log\_request\_headers</code></td>
      <td>Renamed to <code>capture\_request\_headers</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>log\_request\_body</code></td>
      <td>Renamed to <code>capture\_request\_body</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>log\_response\_headers</code></td>
      <td>Renamed to <code>capture\_response\_headers</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>log\_response\_body</code></td>
      <td>Renamed to <code>capture\_response\_body</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>mask\_request\_body\_callback</code></td>
      <td>Renamed to <code>mask\_request\_body</code> with new arguments.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>mask\_response\_body\_callback</code></td>
      <td>Renamed to <code>mask\_response\_body</code> with new arguments.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>exclude\_callback</code></td>
      <td>Replaced by <code>sample\_on\_request</code> or <code>sample\_on\_response</code> with new arguments and return values.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>exclude\_paths</code></td>
      <td>Matches actual request paths instead of matched route patterns.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>urlconf</code></td>
      <td>Renamed to <code>django\_urlconf</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>include\_django\_views</code></td>
      <td>Renamed to <code>django\_include\_class\_based\_views</code>.</td>
    </tr>
  </tbody>
</table>

### Removed options

These options are no longer accepted when initializing the SDK:

<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" }}>request\_logging\_config</code></td>
      <td>Pass its settings directly as initialization keyword arguments, applying the changes above. Remove its <code>enabled</code> flag.</td>
    </tr>

    <tr>
      <td>
        <code style={{ whiteSpace: "nowrap" }}>consumer\_callback</code> and
        <code style={{ whiteSpace: "nowrap" }}>identify\_consumer\_callback</code>
      </td>

      <td>Call <code>apitally.set\_consumer(identifier, name=..., group=...)</code> during request handling instead of returning a consumer from a callback.</td>
    </tr>

    <tr>
      <td>
        <code style={{ whiteSpace: "nowrap" }}>enable\_request\_logging</code> and
        <code style={{ whiteSpace: "nowrap" }}>capture\_traces</code>
      </td>

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

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>log\_query\_params</code></td>
      <td>Query parameters are now always captured. To mask all values, use <code>{'mask_query_params=[r".*"]'}</code>.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>log\_exception</code></td>
      <td>Unhandled exceptions are now always captured in request traces.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>openapi\_url</code></td>
      <td>Custom OpenAPI URLs are no longer supported. FastAPI's schema is captured automatically.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>filter\_openapi\_paths</code></td>
      <td>Schema routes are now automatically filtered from the reported endpoint list.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>capture\_client\_disconnects</code></td>
      <td>Removed without a replacement.</td>
    </tr>

    <tr>
      <td><code style={{ whiteSpace: "nowrap" }}>proxy</code></td>
      <td>Configure proxies through <code>HTTPS\_PROXY</code>, <code>HTTP\_PROXY</code>, and <code>NO\_PROXY</code> environment variables.</td>
    </tr>
  </tbody>
</table>

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

## Consumer identification

The SDK now provides `apitally.set_consumer()` for all frameworks. The request argument and `ApitallyConsumer` class have been removed.

Call it where the consumer is known, such as in your authentication code:

```python theme={null}
import apitally

apitally.set_consumer(
    user.identifier,
    name=user.name,  # optional
    group=user.group,  # optional
)
```

This replaces `consumer_callback`, `identify_consumer_callback`, and consumer values assigned to request state. Existing `set_consumer(request, ...)` calls should use the new function without the `request` argument.

## Body masking callbacks

`mask_request_body_callback` and `mask_response_body_callback` are now named `mask_request_body` and `mask_response_body`. Both receive `(span, body)`, rather than request/response dictionaries. The body is passed as `bytes`. Request metadata is available through [`span.attributes`](/sdk-reference/python/v1/attributes).

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

```python theme={null}
from typing import Any

from opentelemetry.sdk.trace import ReadableSpan


# Before
def mask_request_body(request: dict[str, Any]) -> bytes | None:
    if (request["path"] or "").startswith("/admin/"):
        return None
    return request["body"]


# After
def mask_request_body(span: ReadableSpan, body: bytes) -> bytes | None:
    route = (span.attributes or {}).get("http.route")
    if isinstance(route, str) and route.startswith("/admin/"):
        return None
    return body
```

## Request exclusion

Use sampling callbacks to exclude requests: `sample_on_request(span)` for early decisions based on the request, or `sample_on_response(span)` for decisions based on the response status or consumer.

The callbacks should return `True` to capture the request, and `False` to exclude it. Callbacks can also return a probability as `float` between 0 and 1. Returning `None` preserves a previously made sampling decision.

For example, to capture only error responses:

```python theme={null}
from typing import Any

from opentelemetry.sdk.trace import ReadableSpan


# Before
def exclude_request(request: dict[str, Any], response: dict[str, Any]) -> bool:
    return response["status_code"] < 400


# After
def sample_on_response(span: ReadableSpan) -> bool:
    status_code = (span.attributes or {}).get("http.response.status_code")
    return isinstance(status_code, int) and status_code >= 400
```

Replace the `exclude_callback` argument 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/python/v1/sampling) for details.

### Path exclusions

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

## Existing OpenTelemetry setups

If you already have a global OpenTelemetry SDK `TracerProvider`, the SDK automatically adds its span processor. No manual registration is required.

Review these settings when upgrading:

* **Sampling:** Previously, your provider's sampler affected traces but not Apitally's request logs. It now affects both. Check that its sampling rate provides the request log coverage you want. Metrics remain unsampled.
* **Environment:** Previously, Apitally used its configured `env`. Your provider's `deployment.environment.name` now takes precedence when set. Align conflicting values or omit `env` to use the provider's value.
