# OTLP endpoint reference

> Paths, authentication, content types, compression, response codes, Retry-After and the per-request limits of OTLP/HTTP sent to Belvador.

Belvador accepts OpenTelemetry traces, logs and metrics over OTLP/HTTP, as defined by the OTLP 1.x specification. Any exporter that follows it works; this page lists Belvador's paths, rules and limits.

## Endpoint

```text
https://ingest.belvador.dev/otlp/<project-id>
```

Exporters append `/v1/traces`, `/v1/logs` and `/v1/metrics` themselves when you set `OTEL_EXPORTER_OTLP_ENDPOINT`. If you set the per-signal variables (`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`) instead, give the full URL including `/v1/traces`, `/v1/logs` or `/v1/metrics`. Metrics need a key with **Metrics** ticked ([Metrics](https://belvador.dev/en/docs/otel/metrics)).

## Authentication

```text
Authorization: Bearer <ingest key>
```

The key (`bvi_…`) is created in **Project settings → Ingest keys**, belongs to one project, and lists the signals it may send. Requests that carry a browser `Origin` header are refused, so a key that ends up in front-end code is useless to whoever finds it. Revoking a key takes effect within 60 seconds.

## Format

| | Accepted |
|---|---|
| Method | `POST` |
| `Content-Type` | `application/x-protobuf` (recommended) or `application/json` |
| `Content-Encoding` | none or `gzip` |
| Transport | HTTPS only; gRPC is not accepted directly — use a [Collector](https://belvador.dev/en/docs/otel/collector) |

## Responses

| Code | Means | The exporter should |
|---|---|---|
| 200 | Accepted and stored durably at the edge (empty response body). | Nothing. Records can still be dropped by the node later — see Usage. |
| 400 | The body is not valid OTLP. | Do not retry; fix the exporter. |
| 401 | Key missing, malformed or revoked. | Do not retry; check `OTEL_EXPORTER_OTLP_HEADERS`. |
| 403 | Key belongs to another project or lacks the signal scope (for example **Metrics** is not ticked), a browser sent it, or intake is paused for the workspace. | Do not retry; use the right project/signal key, server-side. |
| 404 | Unknown path. | Do not retry; the endpoint ends in `/otlp/<project-id>` and exporters add `/v1/traces`, `/v1/logs`, `/v1/metrics`. |
| 413 | Body larger than 4 MiB (compressed). | Send smaller batches. |
| 415 | Unsupported Content-Type or Content-Encoding. | Use `http/protobuf` or `http/json`, `gzip` or no compression. |
| 429 | Rate or monthly quota exceeded. | Retry after `Retry-After` (usually 1 s). |
| 503 | The edge cannot take it right now (node buffer full, transient fault). | Retry after `Retry-After` (30 s). |

A successful response has an empty body (`{}` for JSON). Belvador does not return OTLP *partial success* counts: a `200` means the request is stored durably at the edge, and your node validates each record afterwards. Records it drops are counted by reason in **Project settings → Traces, logs and metrics received** ([what each reason means](https://belvador.dev/en/docs/otel/troubleshooting#200-but-some-records-are-missing)).

`429` and `503` carry `Retry-After`. Standard exporters retry these, as well as `502` and `504`, with backoff; Deno's built-in exporter does not ([details](https://belvador.dev/en/docs/otel/deno#limitation-no-retry)).

## Limits

| Limit | Value |
|---|---|
| Request body (compressed) | 4 MiB |
| Body after decompression | 16 MiB |
| Maximum compression ratio | 32× |
| Records per request | 8192 |
| Attributes per span/log/resource | 128 |
| Attribute key length | 256 bytes |
| Attribute string value | 16 KiB |
| Log body | 64 KiB |
| Events / links per span | 128 / 128 |
| Accepted timestamps | 7 days back to 5 minutes ahead |

Values over the attribute and log body limits are cut and end with `…[truncated]`; attributes beyond the count limit are dropped. Both are counted, and the rest of the record is kept.

## Resource attributes Belvador reads

| Attribute | Used for |
|---|---|
| `service.name` | The service in Traces, Logs and the **Connection** tab (up to 200 services per project are listed by name) |
| `deployment.environment.name` | The environment filter; `deployment.environment` is read when the new name is absent |
| `telemetry.sdk.name`, `telemetry.sdk.language`, `telemetry.sdk.version` | The SDK column of **Evidence per signal** |
| `belvador.verification_id` (on a span or log) | Marks a [connection check](https://belvador.dev/en/docs/otel/quickstart#4-check-the-connection) |
