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

# Send data with OpenTelemetry

> The OTLP endpoints, the authentication header, and a first request you can verify — for logs, metrics and traces, over HTTP and gRPC.

aiAxonIQ speaks standard OTLP. Any OpenTelemetry SDK or the OpenTelemetry
Collector can export to it without an aiAxonIQ-specific plugin.

<Info>
  **Before you start.** You need [a license key](/get-started/license-keys), and
  either a service instrumented with OpenTelemetry or `curl` to send a test
  payload.
</Info>

## Endpoints

The receiver exposes one path per signal over OTLP/HTTP:

* `POST /v1/logs`
* `POST /v1/metrics`
* `POST /v1/traces`

These are the standard OTLP/HTTP paths, so an SDK configured with a base
endpoint appends the right one automatically.

| Deployment                               | Base endpoint                    |
| :--------------------------------------- | :------------------------------- |
| **aiAxonIQ Cloud**                       | `https://app.aiaxoniq.com/otlp`  |
| **Self-hosted** behind the bundled nginx | `https://app.<your-domain>/otlp` |

Both forms carry the `/otlp` prefix because nginx serves OTLP under it and
strips it before forwarding, so the receiver still sees `/v1/logs`. Dropping the
prefix is the most common setup mistake: the request reaches the dashboard
instead of the receiver and comes back as an HTML 404 rather than an ingest
error.

<Info>
  **Get Started** in the dashboard shows the exact base endpoint for your
  deployment next to a license key you create there, with copy buttons, and
  then watches for your first records. Where these pages write
  `$OIQ_ENDPOINT`, that page has the real value.
</Info>

```bash theme={null}
export OIQ_ENDPOINT="<the base endpoint from Get Started>"
export OTEL_EXPORTER_OTLP_ENDPOINT="$OIQ_ENDPOINT"
```

## Authenticate

Every ingest request needs your license key in the `X-License-Key` header. For
SDKs, set it through the standard headers variable:

```bash theme={null}
export OTEL_EXPORTER_OTLP_ENDPOINT="$OIQ_ENDPOINT"
export OTEL_EXPORTER_OTLP_HEADERS="X-License-Key=oiq_4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8e"
export OTEL_SERVICE_NAME="checkout"
```

`OTEL_SERVICE_NAME` is not required by the receiver, but set it anyway — without
it every signal arrives attributed to an unknown service and the service-level
views have nothing to group by.

## Send a first request

The fastest way to prove the path end to end, before touching your application's
instrumentation, is a single OTLP/JSON log record.

<CodeGroup>
  ```bash curl theme={null}
  curl -i "$OIQ_ENDPOINT/v1/logs" \
    -H "Content-Type: application/json" \
    -H "X-License-Key: $OIQ_LICENSE_KEY" \
    -d '{
      "resourceLogs": [{
        "resource": { "attributes": [
          { "key": "service.name", "value": { "stringValue": "docs-smoke-test" } }
        ]},
        "scopeLogs": [{
          "logRecords": [{
            "timeUnixNano": "'"$(date +%s)000000000"'",
            "severityText": "INFO",
            "body": { "stringValue": "hello from the aiAxonIQ docs" }
          }]
        }]
      }]
    }'
  ```

  ```javascript Node.js theme={null}
  // Standard OpenTelemetry setup — nothing aiAxonIQ-specific.
  // npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node \
  //             @opentelemetry/exporter-trace-otlp-http

  const { NodeSDK } = require('@opentelemetry/sdk-node');
  const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');

  const sdk = new NodeSDK({
    traceExporter: new OTLPTraceExporter({
      url: `${process.env.OIQ_ENDPOINT}/v1/traces`,
      headers: { 'X-License-Key': process.env.OIQ_LICENSE_KEY },
    }),
    instrumentations: [getNodeAutoInstrumentations()],
  });

  sdk.start();
  ```

  ```yaml Collector theme={null}
  # Point an existing OpenTelemetry Collector at aiAxonIQ by adding an exporter.
  exporters:
    otlphttp/aiaxoniq:
      endpoint: ${OIQ_ENDPOINT}
      headers:
        X-License-Key: ${OIQ_LICENSE_KEY}
      compression: gzip

  service:
    pipelines:
      traces:
        exporters: [otlphttp/aiaxoniq]
      metrics:
        exporters: [otlphttp/aiaxoniq]
      logs:
        exporters: [otlphttp/aiaxoniq]
  ```
</CodeGroup>

A successful request returns **202 Accepted**. That means the batch was
published to the pipeline; it becomes queryable a few seconds later. If you get
anything else, [the error reference](/send-data/endpoints#status-codes) lists
every status the receiver returns and what causes it.

## Encodings and compression

The receiver accepts both OTLP encodings over HTTP:

* `application/json`
* `application/x-protobuf` — also accepted as `application/octet-stream`

Request bodies may be compressed with **gzip**, **deflate** or **brotli** via
`Content-Encoding`. OpenTelemetry SDKs default to gzip, which works without
configuration.

## OTLP over gRPC

<Warning>
  **gRPC ingest is not available on aiAxonIQ Cloud.** The hosted deployment
  serves OTLP over HTTP only; port `4317` is not exposed, and a collector
  configured for gRPC will fail to connect.

  This is why **Get Started** shows no gRPC endpoint on a hosted account — the
  product omits the option rather than advertising an address that refuses
  connections. If you are looking for a gRPC endpoint and cannot find one, it is
  absent on purpose. Use HTTP.
</Warning>

The receiver itself does implement OTLP over gRPC on port **4317**, registering
the standard logs, metrics and trace services. It is reachable only where an
operator has deliberately exposed it — that is, on a **self-hosted** deployment
whose reverse proxy has been configured to carry gRPC.

Where it is available, it takes **the same license key as HTTP** — a key created
in **Settings → License Keys** works on both transports:

```bash theme={null}
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
export OTEL_EXPORTER_OTLP_ENDPOINT="$OIQ_GRPC_ENDPOINT"
export OTEL_EXPORTER_OTLP_HEADERS="x-license-key=$OIQ_LICENSE_KEY"
```

gRPC metadata keys are lower-case by protocol, so `x-license-key` rather than
`X-License-Key`. Everything else — rate limits, plan quotas, per-signal
entitlements — is enforced identically on both transports.

The gRPC base endpoint is always a separate value from the HTTP one, because a
reverse proxy terminating HTTP/1.1 cannot also carry gRPC on the same listener.
Where a deployment serves both, **Get Started** shows both.

<Tip>
  **HTTP is not a downgrade.** OTLP/HTTP with protobuf encoding and gzip
  compression — the default in current SDKs — carries the same data with
  comparable efficiency. gRPC is a deployment convenience, not a capability
  difference.
</Tip>

## Confirm it arrived

Two checks, in order:

1. **Did the receiver accept it?** A `202` response means yes. A `401` means the
   key was missing, malformed or revoked; a `400` means the payload did not
   decode.
2. **Did it land?** Open the Logs Explorer and search for the service name you
   sent — for the curl example above, `service:docs-smoke-test`. See
   [Searching logs](/guides/logs/search) for the query syntax.

If the receiver returned `202` and nothing appears after a minute, the problem
is downstream of ingest rather than in your instrumentation, which is a useful
thing to know before you start rereading your exporter config.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The collector starts, then logs 'Exporting failed. Will retry'" icon="arrows-rotate">
    Read the `error` field on that line — it names the cause exactly.

    ```text theme={null}
    Exporting failed. Will retry the request after interval.
      {"kind": "exporter", "name": "otlphttp/aiaxoniq",
       "error": "failed to make an HTTP request: Post \"…/v1/metrics\":
                 dial tcp: lookup … : no such host", "interval": "5.2s"}
    ```

    * `no such host` — the endpoint hostname does not resolve. Check
      `$OIQ_ENDPOINT` against the value on **Get Started**.
    * `connection refused` — the host resolves but nothing is listening on that
      port.
    * `401 Unauthorized` — the key is missing, malformed or revoked.
    * `404` — the endpoint already ends in `/v1/…`. It must be the **base** URL;
      the collector appends the signal path itself.

    The collector retries with backoff and does not drop data while it retries,
    so a transient failure here is not a loss.
  </Accordion>

  <Accordion title="Nothing at all in the logs after 'Everything is ready'" icon="ear-listen">
    The collector is running and receiving nothing. That is an application-side
    problem, not a collector one — your services are not exporting to it.

    Check that your application's `OTEL_EXPORTER_OTLP_ENDPOINT` points at the
    collector's OTLP port (`4318` for HTTP, `4317` for gRPC), not at aiAxonIQ.
  </Accordion>

  <Accordion title="401 with a key you know is correct" icon="key">
    Two causes that are not about the key's value:

    * **A trailing newline.** A key read from a file created by a shell heredoc,
      or a Kubernetes Secret made with `--from-file`, carries the newline as
      part of the value. Use `--from-literal`, or `printf` rather than `echo`.
    * **A validation outage.** If the receiver cannot reach the service that
      validates keys it fails closed and returns the same `401`. A sudden `401`
      across every service at once, with a key you have not changed, is far more
      likely to be this. Check `$OIQ_ENDPOINT/health` first.
  </Accordion>

  <Accordion title="Config changes appear to do nothing" icon="file-pen">
    The collector reads its configuration only at startup. Restart it after any
    edit, and confirm the file you edited is the one mounted into the process —
    a bind mount pointing at a path that does not exist silently yields the
    image's default config rather than an error.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={3}>
  <Card title="Instrument your application" icon="code" href="/send-data/otel/zero-code">
    Zero-code setup, per language.
  </Card>

  <Card title="Verify your data" icon="circle-check" href="/get-started/verify-data">
    Confirm it landed.
  </Card>

  <Card title="Endpoints and errors" icon="list" href="/send-data/endpoints">
    Every status code and limit.
  </Card>
</CardGroup>
