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

# Support bundles

> Verify a deployed endpoint and build a redacted bundle to share with support.

Before running a full slide, or after something goes wrong, use these tools to check a deployment and package what happened into something you can send to Bioptimus support — nothing here leaves your environment unless you send it yourself. For where logs go and how to customize them, see [Logging](/guides/get-started/logging).

* [**Verify an endpoint**](#verify-an-endpoint) — send one synthetic tile and check the response against the model contract.
* [**Inspect your environment**](#inspect-your-environment) — capture a diagnostic snapshot for self-diagnosis before you open a ticket.
* [**Build a support bundle**](#build-a-support-bundle) — package diagnostics and logs into a redacted `.tar.gz`.
* [**Customize what gets redacted**](#customize-what-gets-redacted) — control the redaction policy on anything you export.

<Note>
  Every artifact below is redacted best-effort, not guaranteed PII-free — see [What gets shared](#what-gets-shared) before sharing one outside your environment.
</Note>

## Verify an endpoint

Before running a full slide, send one synthetic tile through the endpoint and check the response against the model's contract:

<CodeGroup>
  ```python Python theme={null}
  from bioptimus.observability.healthcheck import verify_endpoint

  report = verify_endpoint(
      model="h1",                          # model name, e.g. "h1" or "m-optimus"
      backend="local",                     # "remote", "aws", "local", or "huggingface"
      checkpoint="<path-to-checkpoint>.pt2",  # local only
      device="cuda",                       # "cuda" or "cpu" for the in-process backends
  )
  print(report.summary())  # h1 on local: ok (4 checks)
  ```

  ```bash CLI theme={null}
  bioptimus verify --model h1 --backend local --checkpoint <path-to-checkpoint>.pt2
  bioptimus verify --model h1 --backend remote --base-url http://localhost:8080
  bioptimus verify --model h1 --backend aws --endpoint-name <name> --region-name <region>
  ```
</CodeGroup>

The CLI prints a checklist, one line per step:

```text theme={null}
h1 on local: ok (4 checks)
  [PASS] client_construction (812.4 ms) — Built a local client for h1.
  [PASS] metadata (0.1 ms) — Metadata reports model 'h1'.
  [PASS] embedding_request (156.2 ms) — Endpoint accepted the synthetic tile in embedding mode.
  [PASS] embedding_output_contract (0.0 ms) — Output is 1536 finite value(s), matching the model contract.
```

Each backend takes its own connection arguments, all passed straight through to `Backbone`:

| Backend       | Required arguments               | Python                                             | CLI                                             |
| ------------- | -------------------------------- | -------------------------------------------------- | ----------------------------------------------- |
| `remote`      | Server base URL                  | `base_url="http://localhost:8080"`                 | `--base-url http://localhost:8080`              |
| `aws`         | SageMaker endpoint + region      | `endpoint_name="<name>"`, `region_name="<region>"` | `--endpoint-name <name> --region-name <region>` |
| `local`       | Checkpoint path + device         | `checkpoint="<path>.pt2"`, `device="cuda"`         | `--checkpoint <path>.pt2 --device cuda`         |
| `huggingface` | Device (weights auto-downloaded) | `device="cuda"`, `precision="fp16"`                | `--device cuda --precision fp16`                |

For the `local` backend you can point at the directory holding the checkpoint (`model_dir=` / `--model-dir`) instead of naming the `.pt2` file. Add `--json` to print the full `HealthReport` instead, or `--mode embedding`/`--mode prediction` to force a specific mode.

The command exits non-zero on any failed check, so it can gate a pipeline before the expensive run starts. Each failure carries its own code and fix — see [Error codes](/guides/get-started/logging#error-codes):

```python theme={null}
for check in report.failures:
    print(check.name, check.error_code)  # e.g. client_construction BIO-MODEL-002
    print(check.remediation)              # what to do about it
```

### Read the report

`verify_endpoint` returns a `HealthReport`; `bioptimus verify --json` prints the same object. Every step is a `Check`, and nothing raises unless you ask it to:

```python theme={null}
report.ok            # True when every non-skipped check passed
report.summary()     # "h1 on local: ok (4 checks)"
report.checks        # every Check, in execution order
report.failures      # only the checks that ran and failed
report.to_dict()     # JSON-friendly dict (what --json prints, what a bundle stores)
```

Each `Check` carries `name`, `passed`, `skipped`, `duration_ms`, `detail`, and — on failure — `error_code`, `remediation`, and structured `context` (for example the observed vs. expected output length).

### Verify options

| Option                  | Python                  | CLI                             | Purpose                                               |
| ----------------------- | ----------------------- | ------------------------------- | ----------------------------------------------------- |
| Force specific modes    | `modes=["embedding"]`   | `--mode embedding` (repeatable) | Skip mode auto-detection                              |
| Override tile size      | `tile_size=(224, 224)`  | —                               | Probe how the endpoint handles another geometry       |
| Seed the synthetic tile | `seed=0`                | —                               | Make two runs byte-identical and comparable           |
| Raise instead of report | `raise_on_failure=True` | *(non-zero exit)*               | Turn the first failure into a `BioptimusRuntimeError` |
| Bound a slow endpoint   | `timeout=30.0`          | `--timeout 30`                  | Cap the wait on the `remote`/`aws` request            |

By default `modes="auto"` resolves exactly the modes the model exposes — embedding for the plain backbones, prediction for `tissue-seg`, both for `m-optimus`, and embedding-only for the `huggingface` backend. The tile sent is a deterministic gradient-plus-noise image at the model's declared tile size, so a synthetic request is never mistaken for customer data in a server log.

## Inspect your environment

The same environment snapshot that goes into a bundle is available on its own — useful for self-diagnosis before you open a ticket. It never raises: a probe that cannot run degrades to an `{"error": ...}` entry instead of failing the snapshot.

```python theme={null}
from bioptimus.observability.probes import snapshot

snap = snapshot()                    # a plain, JSON-friendly dict
snap["gpu"]["cuda_available"]
snap["dependencies"]["satisfied"]
```

The snapshot is stamped with `schema_version` and `captured_at` and gathers:

| Section        | Reports                                                                                      |
| -------------- | -------------------------------------------------------------------------------------------- |
| `versions`     | Installed versions of the SDK and key dependencies                                           |
| `dependencies` | Whether installed packages satisfy the SDK's declared constraints (`satisfied`, `conflicts`) |
| `host`         | Python, platform, interpreter, CPU/RAM, container memory limit, open-file limit              |
| `env`          | An allowlist of non-secret, inference-relevant environment variables                         |
| `gpu`          | Torch/CUDA/driver versions, per-device capability and memory, context health                 |
| `wsi_backends` | Which WSI backends (OpenSlide, cuCIM, tifffile) import, and why not                          |
| `disk`         | Temp-dir free space, writability, and leaked AOTInductor extractions                         |
| `pt2_compat`   | Whether each `.pt2` checkpoint matches the host GPU/CPU architecture                         |
| `models`       | Load state, device, and precision of loaded models                                           |

Each probe is also callable on its own — for a fast CUDA or checkpoint check without the whole snapshot:

```python theme={null}
from bioptimus.observability.probes import probe_gpu, probe_pt2_compat

probe_gpu()["cuda_available"]                          # False -> run on a GPU instance
probe_pt2_compat({"h1": "h1.pt2"})[0]["compatible"]   # False -> checkpoint/arch mismatch
```

<Tip>
  A model that loads but fails at inference is almost always a version or architecture mismatch. `snap["dependencies"]["conflicts"]` and `snap["pt2_compat"]` pinpoint it in one call — the same evidence support would ask for.
</Tip>

## Build a support bundle

One command packages a redacted environment snapshot and logs into a `.tar.gz` — attach this to a support request:

```bash theme={null}
bioptimus support-bundle --output bundle.tar.gz
# Wrote support bundle to bundle.tar.gz
```

`--categories` selects what goes in the bundle: `diagnostics` (environment snapshot and logs, the default) and/or `usage` (model-request records only, e.g. for a billing question). Repeat the flag or comma-separate to combine them. Add a per-slide status ledger with `--workspace`, or fold in a running server's own diagnostics:

```bash theme={null}
bioptimus support-bundle --categories usage --output usage.tar.gz
bioptimus support-bundle --categories diagnostics,usage --output bundle.tar.gz
bioptimus support-bundle --workspace path/to/workspace --output bundle.tar.gz
bioptimus support-bundle --server-url http://localhost:8080 --output bundle.tar.gz
```

Or from Python:

```python theme={null}
from bioptimus.observability.support_bundle import build_support_bundle

archive = build_support_bundle(
    output_path="bundle.tar.gz",
    categories=["diagnostics", "usage"],  # omit to get the default full diagnostics bundle
)
print(archive)  # bundle.tar.gz
```

**What you can put in a bundle:**

| Contents                                   | Python                                                           | CLI                                             |
| ------------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------- |
| Environment snapshot + logs                | *default*                                                        | *default*                                       |
| Usage records only                         | `categories=["usage"]`                                           | `--categories usage`                            |
| `.pt2` checkpoint compatibility            | `checkpoints={"h1": "h1.pt2"}`                                   | `--checkpoint h1.pt2`                           |
| Per-slide status ledger                    | `workspace="path/"` or `ledger_file="manifest.yaml"`             | `--workspace path/` or `--ledger manifest.yaml` |
| A running server's own diagnostics         | `server_url="http://localhost:8080"`                             | `--server-url ...`                              |
| Endpoint verification (`healthcheck.json`) | `verify={"model": "h1", "backend": "remote", "base_url": "..."}` | —                                               |
| Loaded-model summary                       | `models={"h1": model}`                                           | —                                               |

The last two are Python-only, since they need a live inference request or in-memory model objects. The bundle finds your log file automatically through `BIOPTIMUS_LOG_FILE` and includes its rotations; point it at a different file with `--log-file` / `log_file=`. Every option writes a single local `.tar.gz`; nothing is sent anywhere.

Both the CLI and Python **redact by default**. For local self-debugging you can keep full paths and identifiers with `--no-anonymize` (CLI) or `anonymize=False` (Python) — never share an unredacted bundle outside your environment.

### What's inside the archive

Extract the `.tar.gz` and inspect it before sending — every bundle is one directory:

```text theme={null}
bioptimus-support-<timestamp>/
  manifest.json            # schema version, timestamp, SDK version, anonymized flag, file list
  diagnostics.json         # the redacted environment snapshot (the sections shown above)
  logs/<name>              # each configured log file and its rotations, redacted line by line
  usage.json               # model-request records — only with the usage category
  healthcheck.json         # endpoint verification report — only with verify=
  ledger.yaml              # per-slide status summary — only with --workspace / --ledger
  server_diagnostics.json  # the server's own /diagnostics — only with --server-url
  server_usage.json        # the server's usage records — only with --server-url and usage
```

`manifest.json` records whether the bundle was anonymized and the exact file list, so a recipient can confirm what they received. The first three files are always present in a diagnostics bundle; the rest appear only when you request the option that produces them.

A running server exposes the same redacted data directly:

<Warning>
  `/diagnostics` is **unauthenticated** — keep it behind the same VPN or network boundary as the rest of your deployment, never on a public network.
</Warning>

```bash theme={null}
curl "http://localhost:8080/diagnostics?categories=usage" -o usage.json
```

```json usage.json theme={null}
{
  "usage": [
    {
      "event": "model_request_received",
      "model_name": "h1",
      "requested_model_name": "h1",
      "mode": "embedding",
      "endpoint": "/invocations"
    }
  ]
}
```

<Note>
  On **AWS SageMaker**, the server itself runs in your own account — its logs land in **your CloudWatch**, not ours. `bioptimus support-bundle` still captures the client-side SDK logs and diagnostics; CloudWatch is the source for server-side logs on that platform.
</Note>

## Customize what gets redacted

Redaction is driven by a per-field policy you can inspect and change. Each recognized field takes one of three strategies:

| Strategy          | Effect                                                                 | Default for                                          |
| ----------------- | ---------------------------------------------------------------------- | ---------------------------------------------------- |
| `Strategy.HASH`   | Replace with a stable pseudonym (`sha256:...`), preserving correlation | Identifiers: `slide_id`, `patient_id`, `wsi_id`, ... |
| `Strategy.REDACT` | Replace with `[REDACTED]`, dropping the value                          | *(opt-in)*                                           |
| `Strategy.KEEP`   | Keep verbatim; still scrubs embedded paths and user names              | Any unrecognized field                               |

The default policy (`DEFAULT_CONFIG`) hashes every recognized identifier and path field. To apply a different policy — for example, redact `slide_id` outright instead of hashing it — build an `AnonymizationConfig` and run your own data (a log entry, a manifest, any dict) through the redactor before you share it:

```python theme={null}
from bioptimus.observability.anonymize import (
    AnonymizationConfig, Strategy, anonymize_mapping,
)
from bioptimus.observability import read_recent_logs

policy = AnonymizationConfig(overrides={"slide_id": Strategy.REDACT})

for entry in read_recent_logs(anonymize=False):   # read full detail...
    shareable = anonymize_mapping(entry, policy)    # ...then redact with your policy
```

The building blocks are reusable on any value: `hash_identifier`, `redact_text` (paths, URIs, and slide names in free text), `scrub_user_paths` (home-directory user names), and `anonymize_value` / `anonymize_mapping` (a single keyed value, or a whole nested mapping). See the [`anonymize` reference](/sdk-reference/observability/anonymize).

## What gets shared

<Warning>
  **Before you share a bundle, know what's in it:**

  * **Hashed, not encrypted.** Identifiers and path-like fields (slide/patient IDs, `slide_path`, ...) are replaced with a stable, unsalted hash so the same slide correlates across log lines — not a cryptographic guarantee. Paths embedded in free text are redacted outright.
  * **Nothing is transmitted automatically.** Every command above only writes a local file or answers a request you make yourself; sharing it with Bioptimus is a manual step.
  * **Best-effort redaction.** Bioptimus makes no warranty that the output is free of PII and accepts no liability for it — review a bundle yourself before sharing it outside your environment. An unrecognized field or path shape can pass through unredacted; in particular, connection arguments such as a SageMaker `endpoint_name` or `region_name` aren't recognized identifier fields, so they pass through unchanged.
</Warning>
