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

# OpenTelemetry

> Send sandbox and account metrics, and sandbox lifecycle events, to Datadog, Grafana Cloud, Honeycomb, or any OTLP collector.

Boat pushes your account's telemetry to one OTLP/HTTP endpoint that you choose. You do not install an agent in your sandboxes.

| Signal | Path | When |
| - | - | - |
| Sandbox and account metrics | `<endpoint>/v1/metrics` | Every 60 seconds |
| Sandbox lifecycle events, as OTLP log records | `<endpoint>/v1/logs` | When each event occurs |

| | CLI | API | SDKs |
| - | - | - | - |
| Set or replace | `boat telemetry set <endpoint> -H key=value` | `PUT /telemetry` | `setTelemetry` |
| Read status | `boat telemetry` | `GET /telemetry` | `getTelemetry` |
| Remove | `boat telemetry unset` | `DELETE /telemetry` | `deleteTelemetry` |

An account has one endpoint. The API needs a key with `account.admin`, because the headers hold your vendor credentials.

## Set the endpoint

In the dashboard, use the **Webhooks & OTel** tab. Or use the CLI, the API or an SDK.

Give the base URL of the collector. Boat adds `/v1/metrics` and `/v1/logs`. If you paste a URL that ends in `/v1/metrics`, Boat removes that part.

<CodeGroup>
  ```bash CLI theme={null}
  boat telemetry set https://otlp.datadoghq.com -H dd-api-key=$DD_API_KEY
  # https://otlp.datadoghq.com
  #   headers      dd-api-key
  #   last export  -
  # Saved. Test export accepted.
  ```

  ```bash curl theme={null}
  curl -sS -X PUT "$BOAT_API_BASE/telemetry" \
    -H "Authorization: Bearer $BOAT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"endpoint":"https://otlp.datadoghq.com","headers":{"dd-api-key":"'"$DD_API_KEY"'"}}'
  # {"type":"telemetry.updated","telemetry":{"endpoint":"https://otlp.datadoghq.com","headers":["dd-api-key"],...},"test":{"ok":true,"status":202,"message":null}}
  ```

  ```ts TypeScript theme={null}
  const saved = await sandbox.setTelemetry({
    endpoint: "https://otlp.datadoghq.com",
    headers: { "dd-api-key": process.env.DD_API_KEY! },
  });
  console.log(saved.test.ok, saved.test.status);
  ```

  ```python Python theme={null}
  import os
  from boat_sdk.models.telemetry_set_request import TelemetrySetRequest

  saved = sandbox.set_telemetry(TelemetrySetRequest(
      endpoint="https://otlp.datadoghq.com",
      headers={"dd-api-key": os.environ["DD_API_KEY"]},
  ))
  print(saved.test.ok, saved.test.status)
  ```
</CodeGroup>

When you save, Boat sends one empty metrics export at once and returns the collector's answer in `test`. A wrong key or endpoint shows here, before you wait for data. The first metrics arrive within 60 seconds.

<Note>
  The endpoint must use HTTPS on port 443 or 4318 and resolve only to public addresses. Boat encrypts the header values at rest and never returns them. Up to 20 headers.
</Note>

### Vendor settings

| Vendor | Endpoint | Headers |
| - | - | - |
| Datadog | `https://otlp.datadoghq.com` (use the OTLP host of your Datadog site) | `dd-api-key=<API key>` |
| Grafana Cloud | The OTLP endpoint on your stack's OpenTelemetry card, for example `https://otlp-gateway-prod-eu-west-2.grafana.net/otlp` | `Authorization=Basic <base64 of instanceId:token>` |
| Honeycomb | `https://api.honeycomb.io` (EU: `https://api.eu1.honeycomb.io`) | `x-honeycomb-team=<API key>`, `x-honeycomb-dataset=boat` |
| Your own OpenTelemetry Collector | `https://otel.example.com` (port 443 or 4318) | The auth header that your collector checks |

For Grafana Cloud, make the header value like this:

```bash theme={null}
echo -n "$GRAFANA_INSTANCE_ID:$GRAFANA_TOKEN" | base64
```

### Your own collector and Prometheus

Run the OpenTelemetry Collector with an OTLP/HTTP receiver behind TLS. Export to Prometheus with remote write, or expose a scrape endpoint:

```yaml otelcol.yaml theme={null}
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
exporters:
  prometheus:
    endpoint: 0.0.0.0:8889
  debug: {}
service:
  pipelines:
    metrics: { receivers: [otlp], exporters: [prometheus] }
    logs: { receivers: [otlp], exporters: [debug] }
```

Prometheus turns `boat.sandbox.cpu.utilization` into `boat_sandbox_cpu_utilization`, with `boat_sandbox_id` and `boat_sandbox_name` labels.

## Metrics

All metrics are gauges. Every point carries `boat.sandbox.id` and `boat.sandbox.name`, so each sandbox is its own series in every vendor.

| Metric | Unit | Meaning |
| - | - | - |
| `boat.sandbox.cpu.utilization` | `1` | Share of CPU time busy, 0 to 1 |
| `boat.sandbox.memory.utilization` | `1` | Share of memory in use, 0 to 1 |
| `boat.sandbox.memory.usage` | `By` | Memory in use |
| `boat.sandbox.filesystem.utilization` | `1` | Share of the disk in use, 0 to 1 |
| `boat.sandbox.filesystem.usage` | `By` | Disk space in use |
| `boat.sandbox.io.pressure` | `1` | Share of time stalled on disk I/O, 0 to 1 |
| `boat.account.sandboxes.active` | `{sandbox}` | Sandboxes running now |
| `boat.account.sandboxes.limit` | `{sandbox}` | Sandboxes your plan lets run at once. Not sent on unlimited plans. |
| `boat.account.credit.balance` | `s` | Machine time left on the account. Not sent on unlimited plans. |

Each sandbox is one resource:

| Resource attribute | Value |
| - | - |
| `service.name` | `boat` |
| `service.instance.id`, `boat.sandbox.id` | The sandbox id |
| `boat.sandbox.name` | The sandbox name |
| `boat.sandbox.size` | The machine size |
| `boat.sandbox.memory.size`, `boat.sandbox.disk.size` | Total memory and disk, in bytes |
| `boat.account.id` | Your account id |

A sandbox sends points only while it runs. Each export holds the samples taken since the previous one.

## Events

Each [lifecycle event](/webhooks#events) is one OTLP log record. Its event name is `sandbox.ready`, `sandbox.hydrated`, `sandbox.error`, `sandbox.archived`, `sandbox.degraded`, or `sandbox.recovered`. Boat sets it in the record's event name field and in the `event.name` attribute.

| Attribute | Value |
| - | - |
| `boat.sandbox.id`, `boat.sandbox.name` | The sandbox |
| `boat.sandbox.state`, `boat.sandbox.previous_state` | The new and the old state, when the event has them |
| `boat.sandbox.reason` | Why, for `sandbox.degraded` and `sandbox.recovered` |
| `boat.event.id` | The same id as the webhook event. Use it to remove duplicates. |

`sandbox.error` has severity `ERROR`, `sandbox.degraded` has `WARN`, and the others have `INFO`.

## Delivery and errors

| Collector answer | What Boat does |
| - | - |
| `2xx` | Done. A partial-success answer shows its message in `lastError`. |
| `429`, `502`, `503`, `504`, or no answer | Sends the same data again later. Boat waits for `Retry-After` when the collector sends it. |
| Any other `4xx` or `5xx` | Drops that data and shows the error in `lastError`. Fix the key or the endpoint; the next window goes as usual. |

Metrics go in time order. After an outage, Boat sends at most the last 10 minutes of metrics. An event is retried like a [webhook delivery](/webhooks#delivery-and-retries), up to 8 attempts.

Read the status:

<CodeGroup>
  ```bash CLI theme={null}
  boat telemetry
  # https://otlp.datadoghq.com
  #   headers      dd-api-key
  #   last export  2026-10-09T12:00:10.000Z
  ```

  ```bash curl theme={null}
  curl -sS "$BOAT_API_BASE/telemetry" \
    -H "Authorization: Bearer $BOAT_API_KEY"
  # {"type":"telemetry.info","telemetry":{"endpoint":"...","headers":["dd-api-key"],"lastExportAt":"...","lastError":null,...}}
  ```

  ```ts TypeScript theme={null}
  const { telemetry } = await sandbox.getTelemetry();
  console.log(telemetry?.lastExportAt, telemetry?.lastError);
  ```

  ```python Python theme={null}
  status = sandbox.get_telemetry()
  print(status.telemetry.last_export_at if status.telemetry else None)
  ```
</CodeGroup>

## Remove the endpoint

<CodeGroup>
  ```bash CLI theme={null}
  boat telemetry unset
  ```

  ```bash curl theme={null}
  curl -sS -X DELETE "$BOAT_API_BASE/telemetry" \
    -H "Authorization: Bearer $BOAT_API_KEY"
  ```

  ```ts TypeScript theme={null}
  await sandbox.deleteTelemetry();
  ```

  ```python Python theme={null}
  sandbox.delete_telemetry()
  ```
</CodeGroup>

Boat stops the export and deletes the queued data at once.

## Related

* [Webhooks](/webhooks): the same lifecycle events, signed, to your own HTTPS endpoint.
* [Command history](/long-running-tasks#command-history): the output of the processes in a sandbox.


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