| name | langfuse-extract |
| description | Extract sessions, traces, observations, prompts, and scores from Langfuse via its public API. Use when the user wants to pull/export/analyze Langfuse data (not for instrumenting an app to send traces — see langfuse-observability for that). |
| origin | local |
Langfuse Extract
Read-side tool: pulls data OUT of Langfuse (traces, sessions, observations/generations,
prompts, scores, daily metrics) via the public REST API. For instrumenting an app to
SEND traces to Langfuse, use the langfuse-observability skill instead.
When to Activate
- "dame las traces de ayer con error"
- "extrae las sessions del usuario X"
- "necesito el historial de prompts de support_agent_v2"
- "cuánto costó el proyecto Y la semana pasada" (daily metrics)
- Building an eval/fine-tuning dataset from production traces
- Auditing what a specific session/trace actually sent to the model
Credentials — .env convention
Same pattern as ~/.intercom_*.env / ~/.twilio_*.env: one .env file per
workspace/project at ~/.langfuse_<workspace>.env, never committed to git.
Template: .env.example in this skill's directory. Required vars:
WORKSPACE_NAME= # label for logs
LANGFUSE_HOST= # https://cloud.langfuse.com (EU) / https://us.cloud.langfuse.com (US) / self-hosted
LANGFUSE_PUBLIC_KEY= # pk-lf-...
LANGFUSE_SECRET_KEY= # sk-lf-...
Get keys from the Langfuse project: Settings > API Keys. Auth is HTTP Basic —
username=public key, password=secret key. Region matters: a US cloud project's
keys will 401 against the EU host and vice versa.
Set chmod 600 on the env file.
Tools in this skill
scripts/langfuse_client.py — reusable LangfuseClient class (auth, pagination,
retry on 429/5xx). Import this if writing a custom extraction script.
scripts/extract.py — CLI wrapper. Covers the common cases without writing code.
CLI usage
cd ~/.claude/skills/langfuse-extract/scripts
python extract.py <kind> --env-file ~/.langfuse_prod.env [options]
<kind>: traces | trace | sessions | session | observations | prompts | prompt | scores | metrics | health
python extract.py traces --env-file ~/.langfuse_prod.env \
--from 2026-07-01 --to 2026-07-16 --session-id abc123 -o traces.jsonl -f jsonl
python extract.py session --env-file ~/.langfuse_prod.env --session-id abc123
python extract.py debug-session --env-file ~/.langfuse_prod.env --session-id abc123
python extract.py observations --env-file ~/.langfuse_prod.env \
--trace-id abc123 --type GENERATION
python extract.py prompt --env-file ~/.langfuse_prod.env \
--name support_agent_v2 --label production
python extract.py scores --env-file ~/.langfuse_prod.env --from 2026-07-01
python extract.py metrics --env-file ~/.langfuse_prod.env \
--trace-name generate_reply --from 2026-07-01
python extract.py health --env-file ~/.langfuse_prod.env
Output: --output/-o file --format/-f json|jsonl|csv (default: prints JSON to stdout).
csv flattens nested fields (input/output/metadata) to JSON strings per cell.
--max-items N (default 500) caps TOTAL items across all pages for traces,
observations, scores — not just page size (--limit is page size only).
Without this cap an unscoped list call on an active project pages forever.
session/trace/prompt (single-resource get) and debug-session (bounded by
one session's traces) ignore it — there's nothing to cap.
API reference (what the client wraps)
Base path: {LANGFUSE_HOST}/api/public/.... All list endpoints are paginated
(page, limit params; response { data: [...], meta: { page, totalPages, totalItems } }) —
LangfuseClient._paginate walks all pages automatically, so callers just get an iterator
of items.
| Resource | List | Get one | Key filters |
|---|
| Traces | GET /api/public/traces | GET /api/public/traces/{id} | fromTimestamp, toTimestamp, sessionId, userId, name, tags, environment |
| Sessions | GET /api/public/sessions | GET /api/public/sessions/{id} (includes its traces) | fromTimestamp, toTimestamp |
| Observations | GET /api/public/observations | GET /api/public/observations/{id} | traceId, type (SPAN/GENERATION/EVENT), userId, name, fromStartTime, toStartTime |
| Prompts | GET /api/public/v2/prompts | GET /api/public/v2/prompts/{name} | version or label (e.g. production, latest) for get-one; name for list |
| Scores | GET /api/public/scores | — | traceId, userId, name, fromTimestamp, toTimestamp |
| Daily metrics | GET /api/public/metrics/daily | — | traceName, userId, tags, fromTimestamp, toTimestamp |
| Debug session (custom, not a raw endpoint) | debug-session --session-id -> session's traces -> each trace's GENERATION observations, sorted by time | — | session_id only |
Full reference: https://api.reference.langfuse.com
Gotchas
fromTimestamp/toTimestamp need full ISO-8601 datetime, not a bare date.
The API 400s on 2026-07-01 — LangfuseClient._ts auto-expands a plain
YYYY-MM-DD to T00:00:00Z, so passing dates on the CLI is fine; only matters
if you're calling the client's methods directly with something else shaped.
- Region-locked keys. Cloud keys are tied to one region (EU
cloud.langfuse.com
vs US us.cloud.langfuse.com). Wrong host -> 401, not a clearer error. Run health
kind first when setting up a new .env — though note health doesn't check auth,
only connectivity/version, so a 200 there doesn't mean the keys are valid.
- Unscoped list calls can hang or time out server-side, not just return a lot of
data —
/sessions and /traces with no date range measured 30s+ per page on a
live project in testing. LangfuseClient retries timeouts/connection errors with
backoff (default 60s per-request timeout, 5 attempts) and --max-items caps total
items, but the real fix is scoping --from/--to tight — don't rely on the cap to
make a lazy unscoped query fast, it only stops it from running forever.
- Rate limits. Public API is rate-limited per plan;
LangfuseClient retries on 429
honoring Retry-After, and backs off on 5xx. Don't lower max_retries to 0 for bulk pulls.
/metrics/daily tends to have tighter limits than /traces.
https://langfuse.com/faq/all/api-limits
GENERATION observations carry the actual prompt/completion sent to the model
(input/output fields) — that's what you want for eval datasets or debugging a bad
reply, not the trace object itself (which is closer to a request-level summary).
promptName/promptVersion on the observation are only populated if the app
fetched the prompt via langfuse.get_prompt(...) and passed it through — a
hardcoded prompt string in the code won't show a linked version at all.
- Sessions ≠ traces. A session groups multiple traces (e.g. one per turn in a
conversation). Pulling gives you the full multi-turn context in one call;
goes one step further and flattens every generation across all of
the session's traces — the shortcut for "what did we send this agent, turn by turn."
Reference Skills
- Instrumenting an app to send traces → skill:
langfuse-observability
- Building eval datasets from extracted traces → skill:
rag-patterns, llm-engineering