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://api.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://api.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://api.spanlens.io/proxy/openai/v1
Anthropic : https://api.spanlens.io/proxy/anthropic
Gemini    : https://api.spanlens.io/proxy/gemini/v1beta
Azure     : https://api.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://api.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. For the background on why teams are moving and what to check before picking any replacement, see Helicone was acquired by Mintlify: a migration checklist.