---
title: AI Agent Telemetry (OpenTelemetry Setup)
description: Connect Claude Code, Cursor, Gemini CLI, Codex, and other AI coding agents to CodeDD via OpenTelemetry push
category: Portfolio Analytics
order: 1
---

# AI Agent Telemetry (OpenTelemetry Setup)

CodeDD can receive **platform-assisted AI usage telemetry** from coding agents that run on developer machines. This is separate from **git-derived AI authorship** (git-ai notes, co-author trailers, commit markers), which proves what was committed with evidence.

| Signal | What it measures | How it arrives |
|--------|------------------|----------------|
| **Git authorship** | Lines and commits disclosed as AI-assisted in git history | Audit pipeline scans the repository |
| **Platform-assisted telemetry** | Sessions, tokens, lines written in the editor, cost, tool usage | Agents push OpenTelemetry (OTEL) to CodeDD, or CodeDD pulls vendor admin APIs |

Platform-assisted numbers are a **floor**, not a ceiling: they reflect only developers and machines that were configured. Treat them as adoption and activity signals, not proof of what landed in production.

## How CodeDD implements OTEL ingest

### Push model (this guide)

Most coding agents never expose a team-wide admin API. Their telemetry stays on the developer machine until you configure an exporter. CodeDD therefore **issues an ingest endpoint** per portfolio organization:

1. An organization **manager** creates the endpoint in **Organization Settings → AI agent telemetry (OpenTelemetry)**.
2. CodeDD returns:
   - **OTLP base URL** — e.g. `https://api.codedd.ai/api/otel`
   - **Ingest token** — a bearer secret scoped to that organization
3. You paste those values into each agent (or into a collector that forwards on the agent's behalf).
4. Agents push **OTLP/HTTP** on a schedule (typically every 60 seconds).
5. CodeDD decodes, normalizes vendor-specific metrics, and stores daily rollups in PostgreSQL (`AgentUsageDaily` with `ingestion_source = otel_push`).

### Endpoints

| Path | Purpose |
|------|---------|
| `POST /api/otel/v1/metrics` | Primary signal — sessions, tokens, lines, cost |
| `POST /api/otel/v1/logs` | Codex and other agents that report usage as log events |
| `POST /api/otel/v1/traces` | Acknowledged and discarded (prevents exporter retry loops) |

**Token-in-path variant** (for agents that cannot set headers):

| Path | Purpose |
|------|---------|
| `POST /api/otel/k/{token}/v1/metrics` | Same as above; token embedded in URL |
| `POST /api/otel/k/{token}/v1/logs` | Logs with path-embedded token |

### Supported encodings

- **OTLP/HTTP protobuf** (`application/x-protobuf`) — recommended; used by Claude Code and Cursor Enterprise
- **OTLP/HTTP JSON** (`application/json`) — required for Gemini CLI
- **Gzip** request bodies — supported

### What CodeDD stores

Daily aggregates per organization and provider:

- Sessions, engaged users (hashed identities — no emails stored)
- Input / output / cache tokens
- Lines added and removed (where the agent reports them)
- Cost (micro-USD, where reported)
- Tool and model breakdowns (bounded cardinality)

### What CodeDD never stores

The normalizer reads **allow-listed numeric attributes only**. The following are structurally ignored even if an agent sends them:

- Prompt text and chat content
- File paths and repository contents
- Tool arguments and shell commands
- Raw user identifiers (hashed before persistence)

**Important:** Gemini CLI enables prompt logging by default upstream. Always set `logPrompts: false` in Gemini settings. Claude Code snippets disable log export explicitly.

### Authentication

Three ways to send the ingest token:

1. **Authorization header** (preferred): `Authorization: Bearer codedd_otel_…`
2. **Custom header**: `X-CodeDD-Ingest-Token: codedd_otel_…`
3. **URL path** (Gemini CLI): `/api/otel/k/{token}/v1/metrics`

Only organization **managers** can create, reveal, rotate, or pause the ingest token.

---

## Step 1 — Create the ingest endpoint in CodeDD

1. Open your **Portfolio Organization** dashboard.
2. Open **Settings** (gear icon).
3. Scroll to **AI agent telemetry (OpenTelemetry)**.
4. Click **Create telemetry endpoint**.
5. Copy the **OTLP endpoint** and **ingest token** (click **Show** to reveal the token).
6. Choose a provider tab for a pre-filled setup snippet, or follow the sections below.

Status meanings:

| Badge | Meaning |
|-------|---------|
| **Not set up** | No endpoint created yet |
| **Waiting for data** | Endpoint exists but no push received yet — restart agents after configuring |
| **Receiving** | At least one batch accepted |
| **Paused** | Token kept but pushes rejected until resumed |

Full setup reference: use the in-app snippet generator and this page together.

---

## Step 2 — Configure your agent or collector

Choose the path that matches your stack. **An existing OpenTelemetry Collector is the highest-conversion route** — one engineer adds an exporter; no developer machines need changing.

### Existing OpenTelemetry Collector (recommended)

**Who configures:** One platform or observability engineer  
**Per-developer install:** No  
**Best for:** Teams that already run a collector and want to forward agent telemetry to CodeDD

Add an OTLP HTTP exporter to your collector configuration:

```yaml
exporters:
  otlphttp/codedd:
    endpoint: https://api.codedd.ai/api/otel
    headers:
      Authorization: "Bearer YOUR_CODEDD_INGEST_TOKEN"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [otlphttp/codedd]
    logs:
      receivers: [otlp]
      exporters: [otlphttp/codedd]
```

Replace the endpoint and token with the values from Organization Settings. If agents already export to your collector on `localhost:4318`, this is the only change required.

**Local development:** use `http://localhost:8000/api/otel` (Django direct) or `http://localhost:3000/api/otel` (via nginx in Docker Compose).

---

### Claude Code

**Who configures:** IT / platform admin via MDM, or each developer once  
**Per-developer install:** Yes, unless you distribute managed settings centrally  
**Protocol:** OTLP/HTTP protobuf  
**Signals:** Metrics only (logs disabled in CodeDD snippets)

Create **managed settings** on each developer machine (or push via MDM):

| OS | Path |
|----|------|
| macOS | `/Library/Application Support/ClaudeCode/managed-settings.json` |
| Linux | `/etc/claude-code/managed-settings.json` |

```json
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "none",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://api.codedd.ai/api/otel",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer YOUR_CODEDD_INGEST_TOKEN",
    "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "delta",
    "OTEL_METRIC_EXPORT_INTERVAL": "60000"
  }
}
```

**After saving:** restart Claude Code. First data usually arrives within one export interval (~60 seconds).

**Alternative — Anthropic admin API (pull):** CodeDD can also poll Claude Code usage with an organization admin key (no per-developer OTEL). That path uses a separate credential connection and is distinct from OTEL push.

---

### Cursor

**Who configures:** Cursor team admin (Enterprise) or each developer (community hooks)  
**Per-developer install:** Depends on path (see below)

Cursor has **two OTEL-compatible paths** in CodeDD:

#### Path A — Cursor Enterprise native export (team-wide)

Available on **Cursor Enterprise** (OpenTelemetry Export beta). Cursor pushes from **Cursor's servers**, not from each IDE instance.

**Requirements:**

- Cursor Enterprise plan
- **Public HTTPS endpoint** reachable from the internet (Cursor egresses from fixed IPs; `localhost` will not work without a tunnel such as ngrok)
- Organization manager has created the CodeDD ingest endpoint

**Setup:**

1. In **Cursor Team Settings → OpenTelemetry Export**, create a destination.
2. Set the **base URL** to your CodeDD OTLP base (no `/v1/...` suffix — Cursor appends `/v1/metrics` and `/v1/logs` automatically):

   ```
   https://api.codedd.ai/api/otel
   ```

3. Add authorization header:

   ```
   Authorization: Bearer YOUR_CODEDD_INGEST_TOKEN
   ```

4. Run **Test connection**, then **Enable**.

**Notes:**

- Cursor sends OTLP/HTTP **protobuf only** (not JSON).
- Metrics are at-most-once; logs are at-least-once (dedupe on `cursor.event.id` if you mirror elsewhere).
- CodeDD stores sessions, tokens, and tool activity where Cursor's wire format maps to daily rollups.

#### Path B — Forward from your collector

If developers already send Cursor hook or agent telemetry to an internal collector, add the CodeDD exporter (see [Existing collector](#existing-opentelemetry-collector-recommended) above). No Cursor Team Settings change required.

#### Path C — Per-developer community hooks (local development)

For **local CodeDD** (`localhost`) or teams without Enterprise export, community tools (e.g. cursor-otel-hook, cursorscope) can capture IDE hook events and POST OTLP to CodeDD. Each developer installs and points exporters at:

```
http://localhost:8000/api/otel
```

with the ingest token in `OTEL_EXPORTER_OTLP_HEADERS`. This path is **per machine** and requires manual setup unless scripted.

**Alternative — Cursor Team Admin API (pull):** CodeDD can poll Cursor team daily usage with a **team admin API key** (one admin paste, whole team covered, no OTEL). That is a separate integration from OTEL push.

---

### Gemini CLI

**Who configures:** Each developer, or once via a committed `.gemini/settings.json` in a shared repo  
**Per-developer install:** Yes, unless the settings file is shared  
**Protocol:** OTLP/HTTP JSON  
**Limitation:** Cannot set authorization headers — token must travel in the URL path

Edit `~/.gemini/settings.json` (or `.gemini/settings.json` in a repository):

```json
{
  "telemetry": {
    "enabled": true,
    "otlpEndpoint": "https://api.codedd.ai/api/otel/k/YOUR_CODEDD_INGEST_TOKEN",
    "otlpProtocol": "http",
    "logPrompts": false
  }
}
```

**Security:** Treat the full URL as a secret because it embeds the token. Rotate the CodeDD token if the URL leaks into logs.

---

### OpenAI Codex

**Who configures:** Each developer, or one system-wide file via MDM  
**Per-developer install:** Yes, unless `/etc/codex/config.toml` is distributed  
**Protocol:** OTLP/HTTP protobuf on **logs** endpoint  
**Coverage:** Tokens, sessions, tool decisions — **no line counts** (Codex reports usage as log events, not metrics)

Edit `~/.codex/config.toml` or `/etc/codex/config.toml`:

```toml
[otel]
environment = "production"
log_user_prompt = false

[otel.exporter.otlp-http]
endpoint = "https://api.codedd.ai/api/otel/v1/logs"
protocol = "binary"
headers = { "Authorization" = "Bearer YOUR_CODEDD_INGEST_TOKEN" }
```

Restart Codex after saving.

---

### Other agents (OpenTelemetry GenAI conventions)

Agents that emit `gen_ai.*` metrics without a vendor-specific prefix are stored under **Other (OpenTelemetry)**. Configure any OTLP/HTTP exporter to the CodeDD metrics endpoint with bearer authentication. If your agent supports only a collector, use the [collector forwarding](#existing-opentelemetry-collector-recommended) path.

---

## Local development (Docker Compose)

When running CodeDD locally:

| Service | OTLP base URL |
|---------|---------------|
| Django direct | `http://localhost:8000/api/otel` |
| Via nginx (frontend container) | `http://localhost:3000/api/otel` |

Create the ingest endpoint from Organization Settings the same way as production. Use the localhost URL in agent configuration.

**Cursor Enterprise native export** cannot target localhost — use a tunnel, a collector in the cloud, or per-developer hooks for local testing.

---

## Operations

### Rotate token

In Organization Settings → **Rotate token**. This immediately invalidates the old token. Every configured agent and collector must be updated. Rotation is explicit (not automatic on page reload) to avoid silently breaking production integrations.

### Pause collection

**Pause collection** stops accepting pushes while keeping the token and historical data. **Resume collection** re-enables the same token without reconfiguring agents.

### Rate limits

CodeDD applies a per-organization rate limit (default 6,000 requests/minute). Exceeding it returns HTTP 429; OTLP exporters should back off. The OTLP path is exempt from IP-based DDoS throttling because entire teams share office egress IPs.

---

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Status stays **Waiting for data** | Agent not restarted after config | Restart the agent; wait one export interval |
| HTTP 401 Unauthorized | Wrong, rotated, or paused token | Reveal token in settings; check pause state |
| HTTP 400 Bad Request | Malformed OTLP body or wrong content type | Use protobuf for Claude/Cursor; JSON for Gemini |
| HTTP 413 Payload too large | Batch exceeds 2 MB | Reduce batch size in exporter |
| Gemini works but Claude does not | Wrong protocol | Claude requires `http/protobuf`, not JSON |
| Cursor Enterprise test fails | localhost or missing HTTPS | Use public URL or ngrok tunnel |
| Data appears but line counts are zero | Expected for Codex | Codex reports tokens/sessions via logs only |
| Double counts after collector change | Same agent sent twice | One path per agent (direct OR collector, not both) |

---

## Privacy and security summary

- Ingest tokens grant **write access** to organization usage data — manager-only management.
- Tokens are encrypted at rest; lookup uses a SHA-256 hash of the plaintext.
- Developer identities are salted and hashed; emails are not persisted.
- Prompt and source content are never read by the ingest pipeline.
- OTEL push data is labeled **`OpenTelemetry push`** in the UI, separate from **`Vendor admin API`** pull data for the same provider.

---

## Related documentation

- [Setting Up a Portfolio Organization](/documentation/setting-up-a-portfolio-organization)
- [Portfolio Monitoring](/documentation/portfolio-monitoring)
- [Team Members & Access Roles](/documentation/team-members-access-roles)
- [Overview](/documentation/overview)
