Multitenancy
Candela supports first-class multitenant observability: every LLM API call can be attributed to a downstream customer (tenant), enabling per-tenant cost tracking and billing.
How It Works
Section titled “How It Works” Your App (ADK agent / FastAPI / etc.) │ │ Baggage: candela.tenant_id=acme-corp │ X-Candela-Tenant-Id: acme-corp ▼ Candela Proxy ────────────────────────── │ Extracts tenant_id → validates → attaches to Span ▼ Storage (DuckDB / SQLite / BigQuery) │ tenant_id column indexed for GROUP BY queries ▼ GetTenantLeaderboard RPC → Dashboard / BillingPropagation Methods
Section titled “Propagation Methods”Option 1: W3C Baggage (Recommended)
Section titled “Option 1: W3C Baggage (Recommended)”The W3C Baggage header carries key-value pairs through distributed traces. If you set candela.tenant_id in baggage at the start of an agent run, it flows through every downstream LLM call automatically — even in multi-hop traces.
Baggage: candela.tenant_id=acme-corp,candela.job_id=eval-42Baggage takes precedence over the explicit header when both are present.
Option 2: Explicit Header
Section titled “Option 2: Explicit Header”For non-OTel callers (curl, direct API clients):
X-Candela-Tenant-Id: acme-corpX-Candela-Job-Id: eval-42Option 3: Enrichment SDKs (Easiest)
Section titled “Option 3: Enrichment SDKs (Easiest)”The Enrichment SDKs set both Baggage and explicit headers automatically:
from candela import CandelaSession
session = CandelaSession(tenant_id="acme-corp", job_id="eval-42")client = OpenAI( base_url="http://localhost:1234/v1", http_client=session.httpx_client(),)Tenant ID Format
Section titled “Tenant ID Format”IDs must match [a-zA-Z0-9\-._]{1,128}:
| ✅ Valid | ❌ Invalid |
|---|---|
acme-corp | acme corp (space) |
tenant_42 | acme@corp (@ sign) |
trial-NCT01750580 | “ (empty) |
customer.a.b.c | ../escape (path traversal) |
Invalid values are silently discarded — the span is written without a tenant_id. This avoids breaking clients that send malformed headers.
Multi-Hop Trace Propagation
Section titled “Multi-Hop Trace Propagation”With Baggage, tenant context propagates automatically through multi-agent pipelines:
FastAPI handler └─ Orchestrator agent (Gemini call ①) ← tenant_id set here └─ Tool: query_data └─ Sub-agent (Gemini call ②) ← auto-propagated via Baggage └─ Tool: find_evidence └─ Sub-agent (Gemini call ③) ← still propagatedWith just headers, you’d need to manually re-attach at every hop. Use Baggage for anything with nested LLM calls.
Querying Tenant Data
Section titled “Querying Tenant Data”ConnectRPC: GetTenantLeaderboard
Section titled “ConnectRPC: GetTenantLeaderboard”buf curl --protocol connect \ https://candela.example.com/candela.v1.DashboardService/GetTenantLeaderboard \ -d '{ "project_id": "my-project", "limit": 10, "time_range": { "start": "2026-01-01T00:00:00Z", "end": "2026-02-01T00:00:00Z" } }'Example output:
acme-corp: $12.45 (342 calls)trial-NCT01234: $7.23 (198 calls)azra-health-demo: $3.11 (89 calls)This endpoint is admin-only.
Direct SQL (DuckDB / BigQuery)
Section titled “Direct SQL (DuckDB / BigQuery)”SELECT COALESCE(tenant_id, '(unattributed)') AS tenant, COUNT(*) AS calls, SUM(gen_ai_input_tokens) AS input_tokens, SUM(gen_ai_output_tokens) AS output_tokens, SUM(gen_ai_cost_usd) AS cost_usdFROM spansWHERE start_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)GROUP BY 1ORDER BY cost_usd DESC;OTLP Export
Section titled “OTLP Export”When the OTLP sink is enabled, tenant_id and job_id are exported as span attributes:
| Attribute | Surfaces In |
|---|---|
candela.tenant_id | Datadog (@candela.tenant_id), Honeycomb, Grafana/Tempo |
candela.job_id | Same — filterable in all OTel-compatible backends |
Security Considerations
Section titled “Security Considerations”tenant_id is an attribution label, not an access control boundary. It’s set by the calling application, not by Candela’s auth system. Enforce tenant authorization in your application layer.
- The
GetTenantLeaderboardAPI is admin-only - Tenant IDs are validated against
[a-zA-Z0-9\-._]{1,128}to prevent injection attacks - The proxy strips
X-Candela-*headers before forwarding to upstream LLMs — tenant metadata never leaks to providers
Related
Section titled “Related”- Enrichment SDKs — Lightweight SDKs for injecting tenant/job metadata
- ADK Integration — Using multitenancy with Google ADK agents
- Pricing & Cost Calculator — How costs are calculated per-tenant