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)
ts

Step 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 },
})
ts

After (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,
})
ts

After (Spanlens, SDK helper):

import { createOpenAI } from '@spanlens/sdk/openai'

const openai = createOpenAI() // reads SPANLENS_API_KEY from env
ts

Anthropic, 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/azure
text

Step 3. Replace Helicone headers with Spanlens equivalents

Helicone uses Helicone-* headers for in-band metadata; Spanlens usesx-spanlens-*. Direct mapping:

Helicone headerSpanlens equivalentSDK helper
Helicone-User-Idx-spanlens-userwithUser(id)
Helicone-Session-Idx-spanlens-sessionwithSession(id)
Helicone-Property-* (custom)part of x-spanlens-prompt-version + future custom tagssee notes below
Helicone-Prompt-Idx-spanlens-prompt-versionwithPromptVersion('name@version')
Helicone-Cache-Enablednot built-in; see Gateway features belown/a
Helicone-RateLimit-Policynot built-in; see Gateway features belown/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,
    },
  },
)
ts

Step 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.

  1. Open /projects
  2. Click + Add provider key on the project card
  3. Paste your real sk-... key, label it, save
  4. 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 featureSpanlens equivalent
Edge cachingCache 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 limitingUse your edge/API gateway (Vercel, Cloudflare, Kong). Spanlens captures rate-limit response codes in status_code so dashboards still surface them.
Retries on 5xxMost 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 })
ts

Verify the cutover

  1. Send one call from the migrated service.
  2. Open /requests. The request appears within seconds, with model, tokens, cost, latency.
  3. Confirm a user-tagged request shows up in /users aggregated under the right user_id.
  4. 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.