Migrate from Helicone · 2026
Helicone and Spanlens share the same fundamental shape: a proxy that you point your provider SDK at. The migration is mostly a base URL swap. The interesting question is what happens to the gateway features Helicone wraps around the proxy. This page covers both.
Why teams switch
- Critical-path tracing built in. Spanlens identifies the longest chain in a multi-step agent so you know which span to optimize. Helicone has the waterfall view but no critical-path marker.
- Prompt versioning and A/B testing. Spanlens ships a statistical A/B comparison for prompt versions out of the box, including significance tests. Helicone tracks prompt versions but does not test them statistically.
- MIT-licensed self-host. Spanlens publishes a Docker image and a single SQL file (
supabase/init.sql). Self-hosting Helicone is more involved. - EU and KR-friendly data residency. Self-host to keep all bodies in your region.
Step 1. Install the Spanlens SDK (optional but recommended)
Helicone is proxy-only by default. Spanlens has the same proxy, plus a small SDK that adds tracing helpers and per-call headers. Both work; pick based on whether you want the extras.
pnpm add @spanlens/sdk
# or skip the SDK and stay with the raw OpenAI client (Step 2 covers that)tsStep 2. Swap the base URL
Helicone routes through https://ai-gateway.helicone.ai (or the olderhttps://oai.helicone.ai/v1). Spanlens routes through https://server.spanlens.io/proxy/<provider>. The auth header stays the same shape; only the key value changes.
OpenAI
Before (Helicone):
import OpenAI from 'openai'
const openai = new OpenAI({
baseURL: 'https://ai-gateway.helicone.ai',
apiKey: process.env.HELICONE_API_KEY,
// Some setups also pass:
// defaultHeaders: { 'Helicone-Auth': 'Bearer ' + process.env.HELICONE_API_KEY },
})tsAfter (Spanlens, raw OpenAI client):
import OpenAI from 'openai'
const openai = new OpenAI({
baseURL: 'https://server.spanlens.io/proxy/openai/v1',
apiKey: process.env.SPANLENS_API_KEY,
})tsAfter (Spanlens, SDK helper):
import { createOpenAI } from '@spanlens/sdk/openai'
const openai = createOpenAI() // reads SPANLENS_API_KEY from envtsAnthropic, Gemini, Azure OpenAI
Different base path per provider. The dashboard shows the exact URL after you register a provider key.
OpenAI : https://server.spanlens.io/proxy/openai/v1
Anthropic : https://server.spanlens.io/proxy/anthropic
Gemini : https://server.spanlens.io/proxy/gemini/v1beta
Azure : https://server.spanlens.io/proxy/azuretextStep 3. Replace Helicone headers with Spanlens equivalents
Helicone uses Helicone-* headers for in-band metadata; Spanlens usesx-spanlens-*. Direct mapping:
| Helicone header | Spanlens equivalent | SDK helper |
|---|---|---|
Helicone-User-Id | x-spanlens-user | withUser(id) |
Helicone-Session-Id | x-spanlens-session | withSession(id) |
Helicone-Property-* (custom) | part of x-spanlens-prompt-version + future custom tags | see notes below |
Helicone-Prompt-Id | x-spanlens-prompt-version | withPromptVersion('name@version') |
Helicone-Cache-Enabled | not built-in; see Gateway features below | n/a |
Helicone-RateLimit-Policy | not built-in; see Gateway features below | n/a |
Spanlens projects also accept arbitrary key/value pairs on traces and spans through the metadata field on createTrace() / span.end({ metadata }). Use that for anything you tagged with Helicone-Property-*.
Setting headers with the SDK helpers
import { createOpenAI, withUser, withSession, withPromptVersion } from '@spanlens/sdk/openai'
const openai = createOpenAI()
const res = await openai.chat.completions.create(
{ model: 'gpt-4o-mini', messages: [...] },
{
headers: {
...withUser(currentUser.id).headers,
...withSession(currentSession.id).headers,
...withPromptVersion('chatbot-system@3').headers,
},
},
)tsStep 4. Register your provider key in the dashboard
Helicone takes your OpenAI key directly in the client (you pass both keys: yours and Helicone's). Spanlens does the opposite: you only pass your Spanlens key; your real provider key is registered once in the dashboard and stays server-side.
- Open /projects
- Click + Add provider key on the project card
- Paste your real
sk-...key, label it, save - Delete the provider key from your application's env file
The key is encrypted with AES-256-GCM before storage; only the proxy can decrypt it in memory at request time. Detail in Keys & encryption.
Gateway features, and what to do about them
Helicone bundles features Spanlens deliberately leaves to upstream tools. If you depend on these, plan ahead:
| Helicone feature | Spanlens equivalent |
|---|---|
| Edge caching | Cache at your app layer (Vercel KV, Redis). Spanlens logs cache_read_tokens / cache_write_tokens when the provider reports them (OpenAI prompt cache, Anthropic cache control). |
| Rate limiting | Use your edge/API gateway (Vercel, Cloudflare, Kong). Spanlens captures rate-limit response codes in status_code so dashboards still surface them. |
| Retries on 5xx | Most provider SDKs retry by default. The OpenAI SDK retries up to 2 times; Anthropic 2 times. Tune at the SDK level rather than the proxy. |
| Provider fallback (gpt-4o → claude-haiku-4-5 on failure) | Not built-in. Pattern: catch in your app code, swap clients (createOpenAI() ↔ createAnthropic()). Both share the same Spanlens key. |
This is a deliberate scope decision. Putting retry/cache/rate-limit inside the observability proxy makes the proxy a single point of failure for application behaviour. Spanlens prefers to be a sidecar you can disable without breaking your app.
Step 5. Dual-run during the cutover
Easiest cutover: change the base URL in one service, leave the rest pointing at Helicone, watch both dashboards for a day, then ramp.
// Read from env so you can flip per environment
const baseURL = process.env.LLM_PROXY === 'spanlens'
? 'https://server.spanlens.io/proxy/openai/v1'
: 'https://ai-gateway.helicone.ai'
const apiKey = process.env.LLM_PROXY === 'spanlens'
? process.env.SPANLENS_API_KEY
: process.env.HELICONE_API_KEY
const openai = new OpenAI({ baseURL, apiKey })tsVerify the cutover
- Send one call from the migrated service.
- Open /requests. The request appears within seconds, with model, tokens, cost, latency.
- Confirm a user-tagged request shows up in /users aggregated under the right
user_id. - Compare daily cost against Helicone for one billing day. They should match within 1% (any larger gap is usually a model name that needs seeding in our price table).
Next: Direct proxy reference for headers and auth, or data model for the full schema.