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

# Send OTLP directly

> Send telemetry to Apsio from any OpenTelemetry SDK or collector, without an Apsio SDK.

The Apsio SDKs are the easiest way to send data, but any OpenTelemetry exporter that speaks
OTLP over HTTP can send to Apsio too: a backend service, an OpenTelemetry Collector, or an
SDK on a platform Apsio does not cover yet.

## Endpoint

| | |
| - | - |
| Per-app host | `https://<app key>.ingest.apsio.cloud` |
| Generic host | `https://ingest.apsio.cloud` |
| Paths | `/v1/logs`, `/v1/traces`, `/v1/metrics` |
| Encodings | OTLP/JSON (`application/json`) or OTLP/protobuf (`application/x-protobuf`), optionally gzip |
| Maximum request size | 1 MiB as sent |

Use the per-app host when you can: it lets Apsio move an app to another region without a
change on your side.

## Authentication

Send the app key in the `Apsio-App-Key` header. App keys ship inside apps, so they are not
secrets; they only route data to your project.

With the OpenTelemetry environment variables:

```sh theme={null}
OTEL_EXPORTER_OTLP_ENDPOINT=https://<app key>.ingest.apsio.cloud
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=apsio-app-key=<app key>
```

## Optional headers

Exporters that can set them should also send:

| Header | What it does |
| - | - |
| `Apsio-Batch-Id` | A unique id per request, kept across retries (16 to 64 characters of letters, digits, `_` or `-`). Apsio stores a request with the same id once. |
| `Apsio-Sent-At` | The sender's clock when sending, in Unix nanoseconds, set again on every retry. Apsio uses it to correct timestamps when the sender's clock is more than 2 minutes off. |

Without them, data is still accepted, but a retried request may be stored twice and
timestamps are stored as sent.

Leave `Apsio-Sent-At` out only when your code does not run at send time, because the
operating system sends the request for you (a background `URLSession` on Apple platforms): a
value written earlier would shift every timestamp by the delay. Any request your code sends,
including from a background job, sets it.

## Responses

| Status | Meaning | What to do |
| - | - | - |
| `200` | Accepted | Nothing |
| `400` | The body, encoding or a header is invalid; the body says which | Fix the request; retrying will not help |
| `401` | `Apsio-App-Key` is missing or unknown | Check the key in Settings → Apps |
| `402` | The organization's trial has ended | Choose a plan in the console; until then, stop sending |
| `403` | The key does not belong to the host, or the app is disabled | Use the app's own host or the generic host |
| `413` | The request is over 1 MiB | Send smaller batches |
| `408`, `429`, `500` to `599` | A timeout, throttling, a server error or a temporary outage | Retry the same request later, with exponential backoff and jitter |
| Any other status | Not an answer from Apsio: a redirect, or a proxy with a wrong path (`404`) or asking for credentials (`407`) | Retry with backoff; only `2xx`, `400` and `413` mean the batch is done |

On a retried response, honor `Retry-After` in both of its forms, a number of seconds or an
HTTP date: wait the longer of your backoff and that time, up to 30 minutes. Keep the same
body and `Apsio-Batch-Id` on every retry.

Error bodies are an OTLP `Status` with a `code` and a `message`, in the encoding of the
request.

## The open profile

The rules above are part of the Apsio Mobile Profile for OTLP, an open specification at
[spec.apsio.io](https://spec.apsio.io/v0.1/). It also describes sessions, crashes and the
other conventions the Apsio SDKs follow. Data you send that follows them counts in
[release health](/concepts/sessions) and [issues](/concepts/issues) like an SDK's.


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