Coming from the Langfuse SDK
Tracepad accepts traces from the Langfuse SDK on the route the SDK already
uses, POST /api/public/otel/v1/traces. An application that sends to Langfuse
today sends to Tracepad by changing its base URL and its keys. The code stays
the same.
What moves across is everything the SDK exports: traces, observations,
payloads, usage, cost, and the images it uploads through its media channel.
Those two routes, /api/public/otel/v1/traces and /api/public/media, are the
only Langfuse paths Tracepad serves. What does not move is anything the SDK
asks Langfuse's REST API for. Scores, prompts, datasets and reads go through
Tracepad's own API instead, and the table below lists the equivalent for each.
The switch
- Run Tracepad (quickstart). The first start prints a key
that holds
ingest, which is everything an application needs. -
Point the SDK at it:
export LANGFUSE_BASE_URL=http://localhost:4318 export LANGFUSE_HOST=http://localhost:4318 # the older name, for older SDKs export LANGFUSE_PUBLIC_KEY=tp-pk-… export LANGFUSE_SECRET_KEY=tp-sk-…Set
LANGFUSE_BASE_URL, not onlyLANGFUSE_HOST. The Python SDK 4.x readsLANGFUSE_BASE_URLfirst and keepsLANGFUSE_HOSTas a deprecated fallback. The JavaScript SDK 5.x (@langfuse/otel) readsLANGFUSE_BASE_URLand never readsLANGFUSE_HOST. With only the old name set, a Node application keeps sending to Langfuse's cloud, its default. Abase_url(Python) orbaseUrl(JavaScript) passed in code beats both variables, so check for one in the code as well. The server's first-start output and the key dialog print both names. -
Run the application. Check with
tracepad traces last --since 10munder a key that reads (quickstart), or open the interface. The trace list fills as the SDK flushes.
The public key and the secret key are a Tracepad key pair. The SDK sends
them as Basic auth, and the secret key alone also works as a bearer token
(ingest.md). A key for an application should hold
ingest and nothing else. Mint one in the interface under Settings → Project →
API keys, where ingest is ticked by default. From a shell, use the admin token
and the project's id (admin.md):
TRACEPAD_API_KEY=$TRACEPAD_ADMIN_TOKEN \
tracepad keys create --project <project id> --scope ingest --name "my app"
CI runs the Langfuse Python SDK 4.14.5 against the server, in
scripts/smoke. The variable names above were read from
the source of that release and of @langfuse/otel 5.11.1 on 2026-10-04. The
JavaScript SDK exports to the same route, but no test runs it
(What beta means).
What carries over
Tracepad maps the langfuse.* attributes listed in
ingest.md's table into its own
fields. Any attribute it does not map, langfuse.experiment.* among them, is
kept in the observation's metadata, so nothing is dropped. In SDK terms:
| From the SDK | In Tracepad |
|---|---|
propagate_attributes(trace_name, user_id, session_id, tags, metadata, environment) |
The trace's name, user, session, tags, metadata and environment. Each one is a filter, and sessions and users get their own screens. |
release, version |
The release and version filters. |
as_type= (generation, agent, tool, chain, retriever, …) |
The observation's type, with all ten values kept as sent. ?type=tool filters on it. |
model, model_parameters |
The model and its parameters. The model feeds the cost and token breakdowns. |
input, output, metadata |
The payloads, shown in the interface's JSON viewer and covered by full-text search. |
usage_details |
Token counts, every class kept under the name the SDK sent. |
cost_details |
The cost, summed into the trace, the session, the user and the statistics. |
level, status_message |
Errors: the trace's error count and the status=error filter. |
completion_start_time |
Time to first token. |
A generation linked to a prompt (prompt=) |
The prompt name and version on the observation, and the prompt=name@version filter. |
| Images in an input or output | The SDK's media upload channel is served, so each image is stored once and shown in the trace view (media.md). |
Two things work differently:
- Cost is what the SDK sends. Tracepad keeps no price table and never
estimates a cost from the model and the tokens. A generation without
cost_detailscounts as no cost. Pass the cost your provider reported, as shown in ingest.md. - Eval runs link through Tracepad's attributes.
langfuse.experiment.*is kept in metadata and links nothing. To tie a trace to a dataset run, settracepad.run_idandtracepad.item_idon the trace's root observation (datasets.md).
What goes through Tracepad's API instead
These calls go to Langfuse's REST API: /api/public/scores, the prompt,
dataset and trace routes, and the rest of /api/public/… apart from the OTLP
and media routes above. Tracepad does not serve them, so against Tracepad they
get a 404.
| Langfuse SDK call | In Tracepad |
|---|---|
Scores (create_score, score_current_trace, …) |
POST /api/v1/scores, or score() in the Python, Node and Go packages (scores.md) |
Prompt management (get_prompt, create_prompt) |
GET and POST /api/v1/prompts, or prompt() in the packages (prompts.md) |
| Datasets and experiments | /api/v1/datasets and /api/v1/runs, or the eval harness in the packages (datasets.md) |
Reading traces back (api.trace.get, api.trace.list, …) |
The read API under /api/v1, the CLI or MCP (api.md). The read API has its own shape: flat JSON with cursors. |
Both libraries can run in one application. The Langfuse SDK keeps tracing,
and the tracepad package handles prompts, scores and datasets against the
same server. Initialise it with export=False so that each span is sent
only once (sdk-python.md).
The way back
Tracepad keeps every export body exactly as the SDK sent it. tracepad export
--otlp replays that archive into any OTLP receiver, including Langfuse's own
endpoint, so the move is not one-way (export.md). In that command
the receiver's keys are Langfuse's pk-lf-…/sk-lf-… pair. The LANGFUSE_*
variables on a machine that sends to Tracepad hold Tracepad's keys.