Ingestion Templates
Status: v1, push-mode templates only. OAuth-redirect and pull-mode (S3, API) shapes are reserved for a future iteration.LangWatch’s Ingestion Templates let you connect tool-specific telemetry sources (Claude Code on Anthropic 20x, Cursor, Claude cowork, …) to your personal workspace without LangWatch ever participating in the upstream tool’s authentication. You install a template, paste an ingestion key into your tool’s OTLP exporter, and traces land at
/me/traces already shaped into the canonical gen_ai.* form, cost,
tokens, and model populated automatically. An ingestion key is just an
API key (sk-lw-…) scoped to one project with a write-only,
ingest-only role: it can create traces and nothing else, so it’s safe to spray into an
agent’s environment.
Why ingestion templates exist
If your tool has a LangWatch virtual key, the AI Gateway proxies the request and we ingest canonical traces from the gateway response. Done. But many tools don’t fit that path:- Subscription-bound tools: Claude Code on Anthropic 20x, GitHub Copilot, etc. The user has no API key the gateway can proxy through; the tool authenticates client-side with the upstream provider.
- Native-OTLP tools: Cursor, Claude cowork, Continue.dev, Aider, Cline. They already emit OpenTelemetry traces locally; what they need is a place to ship them.
langwatch.source provenance and lands in the right personal project.
That’s an Ingestion Template:
- Admin/platform side: a curated catalog row (slug, source identifier, credential
schema, optional
ottlRules). Platform-published rows ship emptyottlRules— Claude Code, Cursor, and Claude cowork all emit OpenTelemetrygen_ai.*canonical spans natively, so no transform is needed for the common case. Org admins can clone a platform-default template into an org-authored template and author OTTL via the OttlEditor at/settings/governance/tool-catalog → Ingestion Templates → Clone to customise → Edit OTTL, to adapt a template to their org’s upstream-tool quirks (an internal IDE wrapper, a custom Cursor build, etc). Platform rows are read-only — admins click Clone to customise which callsingestionTemplates.cloneFromPlatform, then Edit OTTL on the new org-authored row to refine the rules. - User side: an ingestion key (
sk-lw-…) scoped to your personal project with a write-only, ingest-only role. You paste that key into your tool’s OTLP exporter; the tool ships, the receiver verifies the key, ceilings it to ingest-only, lands the trace in the key’s bound project, and applies the referenced template’s OTTL (empty for platform defaults, admin-authored for org forks) when the key carries atemplateId.
Worked example: connect Claude Code on Anthropic 20x
This walks through the whole flow end-to-end. By the end you’ll have a Claude Code trace visible at/me/traces with gen_ai.usage.*, gen_ai.response.model, and
langwatch.cost.usd populated.
Before you start: you need a LangWatch account. If you’re brand new, start with the
getting started guide and then sign in to
LangWatch. First-time sign-in completes onboarding and lands you on your personal
workspace (/me). On self-hosted instances the same path works against your own
deployment’s host.
Step 1: open the catalog
Navigate to/me and scroll to the Trace Ingest section (or jump straight to
/me#trace-ingest).

Step 2: install the Claude Code template
Click Install on the Claude Code tile. A drawer slides in headed Connect Claude Code, auto-shaped, with the subcopy “Traces normalized into gen_ai.* canonical. Cost/tokens/model populated automatically by the receiver.”
- OTLP endpoint: read-only, copyable.
- Ingestion key:
sk-lw-…issued one-time, with a Show/Hide toggle. Copy it while it’s visible; after you dismiss the drawer it’s masked to its first characters. - Snippet preview: pre-wired environment variables for Claude Code:
(Self-hosted: replace the host with your LangWatch base URL. The drawer always shows the correct endpoint for your instance.)
The key is shown one-time. If you lose it, rotate the ingestion key from the tile to
mint a new one. The previous key is revoked immediately when you rotate (hard-cut v1).
Step 3: fire your first Claude Code action
Open a new terminal so the env vars take effect, then run Claude Code as you normally would. As soon as Claude Code completes its first action, OpenTelemetry exports the span to your LangWatch personal workspace.Step 4: see the trace at /me/traces
Click View traces → on the tile, or navigate directly to/me/traces.

gen_ai.system = "anthropic"gen_ai.request.model = "claude-3-5-sonnet"(or whichever model you ran)gen_ai.usage.input_tokens, emitted directly by Claude Code’s OTel exporter (Claude Code is gen_ai-canonical natively; v1 templates apply no OTTL transform).gen_ai.usage.output_tokens, same.langwatch.cost.usd, derived by the receiver’scanonicalCostExtractorfrom the upstream-emittedgen_ai.usage.*+gen_ai.response.model.langwatch.user.id,langwatch.project.id,langwatch.template.id,langwatch.api_key.id,langwatch.source = "claude_code", all receiver-stamped post-auth (authoritative attribution + template provenance), withlangwatch.origin = "coding_agent"(derived from the source: a CLI coding assistant becomescoding_agent, any other ingest source becomesai_tool).
Under the hood
This section is for engineering leads, security reviewers who want to understand the trust model and the request flow. End users can skip ahead to Raw OTLP fallback.Anatomy of an Ingestion Template
AnIngestionTemplate row has:
slug(e.g.claude_code), referenced by an ingestion key via itstemplateId.sourceType(e.g.claude_code), which upstream tool this template parses.credentialSchema(closed enum, v1:null,static_api_key,agent_id), what fields the install drawer captures from the user.ottlRules, an optional OTTL transform applied to the span before the receiver’s authoritative re-stamp pass. Platform-default templates ship emptyottlRulesbecause Claude Code, Cursor, and Claude cowork all emit OpenTelemetrygen_ai.*canonical spans natively, there is nothing to reshape for the common case. Org admins can fork a platform-default template into an org-authored template and author OTTL via the OttlEditor in/settings/governance/tool-catalog → Ingestion Templates → Edit OTTL. Forking lets the admin adapt a template to their org’s upstream-tool quirks (an internal IDE wrapper, a custom Cursor build, a corporate Claude Code variant) without filing a request for a platform change.organizationId,NULLfor platform-published templates (visible everywhere); set for org-authored templates (visible only to that org). Forks created via “Edit OTTL” land here.
credentialSchema shapes
oauth_redirect and s3_bucket are reserved for a future iteration, those require
upstream OAuth refresh, polling, watermark infrastructure that isn’t in the v1 push-only
shape.
Receiver resolution flow
When your tool emits an OTLP payload withAuthorization: Bearer sk-lw-<key>, the
receiver:
- Prefix-discriminates the bearer,
sk-lw-*→ ApiKey verification path. - Verifies the key (HMAC + pepper) and resolves the
ApiKeyrow, the same path any SDK or management key takes. - Ceilings to ingest-only. An ingestion key carries a write-only, ingest-only role
that grants
traces:createand nothing else, so the receiver rejects any non-ingest operation regardless of what the payload claims. - Sets
tenantIdto the key’s bound project, credential-as-scope. This is authoritative. - Applies
template.ottlRulesunder a snapshot+restore principal-field guard, but only when the ingestion key carries atemplateId. For platform-default templates the rules are empty so this step is a no-op; for org-authored forks the admin’s OTTL runs here under the protection lists below. A key with notemplateIdskips OTTL entirely and lands raw. - Post-auth re-stamps the closed
protectedTemplateAttributeKeys(and, for org-authored OTTL, the tier-of-trust additions covering cost, tokens, model) as receiver-authoritative, includinglangwatch.api_key.idandlangwatch.originprovenance (derived from the key’s source:coding_agentfor a CLI assistant,ai_toolotherwise). If the OTTL tried to write any of those keys, the original value is restored and an audit rowgateway.template_ottl_protected_field_attemptfires with the rejected-key list. - Hands off to the trace pipeline, same path as every other ingest source from here.
What the principal-field guard protects
TheprotectedTemplateAttributeKeys closed list:
- The
protectedAttributeKeysfrom the base OTTL guard (B6), all attribution shapes:langwatch.user.id,langwatch.team.id,langwatch.organization.id,langwatch.project.id,langwatch.tenant.id, virtual-key shapes, ingestion-source shapes, governance shapes. langwatch.template.id, receiver-stamped provenance.langwatch.api_key.id, receiver-stamped provenance (the ingestion key that landed the trace).langwatch.origin, receiver-stamped provenance (coding_agentfor a CLI assistant,ai_toolfor any other ingest source).langwatch.source, receiver-stamped provenance (used for tile filtering at/me/traces).
langwatch.cost.usd,.input,.outputgen_ai.usage.input_tokens,.output_tokens,.total_tokensgen_ai.response.model
canonicalCostExtractor reading upstream-emitted gen_ai.usage.*). Platform-default
templates ship empty ottlRules, so the protected list is dormant on this path.
For org-authored templates (forks created via the OttlEditor) the receiver
applies a tier-of-trust addition: the org-authored OTTL is allowed to reshape
upstream-specific attrs into the canonical keys (that’s the template’s whole job),
but langwatch.cost.usd*, gen_ai.usage.*, and gen_ai.response.model are pinned
to receiver-authoritative values via canonicalCostExtractor to prevent within-org
cost-attribution forgery.
See Security model recap below for the per-tier trust
boundary articulation.
Raw OTLP fallback
If you have a custom telemetry pipeline, your own spans, your own normalisation, your own cost calculations, you don’t want a template’s OTTL rewriting your shape. Use the Raw OTLP (advanced) card on/me Trace Ingest instead.
The card deep-links to the Personal OTLP Endpoint panel at /me/settings#otlp,
which surfaces your personal-project’s existing OTLP endpoint and API key for ad-hoc /
custom telemetry. No template OTTL is applied; your spans land at /me/traces exactly
as you emit them.

When to choose Raw OTLP vs a template
You can also surface the same endpoint + token from
/me/settings → Personal OTLP Endpoint
without going through the catalog tile.
Security model recap
For security reviewers evaluating the feature.Trust boundary
- Credential = scope. The
sk-lw-*ingestion key authoritatively determines which project a trace lands in, and its write-only ingest-only role caps it totraces:create. Payload-level claims about user, team, org, project attribution are advisory and re-stamped post-OTTL. - Cross-project guard at mint is structural: the install drawer (and the
langwatch <tool>CLI) mints the key against the user’s resolved personal project; the caller never picks the target project. Minting a key into someone else’s project is unrepresentable. - Cross-project guard at receive is the ingest-only ceiling plus the post-OTTL
principal-field re-stamp. Forge attempts emit
gateway.template_ottl_protected_field_attemptaudit rows.
Trust boundary: two tiers
LangWatch v1 ships two distinct template tiers with different trust contracts: Tier 1, Platform-default templates (organizationId IS NULL). Empty ottlRules.
Cost, tokens, model values come directly from the upstream tool’s OTel exporter
(Claude Code, Cursor, and Claude cowork all emit gen_ai.* canonical natively), and the
receiver derives langwatch.cost.usd from those upstream-emitted values via
canonicalCostExtractor. Trust here rides on upstream-tool correctness: we trust
Claude Code, Cursor, and Claude cowork to populate gen_ai.usage.* correctly per the
OTel semantic-conventions spec.
Tier 2, Org-authored templates (organizationId NOT NULL, forked from a tier-1
template via the OttlEditor at /settings/governance/tool-catalog → Ingestion Templates → Edit OTTL). Admins author OTTL to adapt a template to their org’s
upstream-tool quirks. Trust here rides on org-admin review at save time: the
admin who clicks “Save & validate” is accepting within-org responsibility for the
transform’s correctness. Three protection layers prevent admin OTTL from being
weaponized against the org or other orgs:
- The
protectedTemplateAttributeKeysguard at receive time (attribution + provenance, admin OTTL cannot rewrite who/where/which-template a trace came from). - A tier-2 superset that adds
langwatch.cost.usd*,gen_ai.usage.*, andgen_ai.response.modelto the protected set so admin OTTL cannot forge cost- attribution within the org. - Static analysis at save time, the OttlEditor’s “Save & validate” step rejects OTTL that would write any protected key BEFORE the rules are persisted, with an inline error pointing at the offending line.
scripts/dogfood/emit-otlp.sh, including forge-attempt regressions against both the
base and tier-2 protected lists, and (b) a real-user track, admin publishes via
/settings/governance/tool-catalog, a real user installs via /me, the real upstream
tool emits a real trace, and the result is verified end-to-end with cross-user isolation.
Per-template rituals live at langwatch/ee/governance/ingestion-templates/<slug>/dogfood.md.
What is audited
State-change rows only, per-trace activity is tracked via the ingestion key’slastUsedAt, not via audit volume.
All state-change rows mirror to the
governance_ocsf_events ClickHouse table for
SOC2, ISO27001 evidence collection.
What is not audited
- Per-trace landings (volume reasons; the ingestion key’s
lastUsedAtcarries the activity signal) - Key rotation content (the new key’s minted/revoked lifecycle is the audit signal)
- Credential-content edits on
static_api_key,agent_idtemplates (PII boundary, matches the existing API-key edit pattern)
Frequently asked questions
Does LangWatch see my Anthropic OAuth token?
No. Claude Code holds your Anthropic OAuth session locally; LangWatch never participates in that flow. The only credential LangWatch holds is the ingestion key (sk-lw-…) we
mint for you, which is scoped to your personal project and write-only.
What happens if I rotate the ingestion key?
Hard-cut v1: the previous key is revoked immediately. Update your tool’sOTEL_EXPORTER_OTLP_HEADERS with the new key before continuing. (A grace-period
rotation lands in a future iteration if SOC2 review requests.)
Can my org admin see my personal-workspace traces?
No, not via this feature. Personal-project traces are scoped to your personal project; the admin’s drill-in path (governance dashboards) requires the explicit drill-in flow described in the governance dashboard docs and is logged + persistent-banner’d. Templates do not change that.What if my tool isn’t in the catalog?
For this iteration we ship 3 tool-specific templates (Claude Code, Cursor, Claude cowork) plus the Raw OTLP (advanced) fallback. If your tool emits OTLP natively, the Raw OTLP fallback works today. If you’d like a custom template for another tool, file a request on GitHub Issues and we’ll evaluate.Verifying the contract
The full admin OTTL authoring flow has a Playwright real-user dogfood atlangwatch/e2e/admin-ottl-dogfood.ts (~30s loop), 8 steps end-to-end:
navigate → list 3 platform rows → View OTTL drawer → Clone-to-customise →
Save OTTL → list 4 rows → Edit OTTL → Archive. Pairs with the
service-level integration test at
langwatch/ee/governance/services/__tests__/ingestionTemplate.authoring.integration.test.ts
and the wire-level REST integration test at
langwatch/src/app/api/governance/__tests__/governance-rest-api.integration.test.ts.
If you’re touching the IngestionTemplatesEditor surface, the OttlEditor
component, or the IngestionTemplateService, run the dogfood + both
integration tests locally as a regression playbook before opening a PR.
Related
- Personal Workspace: broader personal-workspace feature surface (Datasets, Evaluations, Annotations, Automations).
- Personas: how admin drill-in works (and why it doesn’t see personal-workspace traces by default).
- Governance REST API: the same IngestionTemplate surface, exposed over Hono REST/JSON for agentic workflows.
specs/ai-gateway/governance/ingestion-templates-catalog.featureand siblings, the BDD specs that lock this feature’s behaviour.