From ac7e6a7566340c94f18bfc64ef3554adbf5d0ffd Mon Sep 17 00:00:00 2001 From: Nimar Date: Fri, 17 Jul 2026 16:59:56 +0200 Subject: [PATCH 01/11] feat(cli): fast and no more JIT openapi spec --- .github/workflows/ci.yml | 11 +- .gitignore | 1 - bin/langfuse.mjs | 4 +- bun.lock | 102 + conformance/README.md | 113 + conformance/src/all.ts | 43 + conformance/src/runner.ts | 26 +- openapi.yml | 14629 ------------------------------------ package.json | 16 +- scripts/build.ts | 54 + scripts/patch-openapi.ts | 277 - scripts/release.ts | 20 +- src/cli.ts | 823 +- src/client.ts | 191 + src/contracts/compiler.ts | 337 + src/contracts/loader.ts | 101 + src/contracts/types.ts | 112 + 17 files changed, 1738 insertions(+), 15122 deletions(-) create mode 100644 bun.lock create mode 100644 conformance/README.md create mode 100644 conformance/src/all.ts delete mode 100644 openapi.yml create mode 100644 scripts/build.ts delete mode 100644 scripts/patch-openapi.ts create mode 100644 src/client.ts create mode 100644 src/contracts/compiler.ts create mode 100644 src/contracts/loader.ts create mode 100644 src/contracts/types.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a766454..695ae92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,16 @@ jobs: bun-version: 1.3.14 - name: Install dependencies - run: bun install + run: bun ci + + - name: Typecheck + run: bun run typecheck - name: Run tests run: bun test + + - name: Fake-call every endpoint in every API snapshot + run: bun run conformance:all + + - name: Smoke-test built CLI + run: bun bin/langfuse.mjs --help diff --git a/.gitignore b/.gitignore index e7a6b96..065923d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ node_modules dist *.tgz -bun.lock bun.lockb .env .env.local diff --git a/bin/langfuse.mjs b/bin/langfuse.mjs index 4869439..709d0eb 100755 --- a/bin/langfuse.mjs +++ b/bin/langfuse.mjs @@ -1,3 +1,3 @@ -#!/usr/bin/env node +#!/usr/bin/env bun import { run } from "../dist/cli.js"; -run(process.argv); +await run(process.argv); diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..c0a2d1a --- /dev/null +++ b/bun.lock @@ -0,0 +1,102 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "langfuse-cli", + "devDependencies": { + "@apidevtools/swagger-parser": "^12.1.0", + "@types/bun": "^1.3.14", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "typescript": "^7.0.2", + "yaml": "^2.8.2", + }, + }, + }, + "packages": { + "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@14.0.1", "", { "dependencies": { "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw=="], + + "@apidevtools/openapi-schemas": ["@apidevtools/openapi-schemas@2.1.0", "", {}, "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ=="], + + "@apidevtools/swagger-methods": ["@apidevtools/swagger-methods@3.0.2", "", {}, "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="], + + "@apidevtools/swagger-parser": ["@apidevtools/swagger-parser@12.1.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "14.0.1", "@apidevtools/openapi-schemas": "^2.1.0", "@apidevtools/swagger-methods": "^3.0.2", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "call-me-maybe": "^1.0.2" }, "peerDependencies": { "openapi-types": ">=7" } }, "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + } +} diff --git a/conformance/README.md b/conformance/README.md new file mode 100644 index 0000000..e0e524b --- /dev/null +++ b/conformance/README.md @@ -0,0 +1,113 @@ +# Langfuse CLI OpenAPI conformance suite + +Language-neutral, version-pinned acceptance tests for the native TypeScript CLI. The oracle does not import the runtime request builder. + +## What is tested + +The primary test fake-calls every operation in every cataloged spec through the real CLI. For each operation it: + +- generates one minimally valid invocation from the untouched OpenAPI source +- starts a local mock HTTP server with a response generated from that operation +- runs the CLI as a subprocess against the mock host +- compares the received method, path, query, headers, authentication, and JSON body +- compares the CLI's response status, body, and exit status + +The black-box oracle does not share request-building code with the CLI. `bun test` currently attempts all 678 operations across all eight v3 snapshots using the historical field-flag adapter. The 18 operations that require lossless JSON bodies remain an explicit compatibility baseline; any additional failure fails the test. The native `contract-v1` adapter passes all 678 operations through `--body-json`. + +Supporting unit tests verify immutable spec hashes, valid sampling, serialization, naming, adapters, and the capture runner itself. + +OpenAPI cannot describe database setup, generated IDs, cross-request bindings, cleanup, licenses, or feature flags. Those stateful live workflows require a small reviewed scenario overlay; they are not silently invented by this generator. + +## Pinned specs + +| Langfuse | Paths | Operations | +|---|---:|---:| +| 3.0.0 | 29 | 39 | +| 3.50.0 | 44 | 68 | +| 3.100.0 | 49 | 77 | +| 3.150.0 | 55 | 86 | +| 3.176.0 | 60 | 96 | +| 3.200.0 | 61 | 98 | +| 3.212.0 | 64 | 101 | +| 3.216.0 | 69 | 113 | + +Each source file is downloaded by immutable commit and verified against the SHA-256 in `catalog.json`. Tests are network-free after sync. + +Langfuse 3.200.0, 3.212.0, and 3.216.0 use the JSON Schema `const` keyword while declaring OpenAPI 3.0.1. `swagger-parser` correctly reports those sources as invalid OAS 3.0 documents. The catalog records this as `oas3.0-const-keyword`; request sampling still preserves and tests the constraint with the independent JSON Schema validator. + +## Files + +```text +catalog.json immutable Git refs, commits, hashes, known source issues +policy.json implementation adapters; not API truth +specs//openapi.yml untouched upstream snapshots +src/ compiler, serializers, adapters, capture runner +tests/ compiler, validator, and runner tests +``` + +## Commands + +```sh +# Generator, schema, serializer, capture, and compatibility tests +bun test + +# Build and fake-call every endpoint through the lossless native CLI +bun run conformance:all + +# Re-download pinned bytes and verify their hashes +bun run conformance:sync +``` + +## CI gate + +GitHub Actions runs both commands for every pull request, merge queue entry, and push to `main`: + +```sh +bun test +bun run conformance:all +``` + +The required check name is **Test and verify OpenAPI conformance**. The interactive release script repeats the checks before building or publishing. + +## Run a focused current-CLI check + +```sh +bun run conformance:run -- \ + --version 3.212.0 \ + --adapter specli-v0 \ + --current-cli +``` + +The adapter name is retained because it describes the old field-flag grammar. The runner builds the native current source and compiles the selected untouched spec into a temporary runtime contract. + +Useful filters: + +```sh +--operation prompts_create +--max 20 +--fail-fast +``` + +Failures identify current limitations inline: raw union bodies, complex body flags, response exit codes, naming mismatches, or missing version selection. + +## Run the lossless native contract + +The native adapter uses lossless JSON body input via `--body-json`: + +```sh +bun run conformance:run -- \ + --version 3.216.0 \ + --adapter contract-v1 \ + -- bun bin/langfuse.mjs --api-version 3.216.0 +``` + +The command after the second `--` is treated as an external black-box executable. + +## Add a version + +1. Resolve the release tag to its immutable commit. +2. Add the tag, commit, and SHA-256 to `catalog.json`. +3. Run `bun run conformance:sync -- --version `. +4. Run `bun test` and `bun run conformance:all`. + +Never point a committed catalog entry at mutable `main` or `latest`. diff --git a/conformance/src/all.ts b/conformance/src/all.ts new file mode 100644 index 0000000..111335b --- /dev/null +++ b/conformance/src/all.ts @@ -0,0 +1,43 @@ +import { loadCatalog } from "./catalog"; +import { generateCorpus } from "./generator"; +import { runConformance } from "./runner"; + +const build = Bun.spawn(["bun", "run", "build"], { + cwd: import.meta.dir + "/../..", + stdout: "inherit", + stderr: "inherit", +}); +if ((await build.exited) !== 0) process.exit(1); + +const catalog = await loadCatalog(); +let passed = 0; +let total = 0; +let failed = false; + +for (const entry of catalog.versions) { + const corpus = await generateCorpus(entry); + const results = await runConformance({ + entry, + manifest: corpus.compiled.manifest, + vectors: corpus.vectors, + adapter: "contract-v1", + command: ["bun", "bin/langfuse.mjs", "--api-version", entry.version], + quiet: true, + }); + const versionPassed = results.filter((result) => result.passed).length; + passed += versionPassed; + total += results.length; + process.stdout.write(`${entry.version}: ${versionPassed}/${results.length}\n`); + + for (const result of results.filter((candidate) => !candidate.passed)) { + failed = true; + process.stderr.write(`FAIL ${result.id}\n`); + for (const failure of result.failures) { + process.stderr.write(` ${failure}\n`); + } + if (result.process.stderr) process.stderr.write(result.process.stderr); + } +} + +process.stdout.write(`Total: ${passed}/${total}\n`); +if (failed) process.exit(1); diff --git a/conformance/src/runner.ts b/conformance/src/runner.ts index 597ab9a..b228f49 100644 --- a/conformance/src/runner.ts +++ b/conformance/src/runner.ts @@ -1,4 +1,4 @@ -import { mkdir, mkdtemp, rm, symlink } from "node:fs/promises"; +import { mkdir, mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; @@ -9,6 +9,7 @@ import { } from "./adapters"; import { CaptureServer, requestDiff, sameJson } from "./capture"; import { POLICY_PATH, REPOSITORY_ROOT, readVerifiedSpec } from "./catalog"; +import { compileApiContract } from "../../src/contracts/compiler"; import type { CatalogEntry, ConformanceVector, @@ -95,11 +96,6 @@ async function currentCliCommand(entry: CatalogEntry): Promise<{ const bin = resolve(directory, "bin"); await mkdir(dist, { recursive: true }); await mkdir(bin, { recursive: true }); - await symlink( - resolve(REPOSITORY_ROOT, "node_modules"), - resolve(directory, "node_modules"), - "dir", - ); const build = Bun.spawn( [ "bun", @@ -108,7 +104,7 @@ async function currentCliCommand(entry: CatalogEntry): Promise<{ "--outfile", resolve(dist, "cli.js"), "--target", - "node", + "bun", "--format", "esm", ], @@ -125,7 +121,21 @@ async function currentCliCommand(entry: CatalogEntry): Promise<{ await rm(directory, { recursive: true, force: true }); throw new Error(`Current CLI build failed:\n${stdout}${stderr}`); } - await Bun.write(resolve(directory, "openapi.yml"), await readVerifiedSpec(entry)); + const raw = await readVerifiedSpec(entry); + const contracts = resolve(dist, "contracts"); + await mkdir(contracts, { recursive: true }); + await Bun.write( + resolve(contracts, `${entry.version}.json`), + `${JSON.stringify(compileApiContract(entry, raw))}\n`, + ); + await Bun.write( + resolve(contracts, "catalog.json"), + `${JSON.stringify({ + schemaVersion: 1, + latest: entry.version, + versions: [{ version: entry.version, sourceSha256: entry.sha256 }], + })}\n`, + ); await Bun.write( resolve(bin, "langfuse.mjs"), await Bun.file(resolve(REPOSITORY_ROOT, "bin/langfuse.mjs")).text(), diff --git a/openapi.yml b/openapi.yml deleted file mode 100644 index 8bd6144..0000000 --- a/openapi.yml +++ /dev/null @@ -1,14629 +0,0 @@ -openapi: 3.0.1 -info: - title: server - version: '' - description: >- - ## Authentication - - - Authenticate with the API using [Basic - Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API - keys in the project settings: - - - - username: Langfuse Public Key - - - password: Langfuse Secret Key - - - ## Exports - - - - OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml -paths: - /api/public/annotation-queues: - get: - description: Get all annotation queues - operationId: annotationQueues_listQueues - tags: - - AnnotationQueues - parameters: - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedAnnotationQueues' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: &ref_0 - - BasicAuth: [] - post: - description: Create an annotation queue - operationId: annotationQueues_createQueue - tags: - - AnnotationQueues - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueue' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateAnnotationQueueRequest' - /api/public/annotation-queues/{queueId}: - get: - description: Get an annotation queue by ID - operationId: annotationQueues_getQueue - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueue' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/annotation-queues/{queueId}/items: - get: - description: Get items for a specific annotation queue - operationId: annotationQueues_listQueueItems - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - - name: status - in: query - description: Filter by status - required: false - schema: - $ref: '#/components/schemas/AnnotationQueueStatus' - nullable: true - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedAnnotationQueueItems' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - post: - description: Add an item to an annotation queue - operationId: annotationQueues_createQueueItem - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueueItem' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateAnnotationQueueItemRequest' - /api/public/annotation-queues/{queueId}/items/{itemId}: - get: - description: Get a specific item from an annotation queue - operationId: annotationQueues_getQueueItem - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - - name: itemId - in: path - description: The unique identifier of the annotation queue item - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueueItem' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - patch: - description: Update an annotation queue item - operationId: annotationQueues_updateQueueItem - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - - name: itemId - in: path - description: The unique identifier of the annotation queue item - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueueItem' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateAnnotationQueueItemRequest' - delete: - description: Remove an item from an annotation queue - operationId: annotationQueues_deleteQueueItem - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - - name: itemId - in: path - description: The unique identifier of the annotation queue item - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteAnnotationQueueItemResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/annotation-queues/{queueId}/assignments: - post: - description: Create an assignment for a user to an annotation queue - operationId: annotationQueues_createQueueAssignment - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/CreateAnnotationQueueAssignmentResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueueAssignmentRequest' - delete: - description: Delete an assignment for a user to an annotation queue - operationId: annotationQueues_deleteQueueAssignment - tags: - - AnnotationQueues - parameters: - - name: queueId - in: path - description: The unique identifier of the annotation queue - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteAnnotationQueueAssignmentResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AnnotationQueueAssignmentRequest' - /api/public/integrations/blob-storage: - get: - description: >- - Get all blob storage integrations for the organization (requires - organization-scoped API key) - operationId: blobStorageIntegrations_getBlobStorageIntegrations - tags: - - BlobStorageIntegrations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/BlobStorageIntegrationsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - put: - description: >- - Create or update a blob storage integration for a specific project - (requires organization-scoped API key). The configuration is validated - by performing a test upload to the bucket. - operationId: blobStorageIntegrations_upsertBlobStorageIntegration - tags: - - BlobStorageIntegrations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/BlobStorageIntegrationResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateBlobStorageIntegrationRequest' - /api/public/integrations/blob-storage/{id}: - get: - description: >- - Get the sync status of a blob storage integration by integration ID - (requires organization-scoped API key) - operationId: blobStorageIntegrations_getBlobStorageIntegrationStatus - tags: - - BlobStorageIntegrations - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/BlobStorageIntegrationStatusResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: >- - Delete a blob storage integration by ID (requires organization-scoped - API key) - operationId: blobStorageIntegrations_deleteBlobStorageIntegration - tags: - - BlobStorageIntegrations - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/BlobStorageIntegrationDeletionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/comments: - post: - description: >- - Create a comment. Comments may be attached to different object types - (trace, observation, session, prompt). - operationId: comments_create - tags: - - Comments - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/CreateCommentResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateCommentRequest' - get: - description: Get all comments - operationId: comments_get - tags: - - Comments - parameters: - - name: page - in: query - description: Page number, starts at 1. - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: >- - Limit of items per page. If you encounter api issues due to too - large page sizes, try to reduce the limit - required: false - schema: - type: integer - nullable: true - - name: objectType - in: query - description: >- - Filter comments by object type (trace, observation, session, prompt). - required: false - schema: - type: string - nullable: true - - name: objectId - in: query - description: >- - Filter comments by object id. If objectType is not provided, an - error will be thrown. - required: false - schema: - type: string - nullable: true - - name: authorUserId - in: query - description: Filter comments by author user id. - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/GetCommentsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/comments/{commentId}: - get: - description: Get a comment by id - operationId: comments_get-by-id - tags: - - Comments - parameters: - - name: commentId - in: path - description: The unique langfuse identifier of a comment - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/dataset-items: - post: - description: Create a dataset item - operationId: datasetItems_create - tags: - - DatasetItems - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DatasetItem' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateDatasetItemRequest' - get: - description: >- - Get dataset items. Optionally specify a version to get the items as they - existed at that point in time. - - Note: If version parameter is provided, datasetName must also be - provided. - operationId: datasetItems_list - tags: - - DatasetItems - parameters: - - name: datasetName - in: query - required: false - schema: - type: string - nullable: true - - name: sourceTraceId - in: query - required: false - schema: - type: string - nullable: true - - name: sourceObservationId - in: query - required: false - schema: - type: string - nullable: true - - name: version - in: query - description: >- - ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., - "2026-01-21T14:35:42Z"). - - If provided, returns state of dataset at this timestamp. - - If not provided, returns the latest version. Requires datasetName to - be specified. - required: false - schema: - type: string - format: date-time - nullable: true - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedDatasetItems' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/dataset-items/{id}: - get: - description: Get a dataset item - operationId: datasetItems_get - tags: - - DatasetItems - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DatasetItem' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: >- - Delete a dataset item and all its run items. This action is irreversible. - operationId: datasetItems_delete - tags: - - DatasetItems - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteDatasetItemResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/dataset-run-items: - post: - description: Create a dataset run item - operationId: datasetRunItems_create - tags: - - DatasetRunItems - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DatasetRunItem' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateDatasetRunItemRequest' - get: - description: List dataset run items - operationId: datasetRunItems_list - tags: - - DatasetRunItems - parameters: - - name: datasetId - in: query - required: true - schema: - type: string - - name: runName - in: query - required: true - schema: - type: string - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedDatasetRunItems' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/v2/datasets: - get: - description: Get all datasets - operationId: datasets_list - tags: - - Datasets - parameters: - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedDatasets' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - post: - description: Create a dataset - operationId: datasets_create - tags: - - Datasets - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Dataset' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateDatasetRequest' - /api/public/v2/datasets/{datasetName}: - get: - description: Get a dataset - operationId: datasets_get - tags: - - Datasets - parameters: - - name: datasetName - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Dataset' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/datasets/{datasetName}/runs/{runName}: - get: - description: Get a dataset run and its items - operationId: datasets_getRun - tags: - - Datasets - parameters: - - name: datasetName - in: path - required: true - schema: - type: string - - name: runName - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DatasetRunWithItems' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: Delete a dataset run and all its run items. This action is irreversible. - operationId: datasets_deleteRun - tags: - - Datasets - parameters: - - name: datasetName - in: path - required: true - schema: - type: string - - name: runName - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteDatasetRunResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/datasets/{datasetName}/runs: - get: - description: Get dataset runs - operationId: datasets_getRuns - tags: - - Datasets - parameters: - - name: datasetName - in: path - required: true - schema: - type: string - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedDatasetRuns' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/experiments: - get: - description: |- - List experiments with cursor-based pagination. Results are ordered by - latest experiment activity descending. - operationId: experiments_list - tags: - - Experiments - parameters: - - name: fields - in: query - description: |- - Comma-separated list of field groups to include. Available groups: - `core`, `metadata`, `scores`. If omitted, `core` is returned. - required: false - schema: - type: string - nullable: true - - name: limit - in: query - description: Number of experiments to return per page. Maximum 100, default 50. - required: false - schema: - type: integer - nullable: true - - name: scoreLimit - in: query - description: >- - Number of scores to return per experiment when `fields=scores` is - requested. Maximum 50, default 50. - required: false - schema: - type: integer - nullable: true - - name: cursor - in: query - description: Versioned base64url cursor from the previous response page. - required: false - schema: - type: string - nullable: true - - name: fromStartTime - in: query - description: Retrieve only experiments on or after this datetime. - required: true - schema: - type: string - format: date-time - - name: toStartTime - in: query - description: Retrieve only experiments before this datetime. - required: false - schema: - type: string - format: date-time - nullable: true - - name: id - in: query - description: Comma-separated list of experiment IDs. - required: false - schema: - type: string - nullable: true - - name: name - in: query - description: Comma-separated list of experiment names. - required: false - schema: - type: string - nullable: true - - name: datasetId - in: query - description: Comma-separated list of dataset IDs. - required: false - schema: - type: string - nullable: true - - name: filter - in: query - description: |- - JSON string containing an array of structured filter conditions. - Supported columns are `id`, `name`, and `datasetId`. - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ExperimentsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/experiment-items: - get: - description: |- - List experiment items with cursor-based pagination. Use this endpoint - to export experiment item inputs, outputs, expected outputs, metadata, - and optionally item/trace scores. Results are ordered by time - descending. - operationId: experiments_listItems - tags: - - Experiments - parameters: - - name: fields - in: query - description: |- - Comma-separated list of field groups to include. Available groups: - `core`, `dataset`, `io`, `metadata`, `itemMetadata`, - `experimentMetadata`, `scores`. If omitted, `core,dataset` is - returned. - required: false - schema: - type: string - nullable: true - - name: limit - in: query - description: >- - Number of experiment items to return per page. Maximum 100, default - 50. - required: false - schema: - type: integer - nullable: true - - name: scoreLimit - in: query - description: >- - Number of scores to return per experiment item when `fields=scores` - is requested. Maximum 50, default 50. - required: false - schema: - type: integer - nullable: true - - name: cursor - in: query - description: Versioned base64url cursor from the previous response page. - required: false - schema: - type: string - nullable: true - - name: fromStartTime - in: query - description: Retrieve only experiment items started on or after this datetime. - required: true - schema: - type: string - format: date-time - - name: toStartTime - in: query - description: Retrieve only experiment items started before this datetime. - required: false - schema: - type: string - format: date-time - nullable: true - - name: experimentId - in: query - description: Comma-separated list of experiment IDs. - required: false - schema: - type: string - nullable: true - - name: experimentName - in: query - description: Comma-separated list of experiment names. - required: false - schema: - type: string - nullable: true - - name: experimentItemId - in: query - description: Comma-separated list of experiment item IDs. - required: false - schema: - type: string - nullable: true - - name: datasetId - in: query - description: Comma-separated list of dataset IDs. - required: false - schema: - type: string - nullable: true - - name: filter - in: query - description: |- - JSON string containing an array of structured filter conditions. - Supported columns are `experimentId`, `experimentName`, - `experimentItemId`, and `datasetId`. - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ExperimentItemsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/health: - get: - description: Check health of API and database - operationId: health_health - tags: - - Health - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/HealthResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '503': - description: '' - /api/public/ingestion: - post: - description: >- - **Legacy endpoint for batch ingestion for Langfuse Observability.** - - - -> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). - Learn more: https://langfuse.com/integrations/native/opentelemetry - - - Within each batch, there can be multiple events. - - Each event has a type, an id, a timestamp, metadata and a body. - - Internally, we refer to this as the "event envelope" as it tells us - something about the event but not the trace. - - We use the event id within this envelope to deduplicate messages to - avoid processing the same event twice, i.e. the event id should be - unique per request. - - The event.body.id is the ID of the actual trace and will be used for - updates and will be visible within the Langfuse App. - - I.e. if you want to update a trace, you'd use the same body id, but - separate event IDs. - - - Notes: - - - Introduction to data model: - https://langfuse.com/docs/observability/data-model - - - Batch sizes are limited to 3.5 MB in total. You need to adjust the - number of events per batch accordingly. - - - The API does not return a 4xx status code for input errors. Instead, - it responds with a 207 status code, which includes a list of the - encountered errors. - operationId: ingestion_batch - tags: - - Ingestion - parameters: [] - responses: - '207': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/IngestionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - batch: - type: array - items: - $ref: '#/components/schemas/IngestionEvent' - description: >- - Batch of tracing events to be ingested. Discriminated by - attribute `type`. - metadata: - nullable: true - description: >- - Optional. Metadata field used by the Langfuse SDKs for - debugging. - required: - - batch - /api/public/metrics: - get: - description: >- - Get metrics from the Langfuse project using a query object. - - - Consider using the [v2 metrics - endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for - better performance. - - - For more details, see the [Metrics API - documentation](https://langfuse.com/docs/metrics/features/metrics-api). - operationId: legacy_metricsV1_metrics - tags: - - LegacyMetricsV1 - parameters: - - name: query - in: query - description: >- - JSON string containing the query parameters with the following - structure: - - ```json - - { - "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by, e.g. "name", "userId", "sessionId" - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure, e.g. "count", "latency", "value" - "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on - "operator": string, // Operator, e.g. "=", ">", "<", "contains" - "value": any, // Value to compare against - "type": string, // Data type, e.g. "string", "number", "stringObject" - "key": string // Required only when filtering on metadata - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 - "row_limit": number // Optional. Row limit for results (1-1000) - } - } - - ``` - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/legacyMetricsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/observations/{observationId}: - get: - description: Get a observation - operationId: legacy_observationsV1_get - tags: - - LegacyObservationsV1 - parameters: - - name: observationId - in: path - description: >- - The unique langfuse identifier of an observation, can be an event, - span or generation - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ObservationsView' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/observations: - get: - description: >- - Get a list of observations. - - - Consider using the [v2 observations - endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) - for cursor-based pagination and field selection. - operationId: legacy_observationsV1_getMany - tags: - - LegacyObservationsV1 - parameters: - - name: page - in: query - description: Page number, starts at 1. - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: >- - Limit of items per page. If you encounter api issues due to too - large page sizes, try to reduce the limit. - required: false - schema: - type: integer - nullable: true - - name: name - in: query - required: false - schema: - type: string - nullable: true - - name: userId - in: query - required: false - schema: - type: string - nullable: true - - name: type - in: query - required: false - schema: - type: string - nullable: true - - name: traceId - in: query - required: false - schema: - type: string - nullable: true - - name: level - in: query - description: >- - Optional filter for observations with a specific level (e.g. - "DEBUG", "DEFAULT", "WARNING", "ERROR"). - required: false - schema: - $ref: '#/components/schemas/ObservationLevel' - nullable: true - - name: parentObservationId - in: query - required: false - schema: - type: string - nullable: true - - name: environment - in: query - description: >- - Optional filter for observations where the environment is one of the - provided values. - required: false - schema: - type: array - items: - type: string - nullable: true - - name: fromStartTime - in: query - description: >- - Retrieve only observations with a start_time on or after this - datetime (ISO 8601). - required: false - schema: - type: string - format: date-time - nullable: true - - name: toStartTime - in: query - description: >- - Retrieve only observations with a start_time before this datetime - (ISO 8601). - required: false - schema: - type: string - format: date-time - nullable: true - - name: version - in: query - description: Optional filter to only include observations with a certain version. - required: false - schema: - type: string - nullable: true - - name: filter - in: query - description: >- - JSON string containing an array of filter conditions. When provided, - this takes precedence over query parameter filters (userId, name, - type, level, environment, fromStartTime, ...). - - - ## Filter Structure - - Each filter condition has the following structure: - - ```json - - [ - { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on (see available columns below) - "operator": string, // Required. Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" - // - numberObject: "=", ">", "<", ">=", "<=" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata - } - ] - - ``` - - - ## Available Columns - - - ### Core Observation Fields - - - `id` (string) - Observation ID - - - `type` (string) - Observation type (SPAN, GENERATION, EVENT) - - - `name` (string) - Observation name - - - `traceId` (string) - Associated trace ID - - - `startTime` (datetime) - Observation start time - - - `endTime` (datetime) - Observation end time - - - `environment` (string) - Environment tag - - - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - - - `statusMessage` (string) - Status message - - - `version` (string) - Version tag - - - ### Performance Metrics - - - `latency` (number) - Latency in seconds (calculated: end_time - - start_time) - - - `timeToFirstToken` (number) - Time to first token in seconds - - - `tokensPerSecond` (number) - Output tokens per second - - - ### Token Usage - - - `inputTokens` (number) - Number of input tokens - - - `outputTokens` (number) - Number of output tokens - - - `totalTokens` (number) - Total tokens (alias: `tokens`) - - - ### Cost Metrics - - - `inputCost` (number) - Input cost in USD - - - `outputCost` (number) - Output cost in USD - - - `totalCost` (number) - Total cost in USD - - - ### Model Information - - - `model` (string) - Provided model name - - - `promptName` (string) - Associated prompt name - - - `promptVersion` (number) - Associated prompt version - - - ### Structured Data - - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata - key-value pairs. Use `key` parameter to filter on specific metadata - keys. - - - ### Associated Trace Fields (requires join with traces table) - - - `userId` (string) - User ID from associated trace - - - `traceName` (string) - Name from associated trace - - - `traceEnvironment` (string) - Environment from associated trace - - - `traceTags` (arrayOptions) - Tags from associated trace - - - ## Filter Examples - - ```json - - [ - { - "type": "string", - "column": "type", - "operator": "=", - "value": "GENERATION" - }, - { - "type": "number", - "column": "latency", - "operator": ">=", - "value": 2.5 - }, - { - "type": "stringObject", - "column": "metadata", - "key": "environment", - "operator": "=", - "value": "production" - } - ] - - ``` - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/legacyObservationsViews' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/scores: - post: - description: Create a score (supports both trace and session scores) - operationId: legacy_scoreV1_create - tags: - - LegacyScoreV1 - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/legacyCreateScoreResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/legacyCreateScoreRequest' - /api/public/scores/{scoreId}: - delete: - description: Delete a score (supports both trace and session scores) - operationId: legacy_scoreV1_delete - tags: - - LegacyScoreV1 - parameters: - - name: scoreId - in: path - description: The unique langfuse identifier of a score - required: true - schema: - type: string - responses: - '204': - description: '' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/llm-connections: - get: - description: Get all LLM connections in a project - operationId: llmConnections_list - tags: - - LlmConnections - parameters: - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedLlmConnections' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - put: - description: >- - Create or update an LLM connection. The connection is upserted on - provider. - operationId: llmConnections_upsert - tags: - - LlmConnections - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/LlmConnection' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpsertLlmConnectionRequest' - /api/public/llm-connections/{id}: - delete: - description: >- - Delete an LLM connection by id. Evaluators that depend on the deleted - connection are automatically paused. - operationId: llmConnections_delete - tags: - - LlmConnections - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteLlmConnectionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/media/{mediaId}: - get: - description: Get a media record - operationId: media_get - tags: - - Media - parameters: - - name: mediaId - in: path - description: The unique langfuse identifier of a media record - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/GetMediaResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - patch: - description: Patch a media record - operationId: media_patch - tags: - - Media - parameters: - - name: mediaId - in: path - description: The unique langfuse identifier of a media record - required: true - schema: - type: string - responses: - '204': - description: '' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PatchMediaBody' - /api/public/media: - post: - description: Get a presigned upload URL for a media record - operationId: media_getUploadUrl - tags: - - Media - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/GetMediaUploadUrlResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/GetMediaUploadUrlRequest' - /api/public/v2/metrics: - get: - description: >- - Get metrics from the Langfuse project using a query object. V2 endpoint - with optimized performance. - - - ## V2 Differences - - - Supports `observations`, `scores-numeric`, and `scores-categorical` - views only (traces view not supported) - - - Direct access to tags and release fields on observations - - - Backwards-compatible: traceName, traceRelease, traceVersion dimensions - are still available on observations view - - - High cardinality dimensions are not supported and will return a 400 - error (see below) - - - For more details, see the [Metrics API - documentation](https://langfuse.com/docs/metrics/features/metrics-api). - - - ## Available Views - - - ### observations - - Query observation-level data (spans, generations, events). - - - **Dimensions:** - - - `environment` - Deployment environment (e.g., production, staging) - - - `type` - Type of observation (SPAN, GENERATION, EVENT) - - - `name` - Name of the observation - - - `level` - Logging level of the observation - - - `version` - Version of the observation - - - `tags` - User-defined tags - - - `release` - Release version - - - `traceName` - Name of the parent trace (backwards-compatible) - - - `traceRelease` - Release version of the parent trace - (backwards-compatible, maps to release) - - - `traceVersion` - Version of the parent trace (backwards-compatible, - maps to version) - - - `providedModelName` - Name of the model used - - - `promptName` - Name of the prompt used - - - `promptVersion` - Version of the prompt used - - - `startTimeMonth` - Month of start_time in YYYY-MM format - - - **Measures:** - - - `count` - Total number of observations - - - `latency` - Observation latency (milliseconds) - - - `streamingLatency` - Generation latency from completion start to end - (milliseconds) - - - `inputTokens` - Sum of input tokens consumed - - - `outputTokens` - Sum of output tokens produced - - - `totalTokens` - Sum of all tokens consumed - - - `outputTokensPerSecond` - Output tokens per second - - - `tokensPerSecond` - Total tokens per second - - - `inputCost` - Input cost (USD) - - - `outputCost` - Output cost (USD) - - - `totalCost` - Total cost (USD) - - - `timeToFirstToken` - Time to first token (milliseconds) - - - `countScores` - Number of scores attached to the observation - - - ### scores-numeric - - Query numeric and boolean score data. - - - **Dimensions:** - - - `environment` - Deployment environment - - - `name` - Name of the score (e.g., accuracy, toxicity) - - - `source` - Origin of the score (API, ANNOTATION, EVAL) - - - `dataType` - Data type (NUMERIC, BOOLEAN) - - - `configId` - Identifier of the score config - - - `timestampMonth` - Month in YYYY-MM format - - - `timestampDay` - Day in YYYY-MM-DD format - - - `value` - Numeric value of the score - - - `traceName` - Name of the parent trace - - - `tags` - Tags - - - `traceRelease` - Release version - - - `traceVersion` - Version - - - `observationName` - Name of the associated observation - - - `observationModelName` - Model name of the associated observation - - - `observationPromptName` - Prompt name of the associated observation - - - `observationPromptVersion` - Prompt version of the associated - observation - - - **Measures:** - - - `count` - Total number of scores - - - `value` - Score value (for aggregations) - - - ### scores-categorical - - Query categorical score data. Same dimensions as scores-numeric except - uses `stringValue` instead of `value`. - - - **Measures:** - - - `count` - Total number of scores - - - ## High Cardinality Dimensions - - The following dimensions cannot be used as grouping dimensions in v2 - metrics API as they can cause performance issues. - - Use them in filters instead. - - - **observations view:** - - - `id` - Use traceId filter to narrow down results - - - `traceId` - Use traceId filter instead - - - `userId` - Use userId filter instead - - - `sessionId` - Use sessionId filter instead - - - `parentObservationId` - Use parentObservationId filter instead - - - **scores-numeric / scores-categorical views:** - - - `id` - Use specific filters to narrow down results - - - `traceId` - Use traceId filter instead - - - `userId` - Use userId filter instead - - - `sessionId` - Use sessionId filter instead - - - `observationId` - Use observationId filter instead - - - ## Aggregations - - Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, - `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` - - - ## Time Granularities - - Available granularities for timeDimension: `auto`, `minute`, `hour`, - `day`, `week`, `month` - - - `auto` bins the data into approximately 50 buckets based on the time - range - operationId: metrics_metrics - tags: - - Metrics - parameters: - - name: query - in: query - description: >- - JSON string containing the query parameters with the following - structure: - - ```json - - { - "view": string, // Required. One of "observations", "scores-numeric", "scores-categorical" - "dimensions": [ // Optional. Default: [] - { - "field": string // Field to group by (see available dimensions above) - } - ], - "metrics": [ // Required. At least one metric must be provided - { - "measure": string, // What to measure (see available measures above) - "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" - } - ], - "filters": [ // Optional. Default: [] - { - "column": string, // Column to filter on (any dimension field) - "operator": string, // Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject/numberObject: same as string/number with required "key" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Value to compare against - "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) - } - ], - "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time - "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" - }, - "fromTimestamp": string, // Required. ISO datetime string for start of time range - "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) - "orderBy": [ // Optional. Default: null - { - "field": string, // Field to order by (dimension or metric alias) - "direction": string // "asc" or "desc" - } - ], - "config": { // Optional. Query-specific configuration - "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 - "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 - } - } - - ``` - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MetricsV2Response' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/models: - post: - description: Create a model - operationId: models_create - tags: - - Models - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Model' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateModelRequest' - get: - description: Get all models - operationId: models_list - tags: - - Models - parameters: - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedModels' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/models/{id}: - get: - description: Get a model - operationId: models_get - tags: - - Models - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Model' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: >- - Delete a model. Cannot delete models managed by Langfuse. You can create - your own definition with the same modelName to override the definition - though. - operationId: models_delete - tags: - - Models - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '204': - description: '' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/v2/observations: - get: - description: >- - Get a list of observations with cursor-based pagination and flexible - field selection. - - - ## Cursor-based Pagination - - This endpoint uses cursor-based pagination for efficient traversal of - large datasets. - - The cursor is returned in the response metadata and should be passed in - subsequent requests - - to retrieve the next page of results. - - - ## Field Selection - - Use the `fields` parameter to control which observation fields are - returned: - - - `core` - Always included: id, traceId, startTime, endTime, projectId, - parentObservationId, type - - - `basic` - name, level, statusMessage, version, environment, - bookmarked, public, userId, sessionId - - - `time` - completionStartTime, createdAt, updatedAt - - - `io` - input, output - - - `metadata` - metadata (truncated to 200 chars by default, use - `expandMetadata` to get full values) - - - `model` - providedModelName, internalModelId, modelParameters - - - `usage` - usageDetails, costDetails, totalCost, usagePricingTierName - - - `prompt` - promptId, promptName, promptVersion - - - `metrics` - latency, timeToFirstToken - - - `trace_context` - tags, release, traceName - - - If not specified, `core` and `basic` field groups are returned. - - - ## Filters - - Multiple filtering options are available via query parameters or the - structured `filter` parameter. - - When using the `filter` parameter, it takes precedence over individual - query parameter filters. - operationId: observations_getMany - tags: - - Observations - parameters: - - name: fields - in: query - description: >- - Comma-separated list of field groups to include in the response. - - Available groups: core, basic, time, io, metadata, model, usage, - prompt, metrics, trace_context. - - If not specified, `core` and `basic` field groups are returned. - - Example: "basic,usage,model" - required: false - schema: - type: string - nullable: true - - name: expandMetadata - in: query - description: |- - Comma-separated list of metadata keys to return non-truncated. - By default, metadata values over 200 characters are truncated. - Use this parameter to retrieve full values for specific keys. - Example: "key1,key2" - required: false - schema: - type: string - nullable: true - - name: limit - in: query - description: Number of items to return per page. Maximum 1000, default 50. - required: false - schema: - type: integer - nullable: true - - name: cursor - in: query - description: >- - Base64-encoded cursor for pagination. Use the cursor from the - previous response to get the next page. - required: false - schema: - type: string - nullable: true - - name: parseIoAsJson - in: query - description: |- - **Deprecated.** Setting this to `true` will return a 400 error. - Input/output fields are always returned as raw strings. - Remove this parameter or set it to `false`. - required: false - schema: - type: boolean - nullable: true - - name: name - in: query - required: false - schema: - type: string - nullable: true - - name: userId - in: query - required: false - schema: - type: string - nullable: true - - name: type - in: query - description: >- - Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", - "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", - "GUARDRAIL") - required: false - schema: - type: string - nullable: true - - name: traceId - in: query - required: false - schema: - type: string - nullable: true - - name: level - in: query - description: >- - Optional filter for observations with a specific level (e.g. - "DEBUG", "DEFAULT", "WARNING", "ERROR"). - required: false - schema: - $ref: '#/components/schemas/ObservationLevel' - nullable: true - - name: parentObservationId - in: query - required: false - schema: - type: string - nullable: true - - name: environment - in: query - description: >- - Optional filter for observations where the environment is one of the - provided values. - required: false - schema: - type: array - items: - type: string - nullable: true - - name: fromStartTime - in: query - description: >- - Retrieve only observations with a start_time on or after this - datetime (ISO 8601). - required: false - schema: - type: string - format: date-time - nullable: true - - name: toStartTime - in: query - description: >- - Retrieve only observations with a start_time before this datetime - (ISO 8601). - required: false - schema: - type: string - format: date-time - nullable: true - - name: version - in: query - description: Optional filter to only include observations with a certain version. - required: false - schema: - type: string - nullable: true - - name: filter - in: query - description: >- - JSON string containing an array of filter conditions. When provided, - this takes precedence over query parameter filters (userId, name, - type, level, environment, fromStartTime, ...). - - - ## Filter Structure - - Each filter condition has the following structure: - - ```json - - [ - { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on (see available columns below) - "operator": string, // Required. Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches" - // - numberObject: "=", ">", "<", ">=", "<=" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata - } - ] - - ``` - - - ## Available Columns - - - ### Core Observation Fields - - - `id` (string) - Observation ID - - - `type` (string) - Observation type (SPAN, GENERATION, EVENT) - - - `name` (string) - Observation name - - - `traceId` (string) - Associated trace ID - - - `startTime` (datetime) - Observation start time - - - `endTime` (datetime) - Observation end time - - - `environment` (string) - Environment tag - - - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) - - - `statusMessage` (string) - Status message - - - `version` (string) - Version tag - - - `userId` (string) - User ID - - - `sessionId` (string) - Session ID - - - ### Trace-Related Fields - - - `traceName` (string) - Name of the parent trace - - - `traceTags` (arrayOptions) - Tags from the parent trace - - - `tags` (arrayOptions) - Alias for traceTags - - - ### Performance Metrics - - - `latency` (number) - Latency in seconds (calculated: end_time - - start_time) - - - `timeToFirstToken` (number) - Time to first token in seconds - - - `tokensPerSecond` (number) - Output tokens per second - - - ### Token Usage - - - `inputTokens` (number) - Number of input tokens - - - `outputTokens` (number) - Number of output tokens - - - `totalTokens` (number) - Total tokens (alias: `tokens`) - - - ### Cost Metrics - - - `inputCost` (number) - Input cost in USD - - - `outputCost` (number) - Output cost in USD - - - `totalCost` (number) - Total cost in USD - - - ### Model Information - - - `model` (string) - Provided model name (alias: - `providedModelName`) - - - `promptName` (string) - Associated prompt name - - - `promptVersion` (number) - Associated prompt version - - - ### Structured Data - - - `input` (string) - Observation input. Supports accelerated indexed - literal search with the `matches` operator. - - - `output` (string) - Observation output. Supports accelerated - indexed literal search with the `matches` operator. - - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata - key-value pairs. Use `key` parameter to filter on specific metadata - keys. - - - The `matches` operator is only supported for `input`, `output`, and - stringObject `metadata` filters. It performs indexed literal search - with token-boundary pruning using the events table text indexes. - Case sensitivity differs by target: `input` and `output` matches are - case-insensitive, while metadata value matches are case-sensitive. - Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. - Use `contains` for legacy substring semantics where the API allows - it. Any v2 `input` or `output` filter must be accompanied by at - least one `=` or `matches` filter on `input` or `output`; standalone - `contains`, `starts with`, `ends with`, and `does not contain` - filters on these columns are rejected. - - - ## Filter Examples - - ```json - - [ - { - "type": "string", - "column": "type", - "operator": "=", - "value": "GENERATION" - }, - { - "type": "number", - "column": "latency", - "operator": ">=", - "value": 2.5 - }, - { - "type": "stringObject", - "column": "metadata", - "key": "environment", - "operator": "=", - "value": "production" - }, - { - "type": "string", - "column": "output", - "operator": "matches", - "value": "needle" - } - ] - - ``` - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ObservationsV2Response' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/otel/v1/traces: - post: - description: >- - **OpenTelemetry Traces Ingestion Endpoint** - - - This endpoint implements the OTLP/HTTP specification for trace - ingestion, providing native OpenTelemetry integration for Langfuse - Observability. - - - **Supported Formats:** - - - Binary Protobuf: `Content-Type: application/x-protobuf` - - - JSON Protobuf: `Content-Type: application/json` - - - Supports gzip compression via `Content-Encoding: gzip` header - - - **Specification Compliance:** - - - Conforms to [OTLP/HTTP Trace - Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) - - - Implements `ExportTraceServiceRequest` message format - - - **Documentation:** - - - Integration guide: - https://langfuse.com/integrations/native/opentelemetry - - - Data model: https://langfuse.com/docs/observability/data-model - operationId: opentelemetry_exportTraces - tags: - - Opentelemetry - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/OtelTraceResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - resourceSpans: - type: array - items: - $ref: '#/components/schemas/OtelResourceSpan' - description: >- - Array of resource spans containing trace data as defined in - the OTLP specification - required: - - resourceSpans - /api/public/organizations/memberships: - get: - description: >- - Get all memberships for the organization associated with the API key - (requires organization-scoped API key) - operationId: organizations_getOrganizationMemberships - tags: - - Organizations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - put: - description: >- - Create or update a membership for the organization associated with the - API key (requires organization-scoped API key) - operationId: organizations_updateOrganizationMembership - tags: - - Organizations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipRequest' - delete: - description: >- - Delete a membership from the organization associated with the API key - (requires organization-scoped API key) - operationId: organizations_deleteOrganizationMembership - tags: - - Organizations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipDeletionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteMembershipRequest' - /api/public/projects/{projectId}/memberships: - get: - description: >- - Get all memberships for a specific project (requires organization-scoped - API key) - operationId: organizations_getProjectMemberships - tags: - - Organizations - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - put: - description: >- - Create or update a membership for a specific project (requires - organization-scoped API key). The user must already be a member of the - organization. - operationId: organizations_updateProjectMembership - tags: - - Organizations - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipRequest' - delete: - description: >- - Delete a membership from a specific project (requires - organization-scoped API key). The user must be a member of the - organization. - operationId: organizations_deleteProjectMembership - tags: - - Organizations - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/MembershipDeletionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteMembershipRequest' - /api/public/organizations/projects: - get: - description: >- - Get all projects for the organization associated with the API key - (requires organization-scoped API key) - operationId: organizations_getOrganizationProjects - tags: - - Organizations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/OrganizationProjectsResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/organizations/apiKeys: - get: - description: >- - Get all API keys for the organization associated with the API key - (requires organization-scoped API key) - operationId: organizations_getOrganizationApiKeys - tags: - - Organizations - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/OrganizationApiKeysResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/projects: - get: - description: >- - Get Project associated with API key (requires project-scoped API key). - You can use GET /api/public/organizations/projects to get all projects - with an organization-scoped key. - operationId: projects_get - tags: - - Projects - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Projects' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - post: - description: Create a new project (requires organization-scoped API key) - operationId: projects_create - tags: - - Projects - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Project' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - metadata: - type: object - additionalProperties: true - nullable: true - description: Optional metadata for the project - retention: - type: integer - description: >- - Number of days to retain data. Must be 0 or at least 3 days. - Requires data-retention entitlement for non-zero values. - Optional. - required: - - name - - retention - /api/public/projects/{projectId}: - put: - description: Update a project by ID (requires organization-scoped API key). - operationId: projects_update - tags: - - Projects - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Project' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - metadata: - type: object - additionalProperties: true - nullable: true - description: Optional metadata for the project - retention: - type: integer - nullable: true - description: |- - Number of days to retain data. - Must be 0 or at least 3 days. - Requires data-retention entitlement for non-zero values. - Optional. Will retain existing retention setting if omitted. - required: - - name - delete: - description: >- - Delete a project by ID (requires organization-scoped API key). Project - deletion is processed asynchronously. - operationId: projects_delete - tags: - - Projects - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '202': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ProjectDeletionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/projects/{projectId}/apiKeys: - get: - description: Get all API keys for a project (requires organization-scoped API key) - operationId: projects_getApiKeys - tags: - - Projects - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ApiKeyList' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - post: - description: >- - Create a new API key for a project (requires organization-scoped API key) - operationId: projects_createApiKey - tags: - - Projects - parameters: - - name: projectId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ApiKeyResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - note: - type: string - nullable: true - description: Optional note for the API key - publicKey: - type: string - nullable: true - description: >- - Optional predefined public key. Must start with 'pk-lf-'. If - provided, secretKey must also be provided. - secretKey: - type: string - nullable: true - description: >- - Optional predefined secret key. Must start with 'sk-lf-'. If - provided, publicKey must also be provided. - /api/public/projects/{projectId}/apiKeys/{apiKeyId}: - delete: - description: Delete an API key for a project (requires organization-scoped API key) - operationId: projects_deleteApiKey - tags: - - Projects - parameters: - - name: projectId - in: path - required: true - schema: - type: string - - name: apiKeyId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ApiKeyDeletionResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/v2/prompts/{name}/versions/{version}: - patch: - description: Update labels for a specific prompt version - operationId: promptVersion_update - tags: - - PromptVersion - parameters: - - name: name - in: path - description: >- - The name of the prompt. If the prompt is in a folder (e.g., - "folder/subfolder/prompt-name"), - - the folder path must be URL encoded. - required: true - schema: - type: string - - name: version - in: path - description: Version of the prompt to update - required: true - schema: - type: integer - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Prompt' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - newLabels: - type: array - items: - type: string - description: >- - New labels for the prompt version. Labels are unique across - versions. The "latest" label is reserved and managed by - Langfuse. - required: - - newLabels - /api/public/v2/prompts/{promptName}: - get: - description: Get a prompt - operationId: prompts_get - tags: - - Prompts - parameters: - - name: promptName - in: path - description: >- - The name of the prompt. If the prompt is in a folder (e.g., - "folder/subfolder/prompt-name"), - - the folder path must be URL encoded. - required: true - schema: - type: string - - name: prompt-version - in: query - description: Version of the prompt to be retrieved. - required: false - schema: - type: integer - nullable: true - - name: label - in: query - description: >- - Label of the prompt to be retrieved. Defaults to "production" if no - label or version is set. - required: false - schema: - type: string - nullable: true - - name: resolve - in: query - description: >- - Resolve prompt dependencies before returning the prompt. Defaults to - `true`. Set to `false` to return the raw stored prompt with - dependency tags intact. This bypasses prompt caching and is intended - for debugging or one-off jobs, not production runtime fetches. - required: false - schema: - type: boolean - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Prompt' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: >- - Delete prompt versions. If neither version nor label is specified, all - versions of the prompt are deleted. - operationId: prompts_delete - tags: - - Prompts - parameters: - - name: promptName - in: path - description: The name of the prompt - required: true - schema: - type: string - - name: label - in: query - description: >- - Optional label to filter deletion. If specified, deletes all prompt - versions that have this label. - required: false - schema: - type: string - nullable: true - - name: version - in: query - description: >- - Optional version to filter deletion. If specified, deletes only this - specific version of the prompt. - required: false - schema: - type: integer - nullable: true - responses: - '204': - description: '' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/v2/prompts: - get: - description: Get a list of prompt names with versions and labels - operationId: prompts_list - tags: - - Prompts - parameters: - - name: name - in: query - required: false - schema: - type: string - nullable: true - - name: label - in: query - required: false - schema: - type: string - nullable: true - - name: tag - in: query - required: false - schema: - type: string - nullable: true - - name: page - in: query - description: page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: limit of items per page - required: false - schema: - type: integer - nullable: true - - name: fromUpdatedAt - in: query - description: >- - Optional filter to only include prompt versions created/updated on - or after a certain datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: toUpdatedAt - in: query - description: >- - Optional filter to only include prompt versions created/updated - before a certain datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PromptMetaListResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - post: - description: >- - Create a new version for the prompt with the given `name` - - - Example: - langfuse api prompts create --type text --name my-prompt --prompt 'Hello {{name}}' - operationId: prompts_create - tags: - - Prompts - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Prompt' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreatePromptRequest' - /api/public/scim/ServiceProviderConfig: - get: - description: >- - Get SCIM Service Provider Configuration (requires organization-scoped - API key) - operationId: scim_getServiceProviderConfig - tags: - - Scim - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ServiceProviderConfig' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/scim/ResourceTypes: - get: - description: Get SCIM Resource Types (requires organization-scoped API key) - operationId: scim_getResourceTypes - tags: - - Scim - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ResourceTypesResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/scim/Schemas: - get: - description: Get SCIM Schemas (requires organization-scoped API key) - operationId: scim_getSchemas - tags: - - Scim - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/SchemasResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/scim/Users: - get: - description: List users in the organization (requires organization-scoped API key) - operationId: scim_listUsers - tags: - - Scim - parameters: - - name: filter - in: query - description: Filter expression (e.g. userName eq "value") - required: false - schema: - type: string - nullable: true - - name: startIndex - in: query - description: 1-based index of the first result to return (default 1) - required: false - schema: - type: integer - nullable: true - - name: count - in: query - description: Maximum number of results to return (default 100) - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScimUsersListResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - post: - description: >- - Create a new user in the organization (requires organization-scoped API - key) - operationId: scim_createUser - tags: - - Scim - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScimUser' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - userName: - type: string - description: User's email address (required) - name: - $ref: '#/components/schemas/ScimName' - description: User's name information - emails: - type: array - items: - $ref: '#/components/schemas/ScimEmail' - nullable: true - description: User's email addresses - active: - type: boolean - nullable: true - description: Whether the user is active - password: - type: string - nullable: true - description: Initial password for the user - required: - - userName - - name - /api/public/scim/Users/{userId}: - get: - description: Get a specific user by ID (requires organization-scoped API key) - operationId: scim_getUser - tags: - - Scim - parameters: - - name: userId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScimUser' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: >- - Remove a user from the organization (requires organization-scoped API - key). Note that this only removes the user from the organization but - does not delete the user entity itself. - operationId: scim_deleteUser - tags: - - Scim - parameters: - - name: userId - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/EmptyResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/score-configs: - post: - description: >- - Create a score configuration (config). Score configs are used to define - the structure of scores - operationId: scoreConfigs_create - tags: - - ScoreConfigs - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScoreConfig' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateScoreConfigRequest' - get: - description: Get all score configs - operationId: scoreConfigs_get - tags: - - ScoreConfigs - parameters: - - name: page - in: query - description: Page number, starts at 1. - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: >- - Limit of items per page. If you encounter api issues due to too - large page sizes, try to reduce the limit - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScoreConfigs' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/score-configs/{configId}: - get: - description: Get a score config - operationId: scoreConfigs_get-by-id - tags: - - ScoreConfigs - parameters: - - name: configId - in: path - description: The unique langfuse identifier of a score config - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScoreConfig' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - patch: - description: Update a score config - operationId: scoreConfigs_update - tags: - - ScoreConfigs - parameters: - - name: configId - in: path - description: The unique langfuse identifier of a score config - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/ScoreConfig' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateScoreConfigRequest' - /api/public/v3/scores: - get: - description: |- - Get a list of scores with a polymorphic `value` field (v3). - - The `value` field type depends on `dataType`: - - `NUMERIC` → number - - `BOOLEAN` → boolean - - `CATEGORICAL`, `TEXT`, `CORRECTION` → string - - The response always includes the core fields: id, projectId, name, - value, dataType, source, timestamp, environment, createdAt, updatedAt. - - Additional field groups can be requested via the `fields` parameter: - - `details` — adds comment, configId, metadata - - `subject` — adds the subject object describing the entity the score - is attached to: kind (trace, observation, session, or experiment), - id, and traceId for observation-level scores - - `annotation` — adds authorUserId, queueId - - Unknown group names return HTTP 400. - operationId: scoresV3_getManyV3 - tags: - - ScoresV3 - parameters: - - name: limit - in: query - description: >- - Number of items per page. Maximum 100, default 50. Requests with a - limit greater than 100 return HTTP 400. - required: false - schema: - type: integer - nullable: true - - name: cursor - in: query - description: >- - URL-safe base64 (base64url) cursor for pagination. Use the cursor - from the previous response to get the next page. Absent on the final - page. - required: false - schema: - type: string - nullable: true - - name: fields - in: query - description: >- - Comma-separated field groups to include in addition to the - always-returned core fields. Allowed: details, subject, annotation — - see the endpoint description for the fields each group adds. Unknown - names return HTTP 400. - required: false - schema: - type: string - nullable: true - - name: id - in: query - description: >- - Comma-separated list of score IDs to filter by (OR within, AND - across filters). - required: false - schema: - type: string - nullable: true - - name: name - in: query - description: Comma-separated list of score names to filter by. - required: false - schema: - type: string - nullable: true - - name: source - in: query - description: >- - Comma-separated list of score sources to filter by (e.g. API, - ANNOTATION, EVAL). Case-insensitive — `api` and `API` are - equivalent. - required: false - schema: - type: string - nullable: true - - name: dataType - in: query - description: >- - Comma-separated list of data types to filter by (NUMERIC, BOOLEAN, - CATEGORICAL, TEXT, CORRECTION). Case-insensitive — `numeric` and - `NUMERIC` are equivalent. Must be a single value when used with - value, valueMin, or valueMax; otherwise the request returns HTTP - 400. Must be NUMERIC when used with valueMin or valueMax. - required: false - schema: - type: string - nullable: true - - name: environment - in: query - description: Comma-separated list of environments to filter by. - required: false - schema: - type: string - nullable: true - - name: configId - in: query - description: Comma-separated list of score config IDs to filter by. - required: false - schema: - type: string - nullable: true - - name: queueId - in: query - description: Comma-separated list of annotation queue IDs to filter by. - required: false - schema: - type: string - nullable: true - - name: authorUserId - in: query - description: Comma-separated list of author user IDs to filter by. - required: false - schema: - type: string - nullable: true - - name: value - in: query - description: >- - Comma-separated list of exact values to filter by. Requires a single - dataType from NUMERIC, BOOLEAN, or CATEGORICAL; any other dataType, - multiple dataTypes, or omitting dataType returns HTTP 400. For - BOOLEAN, each value must be "true" or "false"; for NUMERIC, each - value must be a finite number. Otherwise the request returns HTTP - 400. - required: false - schema: - type: string - nullable: true - - name: valueMin - in: query - description: >- - Inclusive lower bound on the numeric value. Requires - dataType=NUMERIC as a single value; otherwise the request returns - HTTP 400. - required: false - schema: - type: number - format: double - nullable: true - - name: valueMax - in: query - description: >- - Inclusive upper bound on the numeric value. Requires - dataType=NUMERIC as a single value; otherwise the request returns - HTTP 400. - required: false - schema: - type: number - format: double - nullable: true - - name: traceId - in: query - description: >- - Comma-separated list of trace IDs to filter by. Mutually exclusive - with sessionId, experimentId. May be combined with observationId to - scope the observation lookup to a specific trace. - required: false - schema: - type: string - nullable: true - - name: sessionId - in: query - description: >- - Comma-separated list of session IDs to filter by. Mutually exclusive - with traceId, observationId, experimentId. - required: false - schema: - type: string - nullable: true - - name: observationId - in: query - description: >- - Comma-separated list of observation IDs to filter by. Requires - traceId to be specified, because observation IDs are scoped to a - trace. Mutually exclusive with sessionId, experimentId. Returns HTTP - 400 when used without traceId. - required: false - schema: - type: string - nullable: true - - name: experimentId - in: query - description: >- - Comma-separated list of dataset run IDs (experiment IDs) to filter - by. Mutually exclusive with traceId, sessionId, observationId. - required: false - schema: - type: string - nullable: true - - name: fromTimestamp - in: query - description: Inclusive lower bound on the score timestamp. - required: false - schema: - type: string - format: date-time - nullable: true - - name: toTimestamp - in: query - description: Exclusive upper bound on the score timestamp. - required: false - schema: - type: string - format: date-time - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/GetScoresV3Response' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/v2/scores: - get: - description: |- - **Deprecated.** Use `GET /api/public/v3/scores` instead. This endpoint - is no longer available on Langfuse v4 and later. - - Get a list of scores (supports both trace and session scores) - operationId: scores_get-many - tags: - - Scores - parameters: - - name: page - in: query - description: Page number, starts at 1. - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: >- - Limit of items per page. Maximum 100. Defaults to 50. Requests with - a limit greater than 100 return HTTP 400. If you encounter api - issues due to too large page sizes, try to reduce the limit. - required: false - schema: - type: integer - nullable: true - - name: userId - in: query - description: Retrieve only scores with this userId associated to the trace. - required: false - schema: - type: string - nullable: true - - name: name - in: query - description: Retrieve only scores with this name. - required: false - schema: - type: string - nullable: true - - name: fromTimestamp - in: query - description: >- - Optional filter to only include scores created on or after a certain - datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: toTimestamp - in: query - description: >- - Optional filter to only include scores created before a certain - datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: environment - in: query - description: >- - Optional filter for scores where the environment is one of the - provided values. - required: false - schema: - type: array - items: - type: string - nullable: true - - name: source - in: query - description: Retrieve only scores from a specific source. - required: false - schema: - $ref: '#/components/schemas/ScoreSource' - nullable: true - - name: operator - in: query - description: Retrieve only scores with value. - required: false - schema: - type: string - nullable: true - - name: value - in: query - description: Retrieve only scores with value. - required: false - schema: - type: number - format: double - nullable: true - - name: scoreIds - in: query - description: Comma-separated list of score IDs to limit the results to. - required: false - schema: - type: string - nullable: true - - name: configId - in: query - description: Retrieve only scores with a specific configId. - required: false - schema: - type: string - nullable: true - - name: sessionId - in: query - description: Retrieve only scores with a specific sessionId. - required: false - schema: - type: string - nullable: true - - name: datasetRunId - in: query - description: Retrieve only scores with a specific datasetRunId. - required: false - schema: - type: string - nullable: true - - name: traceId - in: query - description: Retrieve only scores with a specific traceId. - required: false - schema: - type: string - nullable: true - - name: observationId - in: query - description: Comma-separated list of observation IDs to filter scores by. - required: false - schema: - type: string - nullable: true - - name: queueId - in: query - description: Retrieve only scores with a specific annotation queueId. - required: false - schema: - type: string - nullable: true - - name: dataType - in: query - description: Retrieve only scores with a specific dataType. - required: false - schema: - $ref: '#/components/schemas/ScoreDataType' - nullable: true - - name: traceTags - in: query - description: >- - Only scores linked to traces that include all of these tags will be - returned. - required: false - schema: - type: array - items: - type: string - nullable: true - - name: fields - in: query - description: >- - Comma-separated list of field groups to include in the response. - Available field groups: 'score' (core score fields), 'trace' (trace - properties: userId, tags, environment, sessionId). If not specified, - both 'score' and 'trace' are returned by default. Example: 'score' - to exclude trace data, 'score,trace' to include both. Note: When - filtering by trace properties (using userId or traceTags - parameters), the 'trace' field group must be included, otherwise a - 400 error will be returned. - required: false - schema: - type: string - nullable: true - - name: filter - in: query - description: >- - A JSON stringified array of filter objects. Each object requires - type, column, operator, and value. Supports filtering by score - metadata using the stringObject type. Example: - [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. - Supported types: stringObject (metadata key-value filtering), - string, number, datetime, stringOptions, arrayOptions. Supported - operators for stringObject: =, contains, does not contain, starts - with, ends with. - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/GetScoresResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/v2/scores/{scoreId}: - get: - description: |- - **Deprecated.** Use `GET /api/public/v3/scores` with the `id` filter - instead. This endpoint is no longer available on Langfuse v4 and later. - - Get a score (supports both trace and session scores) - operationId: scores_get-by-id - tags: - - Scores - parameters: - - name: scoreId - in: path - description: The unique langfuse identifier of a score - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Score' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/sessions: - get: - description: >- - Get sessions. - - - This legacy endpoint is not recommended for new data extraction - workflows. - - Use the v2 observations endpoint with a bounded time range and group - rows by - - `sessionId` instead: - - `GET /api/public/v2/observations?fromStartTime=&toStartTime=`. - operationId: sessions_list - tags: - - Sessions - parameters: - - name: page - in: query - description: Page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: >- - Limit of items per page. If you encounter api issues due to too - large page sizes, try to reduce the limit. - required: false - schema: - type: integer - nullable: true - - name: fromTimestamp - in: query - description: >- - Optional filter to only include sessions created on or after a - certain datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: toTimestamp - in: query - description: >- - Optional filter to only include sessions created before a certain - datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: environment - in: query - description: >- - Optional filter for sessions where the environment is one of the - provided values. - required: false - schema: - type: array - items: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/PaginatedSessions' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/sessions/{sessionId}: - get: - description: >- - Get a session. - - - Please note that `traces` on this endpoint are not paginated. For large - - sessions or new data extraction workflows, use the v2 observations - endpoint - - with a URL-encoded `sessionId` filter and a bounded time range: - - `GET /api/public/v2/observations?filter=&fromStartTime=&toStartTime=`. - operationId: sessions_get - tags: - - Sessions - parameters: - - name: sessionId - in: path - description: The unique id of a session - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/SessionWithTraces' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/traces/{traceId}: - get: - description: Get a specific trace - operationId: trace_get - tags: - - Trace - parameters: - - name: traceId - in: path - description: The unique langfuse identifier of a trace - required: true - schema: - type: string - - name: fields - in: query - description: >- - Comma-separated list of fields to include in the response. Available - field groups: 'core' (always included), 'io' (input, output, - metadata), 'scores', 'observations', 'metrics'. If not specified, - all fields are returned. Example: 'core,scores,metrics'. Note: - Excluded 'observations' or 'scores' fields return empty arrays; - excluded 'metrics' returns -1 for 'totalCost' and 'latency'. - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/TraceWithFullDetails' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: Delete a specific trace - operationId: trace_delete - tags: - - Trace - parameters: - - name: traceId - in: path - description: The unique langfuse identifier of the trace to delete - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteTraceResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - /api/public/traces: - get: - description: Get list of traces - operationId: trace_list - tags: - - Trace - parameters: - - name: page - in: query - description: Page number, starts at 1 - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: >- - Limit of items per page. If you encounter api issues due to too - large page sizes, try to reduce the limit. - required: false - schema: - type: integer - nullable: true - - name: userId - in: query - required: false - schema: - type: string - nullable: true - - name: name - in: query - required: false - schema: - type: string - nullable: true - - name: sessionId - in: query - required: false - schema: - type: string - nullable: true - - name: fromTimestamp - in: query - description: >- - Optional filter to only include traces with a trace.timestamp on or - after a certain datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: toTimestamp - in: query - description: >- - Optional filter to only include traces with a trace.timestamp before - a certain datetime (ISO 8601) - required: false - schema: - type: string - format: date-time - nullable: true - - name: orderBy - in: query - description: >- - Format of the string [field].[asc/desc]. Fields: id, timestamp, - name, userId, release, version, public, bookmarked, sessionId. - Example: timestamp.asc - required: false - schema: - type: string - nullable: true - - name: tags - in: query - description: Only traces that include all of these tags will be returned. - required: false - schema: - type: array - items: - type: string - nullable: true - - name: version - in: query - description: Optional filter to only include traces with a certain version. - required: false - schema: - type: string - nullable: true - - name: release - in: query - description: Optional filter to only include traces with a certain release. - required: false - schema: - type: string - nullable: true - - name: environment - in: query - description: >- - Optional filter for traces where the environment is one of the - provided values. - required: false - schema: - type: array - items: - type: string - nullable: true - - name: fields - in: query - description: >- - Comma-separated list of fields to include in the response. Available - field groups: 'core' (always included), 'io' (input, output, - metadata), 'scores', 'observations', 'metrics'. If not specified, - all fields are returned. Example: 'core,scores,metrics'. Note: - Excluded 'observations' or 'scores' fields return empty arrays; - excluded 'metrics' returns -1 for 'totalCost' and 'latency'. - required: false - schema: - type: string - nullable: true - - name: filter - in: query - description: >- - JSON string containing an array of filter conditions. When provided, - this takes precedence over query parameter filters (userId, name, - sessionId, tags, version, release, environment, fromTimestamp, - toTimestamp). - - - ## Filter Structure - - Each filter condition has the following structure: - - ```json - - [ - { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" - "column": string, // Required. Column to filter on (see available columns below) - "operator": string, // Required. Operator based on type: - // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" - // - arrayOptions: "any of", "none of", "all of" - // - number: "=", ">", "<", ">=", "<=" - // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" - // - numberObject: "=", ">", "<", ">=", "<=" - // - boolean: "=", "<>" - // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata - } - ] - - ``` - - - ## Available Columns - - - ### Core Trace Fields - - - `id` (string) - Trace ID - - - `name` (string) - Trace name - - - `timestamp` (datetime) - Trace timestamp - - - `userId` (string) - User ID - - - `sessionId` (string) - Session ID - - - `environment` (string) - Environment tag - - - `version` (string) - Version tag - - - `release` (string) - Release tag - - - `tags` (arrayOptions) - Array of tags - - - `bookmarked` (boolean) - Bookmark status - - - ### Structured Data - - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata - key-value pairs. Use `key` parameter to filter on specific metadata - keys. - - - ### Aggregated Metrics (from observations) - - These metrics are aggregated from all observations within the trace: - - - `latency` (number) - Latency in seconds (time from first - observation start to last observation end) - - - `inputTokens` (number) - Total input tokens across all - observations - - - `outputTokens` (number) - Total output tokens across all - observations - - - `totalTokens` (number) - Total tokens (alias: `tokens`) - - - `inputCost` (number) - Total input cost in USD - - - `outputCost` (number) - Total output cost in USD - - - `totalCost` (number) - Total cost in USD - - - ### Observation Level Aggregations - - These fields aggregate observation levels within the trace: - - - `level` (string) - Highest severity level (ERROR > WARNING > - DEFAULT > DEBUG) - - - `warningCount` (number) - Count of WARNING level observations - - - `errorCount` (number) - Count of ERROR level observations - - - `defaultCount` (number) - Count of DEFAULT level observations - - - `debugCount` (number) - Count of DEBUG level observations - - - ### Scores (requires join with scores table) - - - `scores_avg` (number) - Average of numeric scores (alias: - `scores`) - - - `score_categories` (categoryOptions) - Categorical score values - - - ## Filter Examples - - ```json - - [ - { - "type": "datetime", - "column": "timestamp", - "operator": ">=", - "value": "2024-01-01T00:00:00Z" - }, - { - "type": "string", - "column": "userId", - "operator": "=", - "value": "user-123" - }, - { - "type": "number", - "column": "totalCost", - "operator": ">=", - "value": 0.01 - }, - { - "type": "arrayOptions", - "column": "tags", - "operator": "all of", - "value": ["production", "critical"] - }, - { - "type": "stringObject", - "column": "metadata", - "key": "customer_tier", - "operator": "=", - "value": "enterprise" - } - ] - - ``` - - - ## Performance Notes - - - Filtering on `userId`, `sessionId`, or `metadata` may enable skip - indexes for better query performance - - - Score filters require a join with the scores table and may impact - query performance - required: false - schema: - type: string - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/Traces' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - delete: - description: Delete multiple traces - operationId: trace_deleteMultiple - tags: - - Trace - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/DeleteTraceResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - traceIds: - type: array - items: - type: string - description: List of trace IDs to delete - required: - - traceIds - /api/public/unstable/dashboard-widgets: - post: - description: >- - Create a reusable dashboard widget. - - - This endpoint creates the widget. It does not place the widget on a - dashboard grid, this has to be done in the UI. - - - Supported views are `observations`, `scores-numeric`, and - `scores-categorical`. - - The legacy `traces` view is not supported by this unstable API, - `minVersion` defaults to `2`; values below `2` are rejected. - - - Unstable API note: - - - This surface may evolve while dashboard/widget APIs are being - finalized. - operationId: unstable_dashboardWidgets_create - tags: - - UnstableDashboardWidgets - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableDashboardWidget' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/unstableCreateDashboardWidgetRequest' - /api/public/unstable/evaluation-rules: - post: - description: >- - Create an evaluation rule. - - - An evaluation rule defines **what** incoming data should be evaluated - and **how prompt variables should be populated** from that data. - - - Use this resource after choosing an evaluator from the evaluator - endpoints. - - - Key rules: - - - `name` must be unique within the project for public evaluation rules - - - `target` must be `observation` or `experiment` - - - `evaluator.name` + `evaluator.scope` must identify an existing - evaluator family returned by the evaluator endpoints - - - Langfuse resolves that family to its latest version before saving the - evaluation rule - - - for `target=experiment`, use dataset `id` values from `GET - /api/public/v2/datasets` when filtering by `datasetId` - - - for `llm_as_judge` evaluators, every evaluator prompt variable must be - mapped exactly once - - - for `code` evaluators, Langfuse uses the fixed code runtime mapping; - omit `mapping` in create and update requests - - - for user-provided `llm_as_judge` mappings, `expected_output` and - `experiment_item_metadata` are only valid for `target=experiment` - - - if `enabled=true`, Langfuse validates that the referenced evaluator - can currently run - - - at most 50 evaluation rules can be effectively active in one project - at the same time - - - If an evaluation rule with the same `name` already exists in the - project, the API returns `409`. - - In that case, update the existing resource with `PATCH - /api/public/unstable/evaluation-rules/{evaluationRuleId}` instead of - creating a second one. - - - If enabling this resource would exceed the 50-active limit, the API also - returns `409`. - - In that case, disable or pause another active evaluation rule before - enabling a new one. - - - Current scope: - - - evaluation rules are live-ingestion rules only - - - they do not trigger historical backfills - - - Recovery guidance: - - - `400 invalid_filter_value`: fix the filter `column` or `value` using - `details.column`, `details.invalidValues`, and `details.allowedValues` - - - `400 invalid_filter_value` with `details.column=datasetId`: call `GET - /api/public/v2/datasets`, then retry with dataset `id` values from that - response - - - `400 missing_variable_mapping`: for `llm_as_judge` evaluators, fetch - the evaluator again and make sure every variable in `variables` appears - exactly once in `mapping` - - - `400 duplicate_variable_mapping`: remove repeated mappings for the - same variable - - - `400 invalid_variable_mapping`: for `llm_as_judge`, switch to a valid - `source` for the selected `target`, or fix the variable name - - - `400 invalid_json_path`: remove or correct the `jsonPath` - - - `422 evaluator_preflight_failed`: the selected evaluator cannot run - with the resolved model configuration. Fix the evaluator/default model - setup, then retry the create request. - operationId: unstable_evaluationRules_create - tags: - - UnstableEvaluationRules - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluationRule' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '409': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '422': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/unstableCreateEvaluationRuleRequest' - get: - description: >- - List evaluation rules in the authenticated project. - - - Each item describes one live evaluation rule and its effective runtime - status. - operationId: unstable_evaluationRules_list - tags: - - UnstableEvaluationRules - parameters: - - name: page - in: query - description: 1-based page number. Defaults to `1`. - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: Maximum number of items per page. Defaults to `50`. - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluationRules' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - /api/public/unstable/evaluation-rules/{evaluationRuleId}: - get: - description: >- - Get one evaluation rule by its identifier. - - - Use this endpoint to inspect the current evaluator, target, mapping, - filters, and effective runtime status. - operationId: unstable_evaluationRules_get - tags: - - UnstableEvaluationRules - parameters: - - name: evaluationRuleId - in: path - description: >- - Evaluation rule identifier returned by the evaluation rule endpoints. - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluationRule' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - patch: - description: >- - Update an evaluation rule. - - - Typical uses: - - - enable or disable live execution - - - switch to another evaluator - - - adjust sampling - - - change filters - - - update LLM-as-judge variable mappings - - - Important behavior: - - - provide only the fields you want to change - - - if you provide `evaluator`, Langfuse resolves that evaluator family to - its latest version before saving - - - changing `target`, `filter`, or an LLM-as-judge `mapping` must still - produce a valid target-specific configuration - - - if you change `target` for an LLM-as-judge rule, also send a - compatible `filter` and `mapping` in the same request unless the - existing ones are still valid for the new target - - - for `code` evaluator rules, omit `mapping`; Langfuse stores the fixed - code runtime mapping automatically - - - if the resulting config is enabled, Langfuse re-validates that the - selected evaluator can run - - - if the update would move a non-active evaluation rule into the active - state and the project already has 50 active evaluation rules, the API - returns `409` - - - Recovery guidance: - - - if an LLM-as-judge update fails with `missing_variable_mapping` or - `invalid_variable_mapping` after changing `evaluator` or `target`, - resend the request with a complete new `mapping` - - - if the update fails with `invalid_filter_value` after changing - `target`, resend the request with a target-compatible `filter` - operationId: unstable_evaluationRules_update - tags: - - UnstableEvaluationRules - parameters: - - name: evaluationRuleId - in: path - description: Evaluation rule identifier. - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluationRule' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '422': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/unstableUpdateEvaluationRuleRequest' - delete: - description: >- - Delete an evaluation rule. - - - This removes the live-ingestion rule only. It does not delete the - referenced evaluator. - operationId: unstable_evaluationRules_delete - tags: - - UnstableEvaluationRules - parameters: - - name: evaluationRuleId - in: path - description: Evaluation rule identifier. - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableDeleteEvaluationRuleResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - /api/public/unstable/evaluators: - post: - description: >- - Create an evaluator in the authenticated project. - - - Use evaluators to define **how** Langfuse should score data. - - LLM-as-a-judge evaluators define a prompt, expected structured output, - and optional model configuration. - - Code evaluators define source code and a runtime language. - - - Naming behavior: - - - If this is a new evaluator name in your project, Langfuse creates - version `1`. - - - If the name already exists in your project, Langfuse creates the next - version and returns it. - - - When a new project version is created, existing evaluation rules in - that project automatically move to the newest version for that evaluator - name. - - - Recommended workflow: - - 1. Create the evaluator. - - 2. Read the returned `variables` array. - - 3. Read the returned `outputDefinition.dataType` so the client knows - whether future scores will be numeric, boolean, or categorical. - - 4. Create one or more evaluation rules that reference the returned - evaluator family using `name` and `scope`. - - - Code evaluator validation: - - - At creation, Langfuse only validates the request shape - - - The `sourceCode` itself is not executed here. It is first run - (preflight-tested against a sample observation) when you link the - evaluator to an evaluation rule, so runtime errors in the code surface - at evaluation-rule creation, not at evaluator creation. - - - Recovery guidance: - - - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run - with the resolved model configuration. Add a valid explicit - `modelConfig`, or configure the project's default evaluation model, then - retry the same request. - - - `400` with `code=invalid_body`: the request shape is malformed. Use - the structured `details.issues` array to fix the specific fields and - retry. - - - `400` with `code=invalid_body` on `outputDefinition`: for - `type=llm_as_judge`, send `dataType`, `reasoning.description`, and - `score.description`. Do not send `version`; it is not part of the public - request shape. - - - If `type` is omitted, Langfuse treats the request as - `type=llm_as_judge` for backwards compatibility. New clients should send - `type` explicitly. - - - Unstable API note: - - - This surface may evolve while the underlying evaluation data model is - being redesigned. - operationId: unstable_evaluators_create - tags: - - UnstableEvaluators - parameters: [] - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluator' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '409': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '422': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/unstableCreateEvaluatorRequest' - get: - description: >- - List the evaluators available to the authenticated project. - - - Important behavior: - - - This endpoint returns the latest version of each available evaluator. - - - Results can include evaluators from your project and Langfuse-managed - evaluators. - - - If the same evaluator name exists in both places, both are returned as - separate items with different `scope` values. - operationId: unstable_evaluators_list - tags: - - UnstableEvaluators - parameters: - - name: page - in: query - description: 1-based page number. Defaults to `1`. - required: false - schema: - type: integer - nullable: true - - name: limit - in: query - description: Maximum number of items per page. Defaults to `50`. - required: false - schema: - type: integer - nullable: true - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluators' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - /api/public/unstable/evaluators/{evaluatorId}: - get: - description: >- - Get one evaluator by `id`. - - - Use this endpoint when you want the prompt, output definition, model - configuration, and derived variables for the evaluator you plan to use - in an evaluation rule. - operationId: unstable_evaluators_get - tags: - - UnstableEvaluators - parameters: - - name: evaluatorId - in: path - description: Evaluator identifier returned by the evaluator endpoints. - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableEvaluator' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 - delete: - description: >- - Delete an evaluator. - - - Important behavior: - - - This deletes the evaluator including all of its stored versions; - `evaluatorId` may reference any version. - - - The API returns `409` while evaluation rules still reference the - evaluator. Delete those evaluation rules first. - - - Langfuse-managed evaluators (`scope=managed`) cannot be deleted; the - API returns `403`. - - - Scores already produced by the evaluator are not deleted. - operationId: unstable_evaluators_delete - tags: - - UnstableEvaluators - parameters: - - name: evaluatorId - in: path - description: Evaluator identifier returned by the evaluator endpoints. - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstableDeleteEvaluatorResponse' - '400': - description: '' - content: - application/json: - schema: {} - '401': - description: '' - content: - application/json: - schema: {} - '403': - description: '' - content: - application/json: - schema: {} - '404': - description: '' - content: - application/json: - schema: {} - '405': - description: '' - content: - application/json: - schema: {} - '409': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '429': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - '500': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/unstablePublicApiError' - security: *ref_0 -components: - schemas: - AnnotationQueueStatus: - title: AnnotationQueueStatus - type: string - enum: - - PENDING - - COMPLETED - AnnotationQueueObjectType: - title: AnnotationQueueObjectType - type: string - enum: - - TRACE - - OBSERVATION - - SESSION - AnnotationQueue: - title: AnnotationQueue - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - nullable: true - scoreConfigIds: - type: array - items: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - name - - description - - scoreConfigIds - - createdAt - - updatedAt - AnnotationQueueItem: - title: AnnotationQueueItem - type: object - properties: - id: - type: string - queueId: - type: string - objectId: - type: string - objectType: - $ref: '#/components/schemas/AnnotationQueueObjectType' - status: - $ref: '#/components/schemas/AnnotationQueueStatus' - completedAt: - type: string - format: date-time - nullable: true - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - queueId - - objectId - - objectType - - status - - createdAt - - updatedAt - PaginatedAnnotationQueues: - title: PaginatedAnnotationQueues - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/AnnotationQueue' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - PaginatedAnnotationQueueItems: - title: PaginatedAnnotationQueueItems - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/AnnotationQueueItem' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - CreateAnnotationQueueRequest: - title: CreateAnnotationQueueRequest - type: object - properties: - name: - type: string - description: - type: string - nullable: true - scoreConfigIds: - type: array - items: - type: string - required: - - name - - scoreConfigIds - CreateAnnotationQueueItemRequest: - title: CreateAnnotationQueueItemRequest - type: object - properties: - objectId: - type: string - objectType: - $ref: '#/components/schemas/AnnotationQueueObjectType' - status: - $ref: '#/components/schemas/AnnotationQueueStatus' - nullable: true - description: Defaults to PENDING for new queue items - required: - - objectId - - objectType - UpdateAnnotationQueueItemRequest: - title: UpdateAnnotationQueueItemRequest - type: object - properties: - status: - $ref: '#/components/schemas/AnnotationQueueStatus' - nullable: true - DeleteAnnotationQueueItemResponse: - title: DeleteAnnotationQueueItemResponse - type: object - properties: - success: - type: boolean - message: - type: string - required: - - success - - message - AnnotationQueueAssignmentRequest: - title: AnnotationQueueAssignmentRequest - type: object - properties: - userId: - type: string - required: - - userId - DeleteAnnotationQueueAssignmentResponse: - title: DeleteAnnotationQueueAssignmentResponse - type: object - properties: - success: - type: boolean - required: - - success - CreateAnnotationQueueAssignmentResponse: - title: CreateAnnotationQueueAssignmentResponse - type: object - properties: - userId: - type: string - queueId: - type: string - projectId: - type: string - required: - - userId - - queueId - - projectId - BlobStorageIntegrationType: - title: BlobStorageIntegrationType - type: string - enum: - - S3 - - S3_COMPATIBLE - - AZURE_BLOB_STORAGE - BlobStorageIntegrationFileType: - title: BlobStorageIntegrationFileType - type: string - enum: - - JSON - - CSV - - JSONL - BlobStorageIntegrationFileTypeResponse: - title: BlobStorageIntegrationFileTypeResponse - type: string - enum: - - JSON - - CSV - - JSONL - - PARQUET - description: >- - File type reported for an existing integration. Includes `PARQUET`, - which a project may enable through the Langfuse UI but cannot yet be set - via this API (the request `fileType` omits it). - BlobStorageExportMode: - title: BlobStorageExportMode - type: string - enum: - - FULL_HISTORY - - FROM_TODAY - - FROM_CUSTOM_DATE - BlobStorageExportFrequency: - title: BlobStorageExportFrequency - type: string - enum: - - every_20_minutes - - hourly - - daily - - weekly - BlobStorageExportSource: - title: BlobStorageExportSource - type: string - enum: - - LEGACY_TRACES_OBSERVATIONS - - OBSERVATIONS_V2 - - LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS - description: >- - What data the integration exports. - - - `LEGACY_TRACES_OBSERVATIONS`: traces, observations, and scores tables. - Observation columns are controlled by `exportFieldGroups`; field groups - without a counterpart in this data model (e.g. `trace_context`) are - omitted. - - - `OBSERVATIONS_V2`: same data model as the - `/api/public/v2/observations` endpoint, plus scores. Columns are - controlled by `exportFieldGroups`. - - - `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: both sets. Observation - columns of both portions are controlled by `exportFieldGroups`. - - - **Note:** `OBSERVATIONS_V2` and the enriched-observations portion of - `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` rely on the enriched - observations table (Langfuse Fast Preview / v4), which is currently - available on Langfuse Cloud only. See https://langfuse.com/docs/v4. - BlobStorageExportFieldGroup: - title: BlobStorageExportFieldGroup - type: string - enum: - - core - - basic - - time - - io - - metadata - - model - - usage - - prompt - - metrics - - tools - - trace_context - description: >- - Field group selecting which observation columns are included in the - export. Applies to all export sources; groups without a counterpart in - the legacy data model (e.g. `trace_context`) are omitted from the legacy - observations export. - CreateBlobStorageIntegrationRequest: - title: CreateBlobStorageIntegrationRequest - type: object - properties: - projectId: - type: string - description: ID of the project in which to configure the blob storage integration - type: - $ref: '#/components/schemas/BlobStorageIntegrationType' - bucketName: - type: string - description: >- - Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid - Azure container name (3-63 chars, lowercase letters, numbers, and - hyphens only, must start and end with a letter or number, no - consecutive hyphens). - endpoint: - type: string - nullable: true - description: Custom endpoint URL (required for S3_COMPATIBLE type) - region: - type: string - description: Storage region - accessKeyId: - type: string - nullable: true - description: Access key ID for authentication - secretAccessKey: - type: string - nullable: true - description: Secret access key for authentication (will be encrypted when stored) - prefix: - type: string - nullable: true - description: >- - Path prefix for exported files (must end with forward slash if - provided) - exportFrequency: - $ref: '#/components/schemas/BlobStorageExportFrequency' - enabled: - type: boolean - description: Whether the integration is active - forcePathStyle: - type: boolean - description: Use path-style URLs for S3 requests - fileType: - $ref: '#/components/schemas/BlobStorageIntegrationFileType' - exportMode: - $ref: '#/components/schemas/BlobStorageExportMode' - exportStartDate: - type: string - format: date-time - nullable: true - description: >- - Custom start date for exports (required when exportMode is - FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for - timezone differences). - compressed: - type: boolean - nullable: true - description: >- - Enable gzip compression for exported files (.csv.gz, .json.gz, - .jsonl.gz). Defaults to true. - exportSource: - $ref: '#/components/schemas/BlobStorageExportSource' - nullable: true - description: >- - Data to export. When omitted on update, the existing value is - preserved. When omitted on create: integrations on Langfuse Cloud - default to `OBSERVATIONS_V2`; self-hosted deployments fall back to - `LEGACY_TRACES_OBSERVATIONS`. Required when `exportFieldGroups` is - provided. - - - **Cloud-only project deprecation gate (effective 2026-05-20):** For - projects created on or after 2026-05-20 on Langfuse Cloud, - `LEGACY_TRACES_OBSERVATIONS` and - `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP - 400. Use `OBSERVATIONS_V2` for all new integrations. Self-hosted - deployments are unaffected. - - - **Cloud-only integration deprecation gate (effective 2026-06-22):** - On Langfuse Cloud, legacy export sources are only accepted for blob - storage integrations created before 2026-06-22, regardless of - project age. Requests that would create a new integration with - `LEGACY_TRACES_OBSERVATIONS` or - `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP - 400. Use `OBSERVATIONS_V2` instead. Self-hosted deployments are - unaffected. - exportFieldGroups: - type: array - items: - $ref: '#/components/schemas/BlobStorageExportFieldGroup' - nullable: true - description: >- - Field groups to include in each exported observation row. Applies to - all export sources; must include `core` if provided. When omitted on - create, the column default (all groups) applies. When omitted on - update, the existing value is preserved. - - - `exportFieldGroups` requires `exportSource` to be provided in the - same request. - required: - - projectId - - type - - bucketName - - region - - exportFrequency - - enabled - - forcePathStyle - - fileType - - exportMode - BlobStorageIntegrationResponse: - title: BlobStorageIntegrationResponse - type: object - properties: - id: - type: string - projectId: - type: string - type: - $ref: '#/components/schemas/BlobStorageIntegrationType' - bucketName: - type: string - endpoint: - type: string - nullable: true - region: - type: string - accessKeyId: - type: string - nullable: true - prefix: - type: string - exportFrequency: - $ref: '#/components/schemas/BlobStorageExportFrequency' - enabled: - type: boolean - forcePathStyle: - type: boolean - fileType: - $ref: '#/components/schemas/BlobStorageIntegrationFileTypeResponse' - exportMode: - $ref: '#/components/schemas/BlobStorageExportMode' - exportStartDate: - type: string - format: date-time - nullable: true - compressed: - type: boolean - exportSource: - $ref: '#/components/schemas/BlobStorageExportSource' - exportFieldGroups: - type: array - items: - $ref: '#/components/schemas/BlobStorageExportFieldGroup' - nullable: true - description: >- - Field groups included in each exported observation row. An empty - list is treated as all groups during export. - nextSyncAt: - type: string - format: date-time - nullable: true - lastSyncAt: - type: string - format: date-time - nullable: true - lastError: - type: string - nullable: true - lastErrorAt: - type: string - format: date-time - nullable: true - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - projectId - - type - - bucketName - - endpoint - - region - - accessKeyId - - prefix - - exportFrequency - - enabled - - forcePathStyle - - fileType - - exportMode - - exportStartDate - - compressed - - exportSource - - exportFieldGroups - - nextSyncAt - - lastSyncAt - - lastError - - lastErrorAt - - createdAt - - updatedAt - BlobStorageIntegrationsResponse: - title: BlobStorageIntegrationsResponse - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/BlobStorageIntegrationResponse' - required: - - data - BlobStorageSyncStatus: - title: BlobStorageSyncStatus - type: string - enum: - - idle - - running - - queued - - up_to_date - - disabled - - error - description: >- - Sync status of the blob storage integration: - - - `disabled` — integration is not enabled - - - `error` — last export failed (see `lastError` for details) - - - `running` — an export job is currently being processed - - - `queued` — next export is overdue (`nextSyncAt` is in the past) and - waiting to be picked up by the worker - - - `idle` — enabled but has never exported yet and no export is queued - - - `up_to_date` — all available data has been exported; next export is - scheduled for the future - - - **ETL usage**: poll this endpoint and check for `up_to_date` status. - Compare `lastSyncAt` against your - - ETL bookmark to determine if new data is available. Note that exports - run with a 20-minute lag buffer, - - so `lastSyncAt` will always be at least 20 minutes behind real-time. - BlobStorageIntegrationStatusResponse: - title: BlobStorageIntegrationStatusResponse - type: object - properties: - id: - type: string - projectId: - type: string - syncStatus: - $ref: '#/components/schemas/BlobStorageSyncStatus' - enabled: - type: boolean - lastSyncAt: - type: string - format: date-time - nullable: true - description: >- - End of the last successfully exported time window. Compare against - your ETL bookmark to determine if new data is available. Null if the - integration has never synced. - nextSyncAt: - type: string - format: date-time - nullable: true - description: When the next export is scheduled. Null if no sync has occurred yet. - lastError: - type: string - nullable: true - description: >- - Raw error message from the storage provider (S3/Azure/GCS) if the - last export failed. Cleared on successful export. - lastErrorAt: - type: string - format: date-time - nullable: true - description: When the last error occurred. Cleared on successful export. - required: - - id - - projectId - - syncStatus - - enabled - - lastSyncAt - - nextSyncAt - - lastError - - lastErrorAt - BlobStorageIntegrationDeletionResponse: - title: BlobStorageIntegrationDeletionResponse - type: object - properties: - message: - type: string - required: - - message - CreateCommentRequest: - title: CreateCommentRequest - type: object - properties: - projectId: - type: string - description: The id of the project to attach the comment to. - objectType: - type: string - description: >- - The type of the object to attach the comment to (trace, observation, - session, prompt). - objectId: - type: string - description: >- - The id of the object to attach the comment to. If this does not - reference a valid existing object, an error will be thrown. - content: - type: string - description: >- - The content of the comment. May include markdown. Currently limited - to 5000 characters. - authorUserId: - type: string - nullable: true - description: The id of the user who created the comment. - required: - - projectId - - objectType - - objectId - - content - CreateCommentResponse: - title: CreateCommentResponse - type: object - properties: - id: - type: string - description: The id of the created object in Langfuse - required: - - id - GetCommentsResponse: - title: GetCommentsResponse - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Comment' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - Trace: - title: Trace - type: object - properties: - id: - type: string - description: The unique identifier of a trace - timestamp: - type: string - format: date-time - description: The timestamp when the trace was created - name: - type: string - nullable: true - description: The name of the trace - input: - nullable: true - description: The input data of the trace. Can be any JSON. - output: - nullable: true - description: The output data of the trace. Can be any JSON. - sessionId: - type: string - nullable: true - description: The session identifier associated with the trace - release: - type: string - nullable: true - description: The release version of the application when the trace was created - version: - type: string - nullable: true - description: The version of the trace - userId: - type: string - nullable: true - description: The user identifier associated with the trace - metadata: - nullable: true - description: The metadata associated with the trace. Can be any JSON. - tags: - type: array - items: - type: string - description: The tags associated with the trace. - public: - type: boolean - description: Public traces are accessible via url without login - environment: - type: string - description: >- - The environment from which this trace originated. Can be any - lowercase alphanumeric string with hyphens and underscores that does - not start with 'langfuse'. - required: - - id - - timestamp - - name - - sessionId - - release - - version - - userId - - tags - - public - - environment - TraceWithDetails: - title: TraceWithDetails - type: object - properties: - htmlPath: - type: string - description: Path of trace in Langfuse UI - latency: - type: number - format: double - nullable: true - description: Latency of trace in seconds - totalCost: - type: number - format: double - nullable: true - description: Cost of trace in USD - observations: - type: array - items: - type: string - nullable: true - description: List of observation ids - scores: - type: array - items: - type: string - nullable: true - description: List of score ids - required: - - htmlPath - allOf: - - $ref: '#/components/schemas/Trace' - TraceWithFullDetails: - title: TraceWithFullDetails - type: object - properties: - htmlPath: - type: string - description: Path of trace in Langfuse UI - latency: - type: number - format: double - nullable: true - description: Latency of trace in seconds - totalCost: - type: number - format: double - nullable: true - description: Cost of trace in USD - observations: - type: array - items: - $ref: '#/components/schemas/ObservationsView' - description: List of observations - scores: - type: array - items: - $ref: '#/components/schemas/ScoreV1' - description: List of scores - required: - - htmlPath - - observations - - scores - allOf: - - $ref: '#/components/schemas/Trace' - Session: - title: Session - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - projectId: - type: string - environment: - type: string - description: The environment from which this session originated. - required: - - id - - createdAt - - projectId - - environment - SessionWithTraces: - title: SessionWithTraces - type: object - properties: - traces: - type: array - items: - $ref: '#/components/schemas/Trace' - required: - - traces - allOf: - - $ref: '#/components/schemas/Session' - Observation: - title: Observation - type: object - properties: - id: - type: string - description: The unique identifier of the observation - traceId: - type: string - nullable: true - description: The trace ID associated with the observation - type: - type: string - description: The type of the observation - name: - type: string - nullable: true - description: The name of the observation - startTime: - type: string - format: date-time - description: The start time of the observation - endTime: - type: string - format: date-time - nullable: true - description: The end time of the observation. - completionStartTime: - type: string - format: date-time - nullable: true - description: The completion start time of the observation - model: - type: string - nullable: true - description: The model used for the observation - modelParameters: - description: The parameters of the model used for the observation - input: - description: The input data of the observation - version: - type: string - nullable: true - description: The version of the observation - metadata: - description: Additional metadata of the observation - output: - description: The output data of the observation - usage: - $ref: '#/components/schemas/Usage' - description: >- - (Deprecated. Use usageDetails and costDetails instead.) The usage - data of the observation - level: - $ref: '#/components/schemas/ObservationLevel' - description: The level of the observation - statusMessage: - type: string - nullable: true - description: The status message of the observation - parentObservationId: - type: string - nullable: true - description: The parent observation ID - promptId: - type: string - nullable: true - description: The prompt ID associated with the observation - usageDetails: - type: object - additionalProperties: - type: integer - description: >- - The usage details of the observation. Key is the name of the usage - metric, value is the number of units consumed. The total key is the - sum of all (non-total) usage metrics or the total value ingested. - costDetails: - type: object - additionalProperties: - type: number - format: double - description: >- - The cost details of the observation. Key is the name of the cost - metric, value is the cost in USD. The total key is the sum of all - (non-total) cost metrics or the total value ingested. - environment: - type: string - description: >- - The environment from which this observation originated. Can be any - lowercase alphanumeric string with hyphens and underscores that does - not start with 'langfuse'. - required: - - id - - traceId - - type - - name - - startTime - - endTime - - completionStartTime - - model - - modelParameters - - input - - version - - metadata - - output - - usage - - level - - statusMessage - - parentObservationId - - promptId - - usageDetails - - costDetails - - environment - ObservationsView: - title: ObservationsView - type: object - properties: - promptName: - type: string - nullable: true - description: The name of the prompt associated with the observation - promptVersion: - type: integer - nullable: true - description: The version of the prompt associated with the observation - modelId: - type: string - nullable: true - description: The unique identifier of the model - inputPrice: - type: number - format: double - nullable: true - description: The price of the input in USD - outputPrice: - type: number - format: double - nullable: true - description: The price of the output in USD. - totalPrice: - type: number - format: double - nullable: true - description: The total price in USD. - calculatedInputCost: - type: number - format: double - nullable: true - description: >- - (Deprecated. Use usageDetails and costDetails instead.) The - calculated cost of the input in USD - calculatedOutputCost: - type: number - format: double - nullable: true - description: >- - (Deprecated. Use usageDetails and costDetails instead.) The - calculated cost of the output in USD - calculatedTotalCost: - type: number - format: double - nullable: true - description: >- - (Deprecated. Use usageDetails and costDetails instead.) The - calculated total cost in USD - latency: - type: number - format: double - nullable: true - description: The latency in seconds. - timeToFirstToken: - type: number - format: double - nullable: true - description: The time to the first token in seconds - required: - - promptName - - promptVersion - - modelId - - inputPrice - - outputPrice - - totalPrice - - calculatedInputCost - - calculatedOutputCost - - calculatedTotalCost - - latency - - timeToFirstToken - allOf: - - $ref: '#/components/schemas/Observation' - ObservationV2: - title: ObservationV2 - type: object - description: >- - An observation from the v2 API with field-group-based selection. - - Core fields are always present. Other fields are included only when - their field group is requested. - properties: - id: - type: string - description: The unique identifier of the observation - traceId: - type: string - nullable: true - description: The trace ID associated with the observation - startTime: - type: string - format: date-time - description: The start time of the observation - endTime: - type: string - format: date-time - nullable: true - description: The end time of the observation - projectId: - type: string - description: The project ID this observation belongs to - parentObservationId: - type: string - nullable: true - description: The parent observation ID - type: - type: string - description: The type of the observation (e.g. GENERATION, SPAN, EVENT) - name: - type: string - nullable: true - description: The name of the observation - level: - $ref: '#/components/schemas/ObservationLevel' - nullable: true - description: The level of the observation - statusMessage: - type: string - nullable: true - description: The status message of the observation - version: - type: string - nullable: true - description: The version of the observation - environment: - type: string - nullable: true - description: The environment from which this observation originated - bookmarked: - type: boolean - nullable: true - description: Whether the observation is bookmarked - public: - type: boolean - nullable: true - description: Whether the observation is public - userId: - type: string - nullable: true - description: The user ID associated with the observation - sessionId: - type: string - nullable: true - description: The session ID associated with the observation - completionStartTime: - type: string - format: date-time - nullable: true - description: The completion start time of the observation - createdAt: - type: string - format: date-time - nullable: true - description: The creation timestamp of the observation - updatedAt: - type: string - format: date-time - nullable: true - description: The last update timestamp of the observation - input: - nullable: true - description: The input data of the observation - output: - nullable: true - description: The output data of the observation - metadata: - nullable: true - description: Additional metadata of the observation - providedModelName: - type: string - nullable: true - description: The model name as provided by the user - internalModelId: - type: string - nullable: true - description: The internal model ID matched by Langfuse - modelParameters: - nullable: true - description: The parameters of the model used for the observation - usageDetails: - type: object - additionalProperties: - type: integer - nullable: true - description: >- - The usage details of the observation. Key is the usage metric name, - value is the number of units consumed. - costDetails: - type: object - additionalProperties: - type: number - format: double - nullable: true - description: >- - The cost details of the observation. Key is the cost metric name, - value is the cost in USD. - totalCost: - type: number - format: double - nullable: true - description: The total cost of the observation in USD - usagePricingTierName: - type: string - nullable: true - description: >- - The name of the pricing tier applied to this observation's usage - costs - promptId: - type: string - nullable: true - description: The prompt ID associated with the observation - promptName: - type: string - nullable: true - description: The prompt name associated with the observation - promptVersion: - type: integer - nullable: true - description: The prompt version associated with the observation - latency: - type: number - format: double - nullable: true - description: The latency in seconds - timeToFirstToken: - type: number - format: double - nullable: true - description: The time to first token in seconds - modelId: - type: string - nullable: true - description: >- - The matched model ID. Null when the `model` field group is not - requested. - inputPrice: - type: string - nullable: true - description: >- - The input token price (USD per unit) from the matched model, - serialized as a decimal string (e.g. "0.0001"). Null when the - `model` field group is not requested. - outputPrice: - type: string - nullable: true - description: >- - The output token price (USD per unit) from the matched model, - serialized as a decimal string (e.g. "0.0001"). Null when the - `model` field group is not requested. - totalPrice: - type: string - nullable: true - description: >- - The total token price (USD per unit) from the matched model, - serialized as a decimal string (e.g. "0.0001"). Null when the - `model` field group is not requested. - traceName: - type: string - nullable: true - description: The name of the parent trace - tags: - type: array - items: - type: string - nullable: true - description: Tags from the parent trace (denormalized onto the observation) - release: - type: string - nullable: true - description: The release version of the parent trace - required: - - id - - traceId - - startTime - - endTime - - projectId - - parentObservationId - - type - - modelId - - inputPrice - - outputPrice - - totalPrice - Usage: - title: Usage - type: object - description: >- - (Deprecated. Use usageDetails and costDetails instead.) Standard - interface for usage and cost - properties: - input: - type: integer - description: Number of input units (e.g. tokens) - output: - type: integer - description: Number of output units (e.g. tokens) - total: - type: integer - description: Defaults to input+output if not set - unit: - type: string - nullable: true - description: Unit of measurement - inputCost: - type: number - format: double - nullable: true - description: USD input cost - outputCost: - type: number - format: double - nullable: true - description: USD output cost - totalCost: - type: number - format: double - nullable: true - description: USD total cost, defaults to input+output - required: - - input - - output - - total - - unit - ScoreConfig: - title: ScoreConfig - type: object - description: Configuration for a score - properties: - id: - type: string - name: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - projectId: - type: string - dataType: - $ref: '#/components/schemas/ScoreConfigDataType' - isArchived: - type: boolean - description: Whether the score config is archived. Defaults to false - minValue: - type: number - format: double - nullable: true - description: >- - Sets minimum value for numerical scores. If not set, the minimum - value defaults to -∞ - maxValue: - type: number - format: double - nullable: true - description: >- - Sets maximum value for numerical scores. If not set, the maximum - value defaults to +∞ - categories: - type: array - items: - $ref: '#/components/schemas/ConfigCategory' - nullable: true - description: Configures custom categories for categorical scores - description: - type: string - nullable: true - description: Description of the score config - required: - - id - - name - - createdAt - - updatedAt - - projectId - - dataType - - isArchived - ConfigCategory: - title: ConfigCategory - type: object - properties: - value: - type: number - format: double - label: - type: string - required: - - value - - label - BaseScoreV1: - title: BaseScoreV1 - type: object - properties: - id: - type: string - traceId: - type: string - name: - type: string - source: - $ref: '#/components/schemas/ScoreSource' - observationId: - type: string - nullable: true - description: The observation ID associated with the score - timestamp: - type: string - format: date-time - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - authorUserId: - type: string - nullable: true - description: The user ID of the author - comment: - type: string - nullable: true - description: Comment on the score - metadata: - description: Metadata associated with the score - configId: - type: string - nullable: true - description: >- - Reference a score config on a score. When set, config and score name - must be equal and value must comply to optionally defined numerical - range - queueId: - type: string - nullable: true - description: >- - The annotation queue referenced by the score. Indicates if score was - initially created while processing annotation queue. - environment: - type: string - description: >- - The environment from which this score originated. Can be any - lowercase alphanumeric string with hyphens and underscores that does - not start with 'langfuse'. - required: - - id - - traceId - - name - - source - - timestamp - - createdAt - - updatedAt - - authorUserId - - comment - - metadata - - configId - - queueId - - environment - NumericScoreV1: - title: NumericScoreV1 - type: object - properties: - value: - type: number - format: double - description: The numeric value of the score - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScoreV1' - BooleanScoreV1: - title: BooleanScoreV1 - type: object - properties: - value: - type: number - format: double - description: >- - The numeric value of the score. Equals 1 for "True" and 0 for "False" - stringValue: - type: string - description: >- - The string representation of the score value. Is inferred from the - numeric value and equals "True" or "False" - required: - - value - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScoreV1' - CategoricalScoreV1: - title: CategoricalScoreV1 - type: object - properties: - value: - type: number - format: double - description: >- - Represents the numeric category mapping of the stringValue. If no - config is linked, defaults to 0. - stringValue: - type: string - description: >- - The string representation of the score value. If no config is - linked, can be any string. Otherwise, must map to a config category - required: - - value - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScoreV1' - TextScoreV1: - title: TextScoreV1 - type: object - properties: - stringValue: - type: string - description: The text content of the score (1-500 characters) - required: - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScoreV1' - ScoreV1: - title: ScoreV1 - type: object - properties: - dataType: - type: string - enum: - - NUMERIC - - CATEGORICAL - - BOOLEAN - - TEXT - value: - type: string - description: The numeric value of the score - stringValue: - type: string - description: The string representation of the score value. If no config is - linked, can be any string. Otherwise, must map to a config category - required: - - dataType - BaseScore: - title: BaseScore - type: object - properties: - id: - type: string - traceId: - type: string - nullable: true - description: The trace ID associated with the score - sessionId: - type: string - nullable: true - description: The session ID associated with the score - observationId: - type: string - nullable: true - description: The observation ID associated with the score - datasetRunId: - type: string - nullable: true - description: The dataset run ID associated with the score - name: - type: string - source: - $ref: '#/components/schemas/ScoreSource' - timestamp: - type: string - format: date-time - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - authorUserId: - type: string - nullable: true - description: The user ID of the author - comment: - type: string - nullable: true - description: Comment on the score - metadata: - description: Metadata associated with the score - configId: - type: string - nullable: true - description: >- - Reference a score config on a score. When set, config and score name - must be equal and value must comply to optionally defined numerical - range - queueId: - type: string - nullable: true - description: >- - The annotation queue referenced by the score. Indicates if score was - initially created while processing annotation queue. - environment: - type: string - description: >- - The environment from which this score originated. Can be any - lowercase alphanumeric string with hyphens and underscores that does - not start with 'langfuse'. - required: - - id - - name - - source - - timestamp - - createdAt - - updatedAt - - authorUserId - - comment - - metadata - - configId - - queueId - - environment - NumericScore: - title: NumericScore - type: object - properties: - value: - type: number - format: double - description: The numeric value of the score - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScore' - BooleanScore: - title: BooleanScore - type: object - properties: - value: - type: number - format: double - description: >- - The numeric value of the score. Equals 1 for "True" and 0 for "False" - stringValue: - type: string - description: >- - The string representation of the score value. Is inferred from the - numeric value and equals "True" or "False" - required: - - value - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScore' - CategoricalScore: - title: CategoricalScore - type: object - properties: - value: - type: number - format: double - description: >- - Represents the numeric category mapping of the stringValue. If no - config is linked, defaults to 0. - stringValue: - type: string - description: >- - The string representation of the score value. If no config is - linked, can be any string. Otherwise, must map to a config category - required: - - value - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScore' - CorrectionScore: - title: CorrectionScore - type: object - properties: - value: - type: number - format: double - description: The numeric value of the score. Always 0 for correction scores. - stringValue: - type: string - description: The string representation of the correction content - required: - - value - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScore' - TextScore: - title: TextScore - type: object - properties: - stringValue: - type: string - description: The text content of the score (1-500 characters) - required: - - stringValue - allOf: - - $ref: '#/components/schemas/BaseScore' - Score: - title: Score - type: object - properties: - dataType: - type: string - enum: - - NUMERIC - - CATEGORICAL - - BOOLEAN - - CORRECTION - - TEXT - value: - type: string - description: The numeric value of the score - stringValue: - type: string - description: The string representation of the score value. If no config is - linked, can be any string. Otherwise, must map to a config category - required: - - dataType - CreateScoreValue: - title: CreateScoreValue - oneOf: - - type: number - format: double - - type: string - description: >- - The value of the score. Must be passed as string for categorical and - text scores, and numeric for boolean and numeric scores - Comment: - title: Comment - type: object - properties: - id: - type: string - projectId: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - objectType: - $ref: '#/components/schemas/CommentObjectType' - objectId: - type: string - content: - type: string - authorUserId: - type: string - nullable: true - description: The user ID of the comment author - required: - - id - - projectId - - createdAt - - updatedAt - - objectType - - objectId - - content - Dataset: - title: Dataset - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - nullable: true - description: Description of the dataset - metadata: - description: Metadata associated with the dataset - inputSchema: - nullable: true - description: JSON Schema for validating dataset item inputs - expectedOutputSchema: - nullable: true - description: JSON Schema for validating dataset item expected outputs - projectId: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - name - - description - - metadata - - inputSchema - - expectedOutputSchema - - projectId - - createdAt - - updatedAt - DatasetItem: - title: DatasetItem - type: object - properties: - id: - type: string - status: - $ref: '#/components/schemas/DatasetStatus' - input: - description: Input data for the dataset item - expectedOutput: - description: Expected output for the dataset item - metadata: - description: Metadata associated with the dataset item - sourceTraceId: - type: string - nullable: true - description: The trace ID that sourced this dataset item - sourceObservationId: - type: string - nullable: true - description: The observation ID that sourced this dataset item - datasetId: - type: string - datasetName: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - mediaReferences: - type: array - items: - $ref: '#/components/schemas/DatasetItemMediaReference' - description: >- - Resolved Langfuse media references found in input, expectedOutput, - and metadata. - required: - - id - - status - - input - - expectedOutput - - metadata - - sourceTraceId - - sourceObservationId - - datasetId - - datasetName - - createdAt - - updatedAt - - mediaReferences - DatasetItemMediaReference: - title: DatasetItemMediaReference - type: object - properties: - field: - $ref: '#/components/schemas/DatasetItemMediaReferenceField' - description: The dataset item field containing the reference - referenceString: - type: string - description: >- - The Langfuse media reference string, e.g. - `@@@langfuseMedia:type=image/png|id=...|source=bytes@@@` - jsonPath: - type: string - description: >- - JSONPath of the string holding the reference within the field, e.g. - `$['image']` - media: - $ref: '#/components/schemas/DatasetItemMediaReferenceMedia' - description: The resolved media record. - required: - - field - - referenceString - - jsonPath - - media - DatasetItemMediaReferenceField: - title: DatasetItemMediaReferenceField - type: string - enum: - - input - - expectedOutput - - metadata - DatasetItemMediaReferenceMedia: - title: DatasetItemMediaReferenceMedia - type: object - properties: - mediaId: - type: string - description: The unique langfuse identifier of the media record - contentType: - type: string - description: The MIME type of the media record - contentLength: - type: integer - description: The size of the media record in bytes - url: - type: string - description: The signed download URL of the media record - urlExpiry: - type: string - description: The expiry date and time of the download URL - required: - - mediaId - - contentType - - contentLength - - url - - urlExpiry - DatasetRunItem: - title: DatasetRunItem - type: object - properties: - id: - type: string - datasetRunId: - type: string - datasetRunName: - type: string - datasetItemId: - type: string - traceId: - type: string - observationId: - type: string - nullable: true - description: The observation ID associated with this run item - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - datasetRunId - - datasetRunName - - datasetItemId - - traceId - - observationId - - createdAt - - updatedAt - DatasetRun: - title: DatasetRun - type: object - properties: - id: - type: string - description: Unique identifier of the dataset run - name: - type: string - description: Name of the dataset run - description: - type: string - nullable: true - description: Description of the run - metadata: - description: Metadata of the dataset run - datasetId: - type: string - description: Id of the associated dataset - datasetName: - type: string - description: Name of the associated dataset - createdAt: - type: string - format: date-time - description: The date and time when the dataset run was created - updatedAt: - type: string - format: date-time - description: The date and time when the dataset run was last updated - required: - - id - - name - - description - - metadata - - datasetId - - datasetName - - createdAt - - updatedAt - DatasetRunWithItems: - title: DatasetRunWithItems - type: object - properties: - datasetRunItems: - type: array - items: - $ref: '#/components/schemas/DatasetRunItem' - required: - - datasetRunItems - allOf: - - $ref: '#/components/schemas/DatasetRun' - Model: - title: Model - type: object - description: >- - Model definition used for transforming usage into USD cost and/or - tokenization. - - - Models can have either simple flat pricing or tiered pricing: - - - Flat pricing: Single price per usage type (legacy, but still - supported) - - - Tiered pricing: Multiple pricing tiers with conditional matching based - on usage patterns - - - The pricing tiers approach is recommended for models with usage-based - pricing variations. - - When using tiered pricing, the flat price fields (inputPrice, - outputPrice, prices) are populated - - from the default tier for backward compatibility. - properties: - id: - type: string - modelName: - type: string - description: >- - Name of the model definition. If multiple with the same name exist, - they are applied in the following order: (1) custom over built-in, - (2) newest according to startTime where - model.startTime- - Regex pattern which matches this model definition to - generation.model. Useful in case of fine-tuned models. If you want - to exact match, use `(?i)^modelname$` - startDate: - type: string - format: date-time - nullable: true - description: Apply only to generations which are newer than this ISO date. - unit: - $ref: '#/components/schemas/ModelUsageUnit' - nullable: true - description: Unit used by this model. - inputPrice: - type: number - format: double - nullable: true - description: Deprecated. See 'prices' instead. Price (USD) per input unit - outputPrice: - type: number - format: double - nullable: true - description: Deprecated. See 'prices' instead. Price (USD) per output unit - totalPrice: - type: number - format: double - nullable: true - description: >- - Deprecated. See 'prices' instead. Price (USD) per total unit. Cannot - be set if input or output price is set. - tokenizerId: - type: string - nullable: true - description: >- - Optional. Tokenizer to be applied to observations which match to - this model. See docs for more details. - tokenizerConfig: - description: >- - Optional. Configuration for the selected tokenizer. Needs to be - JSON. See docs for more details. - isLangfuseManaged: - type: boolean - createdAt: - type: string - format: date-time - description: Timestamp when the model was created - prices: - type: object - additionalProperties: - $ref: '#/components/schemas/ModelPrice' - description: >- - Deprecated. Use 'pricingTiers' instead for models with usage-based - pricing variations. - - - This field shows prices by usage type from the default pricing tier. - Maintained for backward compatibility. - - If the model uses tiered pricing, this field will be populated from - the default tier's prices. - pricingTiers: - type: array - items: - $ref: '#/components/schemas/PricingTier' - description: >- - Array of pricing tiers with conditional pricing based on usage - thresholds. - - - Pricing tiers enable accurate cost tracking for models that charge - different rates based on usage patterns - - (e.g., different rates for high-volume usage, large context windows, - or cached tokens). - - - Each model must have exactly one default tier (isDefault=true, - priority=0) that serves as a fallback. - - Additional conditional tiers can be defined with specific matching - criteria. - - - If this array is empty, the model uses legacy flat pricing from the - inputPrice/outputPrice/totalPrice fields. - required: - - id - - modelName - - matchPattern - - startDate - - inputPrice - - outputPrice - - totalPrice - - tokenizerId - - tokenizerConfig - - isLangfuseManaged - - createdAt - - prices - - pricingTiers - ModelPrice: - title: ModelPrice - type: object - properties: - price: - type: number - format: double - required: - - price - PricingTierCondition: - title: PricingTierCondition - type: object - description: >- - Condition for matching a pricing tier based on usage details. Used to - implement tiered pricing models where costs vary based on usage - thresholds. - - - How it works: - - 1. The regex pattern matches against usage detail keys (e.g., - "input_tokens", "input_cached") - - 2. Values of all matching keys are summed together - - 3. The sum is compared against the threshold value using the specified - operator - - 4. All conditions in a tier must be met (AND logic) for the tier to - match - - - Common use cases: - - - Threshold-based pricing: Match when accumulated usage exceeds a - certain amount - - - Usage-type-specific pricing: Different rates for cached vs non-cached - tokens, or input vs output - - - Volume-based pricing: Different rates based on total request or token - count - properties: - usageDetailPattern: - type: string - description: >- - Regex pattern to match against usage detail keys. All matching keys' - values are summed for threshold comparison. - - - Examples: - - - "^input" matches "input", "input_tokens", "input_cached", etc. - - - "^(input|prompt)" matches both "input_tokens" and "prompt_tokens" - - - "_cache$" matches "input_cache", "output_cache", etc. - - - The pattern is case-insensitive by default. If no keys match, the - sum is treated as zero. - operator: - $ref: '#/components/schemas/PricingTierOperator' - description: >- - Comparison operator to apply between the summed value and the - threshold. - - - - gt: greater than (sum > threshold) - - - gte: greater than or equal (sum >= threshold) - - - lt: less than (sum < threshold) - - - lte: less than or equal (sum <= threshold) - - - eq: equal (sum == threshold) - - - neq: not equal (sum != threshold) - value: - type: number - format: double - description: >- - Threshold value for comparison. For token-based pricing, this is - typically the token count threshold (e.g., 200000 for a 200K token - threshold). - caseSensitive: - type: boolean - description: >- - Whether the regex pattern matching is case-sensitive. Default is - false (case-insensitive matching). - required: - - usageDetailPattern - - operator - - value - - caseSensitive - PricingTier: - title: PricingTier - type: object - description: >- - Pricing tier definition with conditional pricing based on usage - thresholds. - - - Pricing tiers enable accurate cost tracking for LLM providers that - charge different rates based on usage patterns. - - For example, some providers charge higher rates when context size - exceeds certain thresholds. - - - How tier matching works: - - 1. Tiers are evaluated in ascending priority order (priority 1 before - priority 2, etc.) - - 2. The first tier where ALL conditions match is selected - - 3. If no conditional tiers match, the default tier is used as a fallback - - 4. The default tier has priority 0 and no conditions - - - Why priorities matter: - - - Lower priority numbers are evaluated first, allowing you to define - specific cases before general ones - - - Example: Priority 1 for "high usage" (>200K tokens), Priority 2 for - "medium usage" (>100K tokens), Priority 0 for default - - - Without proper ordering, a less specific condition might match before - a more specific one - - - Every model must have exactly one default tier to ensure cost - calculation always succeeds. - properties: - id: - type: string - description: Unique identifier for the pricing tier - name: - type: string - description: >- - Name of the pricing tier for display and identification purposes. - - - Examples: "Standard", "High Volume Tier", "Large Context", "Extended - Context Tier" - isDefault: - type: boolean - description: >- - Whether this is the default tier. Every model must have exactly one - default tier with priority 0 and no conditions. - - - The default tier serves as a fallback when no conditional tiers - match, ensuring cost calculation always succeeds. - - It typically represents the base pricing for standard usage - patterns. - priority: - type: integer - description: >- - Priority for tier matching evaluation. Lower numbers = higher - priority (evaluated first). - - - The default tier must always have priority 0. Conditional tiers - should have priority 1, 2, 3, etc. - - - Example ordering: - - - Priority 0: Default tier (no conditions, always matches as - fallback) - - - Priority 1: High usage tier (e.g., >200K tokens) - - - Priority 2: Medium usage tier (e.g., >100K tokens) - - - This ensures more specific conditions are checked before general - ones. - conditions: - type: array - items: - $ref: '#/components/schemas/PricingTierCondition' - description: >- - Array of conditions that must ALL be met for this tier to match (AND - logic). - - - The default tier must have an empty conditions array. Conditional - tiers should have one or more conditions - - that define when this tier's pricing applies. - - - Multiple conditions enable complex matching scenarios (e.g., "high - input tokens AND low output tokens"). - prices: - type: object - additionalProperties: - type: number - format: double - description: >- - Prices (USD) by usage type for this tier. - - - Common usage types: "input", "output", "total", "request", "image" - - Prices are specified in USD per unit (e.g., per token, per request, - per second). - - - Example: {"input": 0.000003, "output": 0.000015} means $3 per - million input tokens and $15 per million output tokens. - required: - - id - - name - - isDefault - - priority - - conditions - - prices - PricingTierInput: - title: PricingTierInput - type: object - description: >- - Input schema for creating a pricing tier. The tier ID will be - automatically generated server-side. - - - When creating a model with pricing tiers: - - - Exactly one tier must have isDefault=true (the fallback tier) - - - The default tier must have priority=0 and conditions=[] - - - All tier names and priorities must be unique within the model - - - Each tier must define at least one price - - - See PricingTier for detailed information about how tiers work and why - they're useful. - properties: - name: - type: string - description: >- - Name of the pricing tier for display and identification purposes. - - - Must be unique within the model. Common patterns: "Standard", "High - Volume Tier", "Extended Context" - isDefault: - type: boolean - description: >- - Whether this is the default tier. Exactly one tier per model must be - marked as default. - - - Requirements for default tier: - - - Must have isDefault=true - - - Must have priority=0 - - - Must have empty conditions array (conditions=[]) - - - The default tier acts as a fallback when no conditional tiers match. - priority: - type: integer - description: >- - Priority for tier matching evaluation. Lower numbers = higher - priority (evaluated first). - - - Must be unique within the model. The default tier must have - priority=0. - - Conditional tiers should use priority 1, 2, 3, etc. based on their - specificity. - conditions: - type: array - items: - $ref: '#/components/schemas/PricingTierCondition' - description: >- - Array of conditions that must ALL be met for this tier to match (AND - logic). - - - The default tier must have an empty array (conditions=[]). - - Conditional tiers should define one or more conditions that specify - when this tier's pricing applies. - - - Each condition specifies a regex pattern, operator, and threshold - value for matching against usage details. - prices: - type: object - additionalProperties: - type: number - format: double - description: >- - Prices (USD) by usage type for this tier. At least one price must be - defined. - - - Common usage types: "input", "output", "total", "request", "image" - - Prices are in USD per unit (e.g., per token). - - - Example: {"input": 0.000003, "output": 0.000015} represents $3 per - million input tokens and $15 per million output tokens. - required: - - name - - isDefault - - priority - - conditions - - prices - PricingTierOperator: - title: PricingTierOperator - type: string - enum: - - gt - - gte - - lt - - lte - - eq - - neq - description: Comparison operators for pricing tier conditions - ModelUsageUnit: - title: ModelUsageUnit - type: string - enum: - - CHARACTERS - - TOKENS - - MILLISECONDS - - SECONDS - - IMAGES - - REQUESTS - description: Unit of usage in Langfuse - ObservationLevel: - title: ObservationLevel - type: string - enum: - - DEBUG - - DEFAULT - - WARNING - - ERROR - MapValue: - title: MapValue - oneOf: - - type: string - nullable: true - - type: integer - nullable: true - - type: number - format: float - nullable: true - - type: boolean - nullable: true - - type: array - items: - type: string - nullable: true - CommentObjectType: - title: CommentObjectType - type: string - enum: - - TRACE - - OBSERVATION - - SESSION - - PROMPT - DatasetStatus: - title: DatasetStatus - type: string - enum: - - ACTIVE - - ARCHIVED - ScoreSource: - title: ScoreSource - type: string - enum: - - ANNOTATION - - API - - EVAL - ScoreConfigDataType: - title: ScoreConfigDataType - type: string - enum: - - NUMERIC - - BOOLEAN - - CATEGORICAL - - TEXT - ScoreDataType: - title: ScoreDataType - type: string - enum: - - NUMERIC - - BOOLEAN - - CATEGORICAL - - CORRECTION - - TEXT - DeleteDatasetItemResponse: - title: DeleteDatasetItemResponse - type: object - properties: - message: - type: string - description: Success message after deletion - required: - - message - CreateDatasetItemRequest: - title: CreateDatasetItemRequest - type: object - properties: - datasetName: - type: string - input: - nullable: true - expectedOutput: - nullable: true - metadata: - nullable: true - sourceTraceId: - type: string - nullable: true - sourceObservationId: - type: string - nullable: true - id: - type: string - nullable: true - description: >- - Dataset items are upserted on their id. Id needs to be unique - (project-level), cannot be reused across datasets, and must be at - most 255 characters. - status: - $ref: '#/components/schemas/DatasetStatus' - nullable: true - description: Defaults to ACTIVE for newly created items - required: - - datasetName - PaginatedDatasetItems: - title: PaginatedDatasetItems - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/DatasetItem' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - CreateDatasetRunItemRequest: - title: CreateDatasetRunItemRequest - type: object - properties: - runName: - type: string - runDescription: - type: string - nullable: true - description: Description of the run. If run exists, description will be updated. - metadata: - nullable: true - description: Metadata of the dataset run, updates run if run already exists - datasetItemId: - type: string - observationId: - type: string - nullable: true - traceId: - type: string - nullable: true - description: >- - traceId should always be provided. For compatibility with older SDK - versions it can also be inferred from the provided observationId. - datasetVersion: - type: string - format: date-time - nullable: true - description: >- - ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., - "2026-01-21T14:35:42Z"). - - Specifies the dataset version to use for this experiment run. - - If provided, the experiment will use dataset items as they existed - at or before this timestamp. - - If not provided, uses the latest version of dataset items. - createdAt: - type: string - format: date-time - nullable: true - description: >- - Optional timestamp to set the createdAt field of the dataset run - item. If not provided or null, defaults to current timestamp. - required: - - runName - - datasetItemId - PaginatedDatasetRunItems: - title: PaginatedDatasetRunItems - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/DatasetRunItem' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - PaginatedDatasets: - title: PaginatedDatasets - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Dataset' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - CreateDatasetRequest: - title: CreateDatasetRequest - type: object - properties: - name: - type: string - description: - type: string - nullable: true - metadata: - nullable: true - inputSchema: - nullable: true - description: >- - JSON Schema for validating dataset item inputs. When set, all new - and existing dataset items will be validated against this schema. - expectedOutputSchema: - nullable: true - description: >- - JSON Schema for validating dataset item expected outputs. When set, - all new and existing dataset items will be validated against this - schema. - required: - - name - PaginatedDatasetRuns: - title: PaginatedDatasetRuns - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/DatasetRun' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - DeleteDatasetRunResponse: - title: DeleteDatasetRunResponse - type: object - properties: - message: - type: string - required: - - message - ExperimentsResponse: - title: ExperimentsResponse - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Experiment' - meta: - $ref: '#/components/schemas/ExperimentsResponseMeta' - required: - - data - - meta - ExperimentsResponseMeta: - title: ExperimentsResponseMeta - type: object - properties: - cursor: - type: string - nullable: true - description: >- - Versioned base64url cursor for retrieving the next page. Absent when - there are no more results. - Experiment: - title: Experiment - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - nullable: true - startTime: - type: string - format: date-time - description: |- - Start of the experiment, i.e. the earliest event within the - requested time range. Clipped to `fromStartTime` when the - experiment started before the requested range. - endTime: - type: string - format: date-time - description: |- - End of the experiment, i.e. the latest event end within the - requested time range. - itemCount: - type: integer - description: Number of experiment items within the requested time range. - datasetId: - type: string - nullable: true - description: Null when the experiment is not associated with a dataset. - metadata: - type: object - additionalProperties: true - nullable: true - description: Included only when `fields=metadata` is requested. - scores: - type: array - items: - $ref: '#/components/schemas/ScoreV3' - nullable: true - description: >- - Included only when `fields=scores` is requested. Contains scores - directly attached to the experiment. - required: - - id - - name - - description - - startTime - - endTime - - itemCount - - datasetId - ExperimentItemsResponse: - title: ExperimentItemsResponse - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/ExperimentItem' - meta: - $ref: '#/components/schemas/ExperimentsResponseMeta' - required: - - data - - meta - ExperimentItem: - title: ExperimentItem - type: object - properties: - id: - type: string - traceId: - type: string - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - nullable: true - level: - $ref: '#/components/schemas/ObservationLevel' - environment: - type: string - experimentId: - type: string - experimentName: - type: string - experimentItemId: - type: string - experimentDatasetId: - type: string - nullable: true - description: Included when `fields=dataset` is requested. - experimentItemVersion: - type: string - format: date-time - nullable: true - description: Included when `fields=dataset` is requested. - input: - nullable: true - description: Included when `fields=io` is requested. - output: - nullable: true - description: Included when `fields=io` is requested. - expectedOutput: - nullable: true - description: Included when `fields=io` is requested. - metadata: - type: object - additionalProperties: true - nullable: true - description: Included when `fields=metadata` is requested. - experimentItemMetadata: - type: object - additionalProperties: true - nullable: true - description: Included when `fields=itemMetadata` is requested. - experimentMetadata: - type: object - additionalProperties: true - nullable: true - description: Included when `fields=experimentMetadata` is requested. - experimentDescription: - type: string - nullable: true - description: Included when `fields=experimentMetadata` is requested. - scores: - type: array - items: - $ref: '#/components/schemas/ScoreV3' - nullable: true - description: >- - Included only when `fields=scores` is requested. Contains item and - trace scores only; experiment-level scores are returned by the - experiments endpoint. - required: - - id - - traceId - - startTime - - endTime - - level - - environment - - experimentId - - experimentName - - experimentItemId - HealthResponse: - title: HealthResponse - type: object - properties: - version: - type: string - description: Langfuse server version - status: - type: string - required: - - version - - status - IngestionEvent: - title: IngestionEvent - type: object - properties: - type: - type: string - enum: - - trace-create - - score-create - - span-create - - span-update - - generation-create - - generation-update - - event-create - - sdk-log - - observation-create - - observation-update - body: - type: string - required: - - type - - body - ObservationType: - title: ObservationType - type: string - enum: - - SPAN - - GENERATION - - EVENT - - AGENT - - TOOL - - CHAIN - - RETRIEVER - - EVALUATOR - - EMBEDDING - - GUARDRAIL - IngestionUsage: - title: IngestionUsage - oneOf: - - $ref: '#/components/schemas/Usage' - - $ref: '#/components/schemas/OpenAIUsage' - OpenAIUsage: - title: OpenAIUsage - type: object - description: Usage interface of OpenAI for improved compatibility. - properties: - promptTokens: - type: integer - nullable: true - completionTokens: - type: integer - nullable: true - totalTokens: - type: integer - nullable: true - OptionalObservationBody: - title: OptionalObservationBody - type: object - properties: - traceId: - type: string - nullable: true - name: - type: string - nullable: true - startTime: - type: string - format: date-time - nullable: true - metadata: - nullable: true - input: - nullable: true - output: - nullable: true - level: - $ref: '#/components/schemas/ObservationLevel' - nullable: true - statusMessage: - type: string - nullable: true - parentObservationId: - type: string - nullable: true - version: - type: string - nullable: true - environment: - type: string - nullable: true - CreateEventBody: - title: CreateEventBody - type: object - properties: - id: - type: string - nullable: true - allOf: - - $ref: '#/components/schemas/OptionalObservationBody' - UpdateEventBody: - title: UpdateEventBody - type: object - properties: - id: - type: string - required: - - id - allOf: - - $ref: '#/components/schemas/OptionalObservationBody' - CreateSpanBody: - title: CreateSpanBody - type: object - properties: - endTime: - type: string - format: date-time - nullable: true - allOf: - - $ref: '#/components/schemas/CreateEventBody' - UpdateSpanBody: - title: UpdateSpanBody - type: object - properties: - endTime: - type: string - format: date-time - nullable: true - allOf: - - $ref: '#/components/schemas/UpdateEventBody' - CreateGenerationBody: - title: CreateGenerationBody - type: object - properties: - completionStartTime: - type: string - format: date-time - nullable: true - model: - type: string - nullable: true - modelParameters: - type: object - additionalProperties: - $ref: '#/components/schemas/MapValue' - nullable: true - usage: - $ref: '#/components/schemas/IngestionUsage' - nullable: true - usageDetails: - $ref: '#/components/schemas/UsageDetails' - nullable: true - costDetails: - type: object - additionalProperties: - type: number - format: double - nullable: true - promptName: - type: string - nullable: true - promptVersion: - type: integer - nullable: true - allOf: - - $ref: '#/components/schemas/CreateSpanBody' - UpdateGenerationBody: - title: UpdateGenerationBody - type: object - properties: - completionStartTime: - type: string - format: date-time - nullable: true - model: - type: string - nullable: true - modelParameters: - type: object - additionalProperties: - $ref: '#/components/schemas/MapValue' - nullable: true - usage: - $ref: '#/components/schemas/IngestionUsage' - nullable: true - promptName: - type: string - nullable: true - usageDetails: - $ref: '#/components/schemas/UsageDetails' - nullable: true - costDetails: - type: object - additionalProperties: - type: number - format: double - nullable: true - promptVersion: - type: integer - nullable: true - allOf: - - $ref: '#/components/schemas/UpdateSpanBody' - ObservationBody: - title: ObservationBody - type: object - properties: - id: - type: string - nullable: true - traceId: - type: string - nullable: true - type: - $ref: '#/components/schemas/ObservationType' - name: - type: string - nullable: true - startTime: - type: string - format: date-time - nullable: true - endTime: - type: string - format: date-time - nullable: true - completionStartTime: - type: string - format: date-time - nullable: true - model: - type: string - nullable: true - modelParameters: - type: object - additionalProperties: - $ref: '#/components/schemas/MapValue' - nullable: true - input: - nullable: true - version: - type: string - nullable: true - metadata: - nullable: true - output: - nullable: true - usage: - $ref: '#/components/schemas/Usage' - nullable: true - level: - $ref: '#/components/schemas/ObservationLevel' - nullable: true - statusMessage: - type: string - nullable: true - parentObservationId: - type: string - nullable: true - environment: - type: string - nullable: true - required: - - type - TraceBody: - title: TraceBody - type: object - properties: - id: - type: string - nullable: true - timestamp: - type: string - format: date-time - nullable: true - name: - type: string - nullable: true - userId: - type: string - nullable: true - input: - nullable: true - output: - nullable: true - sessionId: - type: string - nullable: true - release: - type: string - nullable: true - version: - type: string - nullable: true - metadata: - nullable: true - tags: - type: array - items: - type: string - nullable: true - environment: - type: string - nullable: true - public: - type: boolean - nullable: true - description: Make trace publicly accessible via url - SDKLogBody: - title: SDKLogBody - type: object - properties: - log: {} - required: - - log - ScoreBody: - title: ScoreBody - type: object - properties: - id: - type: string - nullable: true - traceId: - type: string - nullable: true - sessionId: - type: string - nullable: true - observationId: - type: string - nullable: true - datasetRunId: - type: string - nullable: true - name: - type: string - description: >- - The name of the score. Always overrides "output" for correction - scores. - environment: - type: string - nullable: true - queueId: - type: string - nullable: true - description: >- - The annotation queue referenced by the score. Indicates if score was - initially created while processing annotation queue. - value: - $ref: '#/components/schemas/CreateScoreValue' - description: >- - The value of the score. Must be passed as string for categorical and - text scores, and numeric for boolean and numeric scores. Boolean - score values must equal either 1 or 0 (true or false). Text score - values must be between 1 and 500 characters. - comment: - type: string - nullable: true - metadata: - nullable: true - dataType: - $ref: '#/components/schemas/ScoreDataType' - nullable: true - description: >- - When set, must match the score value's type. If not set, will be - inferred from the score value or config - configId: - type: string - nullable: true - description: >- - Reference a score config on a score. When set, the score name must - equal the config name and scores must comply with the config's range - and data type. For categorical scores, the value must map to a - config category. Numeric scores might be constrained by the score - config's max and min values - required: - - name - - value - BaseEvent: - title: BaseEvent - type: object - properties: - id: - type: string - description: UUID v4 that identifies the event - timestamp: - type: string - description: >- - Datetime (ISO 8601) of event creation in client. Should be as close - to actual event creation in client as possible, this timestamp will - be used for ordering of events in future release. Resolution: - milliseconds (required), microseconds (optimal). - metadata: - nullable: true - description: Optional. Metadata field used by the Langfuse SDKs for debugging. - required: - - id - - timestamp - TraceEvent: - title: TraceEvent - type: object - properties: - body: - $ref: '#/components/schemas/TraceBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - CreateObservationEvent: - title: CreateObservationEvent - type: object - properties: - body: - $ref: '#/components/schemas/ObservationBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - UpdateObservationEvent: - title: UpdateObservationEvent - type: object - properties: - body: - $ref: '#/components/schemas/ObservationBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - ScoreEvent: - title: ScoreEvent - type: object - properties: - body: - $ref: '#/components/schemas/ScoreBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - SDKLogEvent: - title: SDKLogEvent - type: object - properties: - body: - $ref: '#/components/schemas/SDKLogBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - CreateGenerationEvent: - title: CreateGenerationEvent - type: object - properties: - body: - $ref: '#/components/schemas/CreateGenerationBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - UpdateGenerationEvent: - title: UpdateGenerationEvent - type: object - properties: - body: - $ref: '#/components/schemas/UpdateGenerationBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - CreateSpanEvent: - title: CreateSpanEvent - type: object - properties: - body: - $ref: '#/components/schemas/CreateSpanBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - UpdateSpanEvent: - title: UpdateSpanEvent - type: object - properties: - body: - $ref: '#/components/schemas/UpdateSpanBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - CreateEventEvent: - title: CreateEventEvent - type: object - properties: - body: - $ref: '#/components/schemas/CreateEventBody' - required: - - body - allOf: - - $ref: '#/components/schemas/BaseEvent' - IngestionSuccess: - title: IngestionSuccess - type: object - properties: - id: - type: string - status: - type: integer - required: - - id - - status - IngestionError: - title: IngestionError - type: object - properties: - id: - type: string - status: - type: integer - message: - type: string - nullable: true - error: - nullable: true - required: - - id - - status - IngestionResponse: - title: IngestionResponse - type: object - properties: - successes: - type: array - items: - $ref: '#/components/schemas/IngestionSuccess' - errors: - type: array - items: - $ref: '#/components/schemas/IngestionError' - required: - - successes - - errors - OpenAICompletionUsageSchema: - title: OpenAICompletionUsageSchema - type: object - description: OpenAI Usage schema from (Chat-)Completion APIs - properties: - prompt_tokens: - type: integer - completion_tokens: - type: integer - total_tokens: - type: integer - prompt_tokens_details: - type: object - additionalProperties: - type: integer - nullable: true - nullable: true - completion_tokens_details: - type: object - additionalProperties: - type: integer - nullable: true - nullable: true - required: - - prompt_tokens - - completion_tokens - - total_tokens - OpenAIResponseUsageSchema: - title: OpenAIResponseUsageSchema - type: object - description: OpenAI Usage schema from Response API - properties: - input_tokens: - type: integer - output_tokens: - type: integer - total_tokens: - type: integer - input_tokens_details: - type: object - additionalProperties: - type: integer - nullable: true - nullable: true - output_tokens_details: - type: object - additionalProperties: - type: integer - nullable: true - nullable: true - required: - - input_tokens - - output_tokens - - total_tokens - UsageDetails: - title: UsageDetails - oneOf: - - type: object - additionalProperties: - type: integer - - $ref: '#/components/schemas/OpenAICompletionUsageSchema' - - $ref: '#/components/schemas/OpenAIResponseUsageSchema' - legacyMetricsResponse: - title: legacyMetricsResponse - type: object - properties: - data: - type: array - items: - type: object - additionalProperties: true - description: >- - The metrics data. Each item in the list contains the metric values - and dimensions requested in the query. - - Format varies based on the query parameters. - - Histograms will return an array with [lower, upper, height] tuples. - required: - - data - legacyObservations: - title: legacyObservations - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Observation' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - legacyObservationsViews: - title: legacyObservationsViews - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/ObservationsView' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - legacyCreateScoreRequest: - title: legacyCreateScoreRequest - type: object - properties: - id: - type: string - nullable: true - traceId: - type: string - nullable: true - sessionId: - type: string - nullable: true - observationId: - type: string - nullable: true - datasetRunId: - type: string - nullable: true - name: - type: string - value: - $ref: '#/components/schemas/CreateScoreValue' - description: >- - The value of the score. Must be passed as string for categorical and - text scores, and numeric for boolean and numeric scores. Boolean - score values must equal either 1 or 0 (true or false). Text score - values must be between 1 and 500 characters. - comment: - type: string - nullable: true - metadata: - type: object - additionalProperties: true - nullable: true - environment: - type: string - nullable: true - description: >- - The environment of the score. Can be any lowercase alphanumeric - string with hyphens and underscores that does not start with - 'langfuse'. - queueId: - type: string - nullable: true - description: >- - The annotation queue referenced by the score. Indicates if score was - initially created while processing annotation queue. - dataType: - $ref: '#/components/schemas/ScoreDataType' - nullable: true - description: >- - The data type of the score. When passing a configId this field is - inferred. Otherwise, this field must be passed or will default to - numeric. - configId: - type: string - nullable: true - description: >- - Reference a score config on a score. The unique langfuse identifier - of a score config. When passing this field, the dataType and - stringValue fields are automatically populated. - source: - $ref: '#/components/schemas/legacyCreateScoreSource' - nullable: true - description: >- - The source of the score. Defaults to API. Set to ANNOTATION to - prefill scores (e.g. from an LLM) for a human reviewer to verify in - an annotation queue. When source is ANNOTATION, a configId is - required unless dataType is CORRECTION. EVAL is reserved for - internal evaluator outputs and is not accepted on this endpoint. - required: - - name - - value - legacyCreateScoreSource: - title: legacyCreateScoreSource - type: string - enum: - - API - - ANNOTATION - description: |- - Source values accepted when creating a score via the public REST API. - EVAL is reserved for internal evaluator outputs and is intentionally not - exposed here — use commons.ScoreSource when reading scores. - legacyCreateScoreResponse: - title: legacyCreateScoreResponse - type: object - properties: - id: - type: string - description: The id of the created object in Langfuse - required: - - id - LlmConnection: - title: LlmConnection - type: object - description: LLM API connection configuration (secrets excluded) - properties: - id: - type: string - provider: - type: string - description: >- - Provider name (e.g., 'openai', 'my-gateway'). Must be unique in - project, used for upserting. - adapter: - type: string - description: The adapter used to interface with the LLM - displaySecretKey: - type: string - description: Masked version of the secret key for display purposes - baseURL: - type: string - nullable: true - description: Custom base URL for the LLM API - customModels: - type: array - items: - type: string - description: List of custom model names available for this connection - withDefaultModels: - type: boolean - description: Whether to include default models for this adapter - extraHeaderKeys: - type: array - items: - type: string - description: >- - Keys of extra headers sent with requests (values excluded for - security) - config: - type: object - additionalProperties: true - nullable: true - description: >- - Adapter-specific configuration. Required for Bedrock - (`{"region":"us-east-1"}`), optional for OpenAI - (`{"useResponsesApi":true}`), optional for VertexAI - (`{"location":"us-central1"}`), not used by other adapters. - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - provider - - adapter - - displaySecretKey - - customModels - - withDefaultModels - - extraHeaderKeys - - createdAt - - updatedAt - PaginatedLlmConnections: - title: PaginatedLlmConnections - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/LlmConnection' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - UpsertLlmConnectionRequest: - title: UpsertLlmConnectionRequest - type: object - description: Request to create or update an LLM connection (upsert) - properties: - provider: - type: string - description: >- - Provider name (e.g., 'openai', 'my-gateway'). Must be unique in - project, used for upserting. - adapter: - $ref: '#/components/schemas/LlmAdapter' - description: The adapter used to interface with the LLM - secretKey: - type: string - description: Secret key for the LLM API. - baseURL: - type: string - nullable: true - description: Custom base URL for the LLM API - customModels: - type: array - items: - type: string - nullable: true - description: List of custom model names - withDefaultModels: - type: boolean - nullable: true - description: Whether to include default models. Default is true. - extraHeaders: - type: object - additionalProperties: - type: string - nullable: true - description: Extra headers to send with requests - config: - type: object - additionalProperties: true - nullable: true - description: >- - Adapter-specific configuration. Validation rules: - **Bedrock**: - Required. Must be `{"region": ""}` (e.g., - `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must - be `{"useResponsesApi": }` to control whether Langfuse - routes calls through OpenAI's Responses API. - **VertexAI**: - Optional. If provided, must be `{"location": ""}` - (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not - supported. Omit this field or set to null. - required: - - provider - - adapter - - secretKey - DeleteLlmConnectionResponse: - title: DeleteLlmConnectionResponse - type: object - properties: - message: - type: string - required: - - message - LlmAdapter: - title: LlmAdapter - type: string - enum: - - anthropic - - openai - - azure - - bedrock - - google-vertex-ai - - google-ai-studio - GetMediaResponse: - title: GetMediaResponse - type: object - properties: - mediaId: - type: string - description: The unique langfuse identifier of a media record - contentType: - type: string - description: The MIME type of the media record - contentLength: - type: integer - description: The size of the media record in bytes - uploadedAt: - type: string - format: date-time - description: The date and time when the media record was uploaded - url: - type: string - description: The download URL of the media record - urlExpiry: - type: string - description: The expiry date and time of the media record download URL - required: - - mediaId - - contentType - - contentLength - - uploadedAt - - url - - urlExpiry - PatchMediaBody: - title: PatchMediaBody - type: object - properties: - uploadedAt: - type: string - format: date-time - description: The date and time when the media record was uploaded - uploadHttpStatus: - type: integer - description: The HTTP status code of the upload - uploadHttpError: - type: string - nullable: true - description: The HTTP error message of the upload - uploadTimeMs: - type: integer - nullable: true - description: The time in milliseconds it took to upload the media record - required: - - uploadedAt - - uploadHttpStatus - GetMediaUploadUrlRequest: - title: GetMediaUploadUrlRequest - type: object - description: >- - Request a presigned media upload URL. Provide exactly one context: a - trace (traceId, optionally observationId) or a dataset item (datasetId + - datasetItemId). field is required and must match the chosen context. - properties: - traceId: - type: string - nullable: true - description: >- - The trace the media is associated with. Null for dataset item media - uploads. - observationId: - type: string - nullable: true - description: >- - The observation ID associated with the media record. If the media - record is associated directly with a trace, this will be null. - datasetId: - type: string - nullable: true - description: >- - The dataset the media belongs to. Null for trace/observation media - uploads. - datasetItemId: - type: string - nullable: true - description: >- - The dataset item the media is associated with (need not exist yet). - Null for trace/observation media uploads. - contentType: - $ref: '#/components/schemas/MediaContentType' - contentLength: - type: integer - description: The size of the media record in bytes - sha256Hash: - type: string - description: The SHA-256 hash of the media record - field: - type: string - description: >- - The item field the media is in: `input`/`output`/`metadata` (trace) - or `input`/`expectedOutput`/`metadata` (dataset item). - required: - - contentType - - contentLength - - sha256Hash - - field - GetMediaUploadUrlResponse: - title: GetMediaUploadUrlResponse - type: object - properties: - uploadUrl: - type: string - nullable: true - description: >- - The presigned upload URL. If the asset is already uploaded, this - will be null - mediaId: - type: string - description: The unique langfuse identifier of a media record - required: - - mediaId - MediaContentType: - title: MediaContentType - type: string - enum: - - image/png - - image/jpeg - - image/jpg - - image/webp - - image/gif - - image/svg+xml - - image/tiff - - image/bmp - - image/avif - - image/heic - - audio/mpeg - - audio/mp3 - - audio/wav - - audio/ogg - - audio/oga - - audio/aac - - audio/mp4 - - audio/flac - - audio/opus - - audio/webm - - video/mp4 - - video/webm - - video/ogg - - video/mpeg - - video/quicktime - - video/x-msvideo - - video/x-matroska - - text/plain - - text/html - - text/css - - text/csv - - text/markdown - - text/x-python - - application/javascript - - text/x-typescript - - application/x-yaml - - application/pdf - - application/msword - - application/vnd.ms-excel - - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - - application/zip - - application/json - - application/xml - - application/octet-stream - - >- - application/vnd.openxmlformats-officedocument.wordprocessingml.document - - >- - application/vnd.openxmlformats-officedocument.presentationml.presentation - - application/rtf - - application/x-ndjson - - application/vnd.apache.parquet - - application/gzip - - application/x-tar - - application/x-7z-compressed - description: The MIME type of the media record - MetricsV2Response: - title: MetricsV2Response - type: object - properties: - data: - type: array - items: - type: object - additionalProperties: true - description: >- - The metrics data. Each item in the list contains the metric values - and dimensions requested in the query. - - Format varies based on the query parameters. - - Histograms will return an array with [lower, upper, height] tuples. - required: - - data - PaginatedModels: - title: PaginatedModels - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Model' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - CreateModelRequest: - title: CreateModelRequest - type: object - properties: - modelName: - type: string - description: >- - Name of the model definition. If multiple with the same name exist, - they are applied in the following order: (1) custom over built-in, - (2) newest according to startTime where - model.startTime- - Regex pattern which matches this model definition to - generation.model. Useful in case of fine-tuned models. If you want - to exact match, use `(?i)^modelname$` - startDate: - type: string - format: date-time - nullable: true - description: Apply only to generations which are newer than this ISO date. - unit: - $ref: '#/components/schemas/ModelUsageUnit' - nullable: true - description: Unit used by this model. - inputPrice: - type: number - format: double - nullable: true - description: >- - Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. - Creates a default tier if pricingTiers not provided. - outputPrice: - type: number - format: double - nullable: true - description: >- - Deprecated. Use 'pricingTiers' instead. Price (USD) per output unit. - Creates a default tier if pricingTiers not provided. - totalPrice: - type: number - format: double - nullable: true - description: >- - Deprecated. Use 'pricingTiers' instead. Price (USD) per total units. - Cannot be set if input or output price is set. Creates a default - tier if pricingTiers not provided. - pricingTiers: - type: array - items: - $ref: '#/components/schemas/PricingTierInput' - nullable: true - description: >- - Optional. Array of pricing tiers for this model. - - - Use pricing tiers for all models - both those with threshold-based - pricing variations and those with simple flat pricing: - - - - For models with standard flat pricing: Create a single default - tier with your prices - (e.g., one tier with isDefault=true, priority=0, conditions=[], and your standard prices) - - - For models with threshold-based pricing: Create a default tier - plus additional conditional tiers - (e.g., default tier for standard usage + high-volume tier for usage above certain thresholds) - - Requirements: - - - Cannot be provided with flat prices - (inputPrice/outputPrice/totalPrice) - use one approach or the other - - - Must include exactly one default tier with isDefault=true, - priority=0, and conditions=[] - - - All tier names and priorities must be unique within the model - - - Each tier must define at least one price - - - If omitted, you must provide flat prices instead - (inputPrice/outputPrice/totalPrice), - - which will automatically create a single default tier named - "Standard". - tokenizerId: - type: string - nullable: true - description: >- - Optional. Tokenizer to be applied to observations which match to - this model. See docs for more details. - tokenizerConfig: - nullable: true - description: >- - Optional. Configuration for the selected tokenizer. Needs to be - JSON. See docs for more details. - required: - - modelName - - matchPattern - ObservationsV2Response: - title: ObservationsV2Response - type: object - description: >- - Response containing observations with field-group-based filtering and - cursor-based pagination. - - - The `data` array contains observation objects with only the requested - field groups included. - - Use the `cursor` in `meta` to retrieve the next page of results. - properties: - data: - type: array - items: - $ref: '#/components/schemas/ObservationV2' - description: >- - Array of observation objects. Fields included depend on the `fields` - parameter in the request. - meta: - $ref: '#/components/schemas/ObservationsV2Meta' - required: - - data - - meta - ObservationsV2Meta: - title: ObservationsV2Meta - type: object - description: Metadata for cursor-based pagination - properties: - cursor: - type: string - nullable: true - description: >- - Base64-encoded cursor to use for retrieving the next page. If not - present, there are no more results. - OtelResourceSpan: - title: OtelResourceSpan - type: object - description: >- - Represents a collection of spans from a single resource as per OTLP - specification - properties: - resource: - $ref: '#/components/schemas/OtelResource' - nullable: true - description: Resource information - scopeSpans: - type: array - items: - $ref: '#/components/schemas/OtelScopeSpan' - nullable: true - description: Array of scope spans - OtelResource: - title: OtelResource - type: object - description: Resource attributes identifying the source of telemetry - properties: - attributes: - type: array - items: - $ref: '#/components/schemas/OtelAttribute' - nullable: true - description: Resource attributes like service.name, service.version, etc. - OtelScopeSpan: - title: OtelScopeSpan - type: object - description: Collection of spans from a single instrumentation scope - properties: - scope: - $ref: '#/components/schemas/OtelScope' - nullable: true - description: Instrumentation scope information - spans: - type: array - items: - $ref: '#/components/schemas/OtelSpan' - nullable: true - description: Array of spans - OtelScope: - title: OtelScope - type: object - description: Instrumentation scope information - properties: - name: - type: string - nullable: true - description: Instrumentation scope name - version: - type: string - nullable: true - description: Instrumentation scope version - attributes: - type: array - items: - $ref: '#/components/schemas/OtelAttribute' - nullable: true - description: Additional scope attributes - OtelSpan: - title: OtelSpan - type: object - description: Individual span representing a unit of work or operation - properties: - traceId: - nullable: true - description: Trace ID (16 bytes, hex-encoded string in JSON or Buffer in binary) - spanId: - nullable: true - description: Span ID (8 bytes, hex-encoded string in JSON or Buffer in binary) - parentSpanId: - nullable: true - description: Parent span ID if this is a child span - name: - type: string - nullable: true - description: Span name describing the operation - kind: - type: integer - nullable: true - description: Span kind (1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER) - startTimeUnixNano: - nullable: true - description: Start time in nanoseconds since Unix epoch - endTimeUnixNano: - nullable: true - description: End time in nanoseconds since Unix epoch - attributes: - type: array - items: - $ref: '#/components/schemas/OtelAttribute' - nullable: true - description: >- - Span attributes including Langfuse-specific attributes - (langfuse.observation.*) - status: - nullable: true - description: Span status object - OtelAttribute: - title: OtelAttribute - type: object - description: Key-value attribute pair for resources, scopes, or spans - properties: - key: - type: string - nullable: true - description: Attribute key (e.g., "service.name", "langfuse.observation.type") - value: - $ref: '#/components/schemas/OtelAttributeValue' - nullable: true - description: Attribute value - OtelAttributeValue: - title: OtelAttributeValue - type: object - description: Attribute value wrapper supporting different value types - properties: - stringValue: - type: string - nullable: true - description: String value - intValue: - type: integer - nullable: true - description: Integer value - doubleValue: - type: number - format: double - nullable: true - description: Double value - boolValue: - type: boolean - nullable: true - description: Boolean value - OtelTraceResponse: - title: OtelTraceResponse - type: object - description: Response from trace export request. Empty object indicates success. - properties: {} - MembershipRole: - title: MembershipRole - type: string - enum: - - OWNER - - ADMIN - - MEMBER - - VIEWER - MembershipRequest: - title: MembershipRequest - type: object - properties: - userId: - type: string - role: - $ref: '#/components/schemas/MembershipRole' - required: - - userId - - role - DeleteMembershipRequest: - title: DeleteMembershipRequest - type: object - properties: - userId: - type: string - required: - - userId - MembershipResponse: - title: MembershipResponse - type: object - properties: - userId: - type: string - role: - $ref: '#/components/schemas/MembershipRole' - email: - type: string - name: - type: string - required: - - userId - - role - - email - - name - MembershipDeletionResponse: - title: MembershipDeletionResponse - type: object - properties: - message: - type: string - userId: - type: string - required: - - message - - userId - MembershipsResponse: - title: MembershipsResponse - type: object - properties: - memberships: - type: array - items: - $ref: '#/components/schemas/MembershipResponse' - required: - - memberships - OrganizationProject: - title: OrganizationProject - type: object - properties: - id: - type: string - name: - type: string - metadata: - type: object - additionalProperties: true - nullable: true - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - name - - createdAt - - updatedAt - OrganizationProjectsResponse: - title: OrganizationProjectsResponse - type: object - properties: - projects: - type: array - items: - $ref: '#/components/schemas/OrganizationProject' - required: - - projects - OrganizationApiKey: - title: OrganizationApiKey - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - expiresAt: - type: string - format: date-time - nullable: true - lastUsedAt: - type: string - format: date-time - nullable: true - note: - type: string - nullable: true - publicKey: - type: string - displaySecretKey: - type: string - required: - - id - - createdAt - - publicKey - - displaySecretKey - OrganizationApiKeysResponse: - title: OrganizationApiKeysResponse - type: object - properties: - apiKeys: - type: array - items: - $ref: '#/components/schemas/OrganizationApiKey' - required: - - apiKeys - Projects: - title: Projects - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Project' - required: - - data - Organization: - title: Organization - type: object - properties: - id: - type: string - description: The unique identifier of the organization - name: - type: string - description: The name of the organization - required: - - id - - name - Project: - title: Project - type: object - properties: - id: - type: string - name: - type: string - organization: - $ref: '#/components/schemas/Organization' - description: The organization this project belongs to - metadata: - type: object - additionalProperties: true - description: Metadata for the project - retentionDays: - type: integer - nullable: true - description: >- - Number of days to retain data. Null or 0 means no retention. Omitted - if no retention is configured. - required: - - id - - name - - organization - - metadata - ProjectDeletionResponse: - title: ProjectDeletionResponse - type: object - properties: - success: - type: boolean - message: - type: string - required: - - success - - message - ApiKeyList: - title: ApiKeyList - type: object - description: List of API keys for a project - properties: - apiKeys: - type: array - items: - $ref: '#/components/schemas/ApiKeySummary' - required: - - apiKeys - ApiKeySummary: - title: ApiKeySummary - type: object - description: Summary of an API key - properties: - id: - type: string - createdAt: - type: string - format: date-time - expiresAt: - type: string - format: date-time - nullable: true - lastUsedAt: - type: string - format: date-time - nullable: true - note: - type: string - nullable: true - publicKey: - type: string - displaySecretKey: - type: string - required: - - id - - createdAt - - publicKey - - displaySecretKey - ApiKeyResponse: - title: ApiKeyResponse - type: object - description: Response for API key creation - properties: - id: - type: string - createdAt: - type: string - format: date-time - publicKey: - type: string - secretKey: - type: string - displaySecretKey: - type: string - note: - type: string - nullable: true - required: - - id - - createdAt - - publicKey - - secretKey - - displaySecretKey - ApiKeyDeletionResponse: - title: ApiKeyDeletionResponse - type: object - description: Response for API key deletion - properties: - success: - type: boolean - required: - - success - PromptMetaListResponse: - title: PromptMetaListResponse - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/PromptMeta' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - PromptMeta: - title: PromptMeta - type: object - properties: - name: - type: string - type: - $ref: '#/components/schemas/PromptType' - description: Indicates whether the prompt is a text or chat prompt. - versions: - type: array - items: - type: integer - labels: - type: array - items: - type: string - tags: - type: array - items: - type: string - lastUpdatedAt: - type: string - format: date-time - lastConfig: - description: >- - Config object of the most recent prompt version that matches the - filters (if any are provided) - required: - - name - - type - - versions - - labels - - tags - - lastUpdatedAt - - lastConfig - CreatePromptRequest: - title: CreatePromptRequest - oneOf: - - $ref: '#/components/schemas/CreateChatPromptRequest' - - $ref: '#/components/schemas/CreateTextPromptRequest' - CreateChatPromptRequest: - title: CreateChatPromptRequest - type: object - properties: - name: - type: string - prompt: - type: array - items: - $ref: '#/components/schemas/ChatMessageWithPlaceholders' - config: - nullable: true - type: - $ref: '#/components/schemas/CreateChatPromptType' - labels: - type: array - items: - type: string - nullable: true - description: List of deployment labels of this prompt version. - tags: - type: array - items: - type: string - nullable: true - description: List of tags to apply to all versions of this prompt. - commitMessage: - type: string - nullable: true - description: Commit message for this prompt version. - required: - - name - - prompt - - type - CreateTextPromptRequest: - title: CreateTextPromptRequest - type: object - properties: - name: - type: string - prompt: - type: string - config: - nullable: true - type: - $ref: '#/components/schemas/CreateTextPromptType' - nullable: true - labels: - type: array - items: - type: string - nullable: true - description: List of deployment labels of this prompt version. - tags: - type: array - items: - type: string - nullable: true - description: List of tags to apply to all versions of this prompt. - commitMessage: - type: string - nullable: true - description: Commit message for this prompt version. - required: - - name - - prompt - Prompt: - title: Prompt - type: object - properties: - type: - type: string - enum: - - chat - - text - prompt: - type: string - required: - - type - - prompt - PromptType: - title: PromptType - type: string - enum: - - chat - - text - BasePrompt: - title: BasePrompt - type: object - properties: - name: - type: string - version: - type: integer - config: {} - labels: - type: array - items: - type: string - description: List of deployment labels of this prompt version. - tags: - type: array - items: - type: string - description: >- - List of tags. Used to filter via UI and API. The same across - versions of a prompt. - commitMessage: - type: string - nullable: true - description: Commit message for this prompt version. - resolutionGraph: - type: object - additionalProperties: true - nullable: true - description: >- - The dependency resolution graph for the current prompt. Null if the - prompt has no dependencies or if `resolve=false` was used. - required: - - name - - version - - config - - labels - - tags - ChatMessageWithPlaceholders: - title: ChatMessageWithPlaceholders - oneOf: - - $ref: '#/components/schemas/ChatMessage' - - $ref: '#/components/schemas/PlaceholderMessage' - ChatMessage: - title: ChatMessage - type: object - properties: - role: - type: string - content: - type: string - type: - $ref: '#/components/schemas/ChatMessageType' - nullable: true - required: - - role - - content - ChatMessageType: - title: ChatMessageType - type: string - enum: - - chatmessage - PlaceholderMessage: - title: PlaceholderMessage - type: object - properties: - name: - type: string - type: - $ref: '#/components/schemas/PlaceholderMessageType' - nullable: true - required: - - name - PlaceholderMessageType: - title: PlaceholderMessageType - type: string - enum: - - placeholder - TextPrompt: - title: TextPrompt - type: object - properties: - prompt: - type: string - required: - - prompt - allOf: - - $ref: '#/components/schemas/BasePrompt' - ChatPrompt: - title: ChatPrompt - type: object - properties: - prompt: - type: array - items: - $ref: '#/components/schemas/ChatMessageWithPlaceholders' - required: - - prompt - allOf: - - $ref: '#/components/schemas/BasePrompt' - CreateChatPromptType: - title: CreateChatPromptType - type: string - enum: - - chat - CreateTextPromptType: - title: CreateTextPromptType - type: string - enum: - - text - ServiceProviderConfig: - title: ServiceProviderConfig - type: object - properties: - schemas: - type: array - items: - type: string - documentationUri: - type: string - patch: - $ref: '#/components/schemas/ScimFeatureSupport' - bulk: - $ref: '#/components/schemas/BulkConfig' - filter: - $ref: '#/components/schemas/FilterConfig' - changePassword: - $ref: '#/components/schemas/ScimFeatureSupport' - sort: - $ref: '#/components/schemas/ScimFeatureSupport' - etag: - $ref: '#/components/schemas/ScimFeatureSupport' - authenticationSchemes: - type: array - items: - $ref: '#/components/schemas/AuthenticationScheme' - meta: - $ref: '#/components/schemas/ResourceMeta' - required: - - schemas - - documentationUri - - patch - - bulk - - filter - - changePassword - - sort - - etag - - authenticationSchemes - - meta - ScimFeatureSupport: - title: ScimFeatureSupport - type: object - properties: - supported: - type: boolean - required: - - supported - BulkConfig: - title: BulkConfig - type: object - properties: - supported: - type: boolean - maxOperations: - type: integer - maxPayloadSize: - type: integer - required: - - supported - - maxOperations - - maxPayloadSize - FilterConfig: - title: FilterConfig - type: object - properties: - supported: - type: boolean - maxResults: - type: integer - required: - - supported - - maxResults - ResourceMeta: - title: ResourceMeta - type: object - properties: - resourceType: - type: string - location: - type: string - required: - - resourceType - - location - AuthenticationScheme: - title: AuthenticationScheme - type: object - properties: - name: - type: string - description: - type: string - specUri: - type: string - type: - type: string - primary: - type: boolean - required: - - name - - description - - specUri - - type - - primary - ResourceTypesResponse: - title: ResourceTypesResponse - type: object - properties: - schemas: - type: array - items: - type: string - totalResults: - type: integer - Resources: - type: array - items: - $ref: '#/components/schemas/ResourceType' - required: - - schemas - - totalResults - - Resources - ResourceType: - title: ResourceType - type: object - properties: - schemas: - type: array - items: - type: string - nullable: true - id: - type: string - name: - type: string - endpoint: - type: string - description: - type: string - schema: - type: string - schemaExtensions: - type: array - items: - $ref: '#/components/schemas/SchemaExtension' - meta: - $ref: '#/components/schemas/ResourceMeta' - required: - - id - - name - - endpoint - - description - - schema - - schemaExtensions - - meta - SchemaExtension: - title: SchemaExtension - type: object - properties: - schema: - type: string - required: - type: boolean - required: - - schema - - required - SchemasResponse: - title: SchemasResponse - type: object - properties: - schemas: - type: array - items: - type: string - totalResults: - type: integer - Resources: - type: array - items: - $ref: '#/components/schemas/SchemaResource' - required: - - schemas - - totalResults - - Resources - SchemaResource: - title: SchemaResource - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - attributes: - type: array - items: {} - meta: - $ref: '#/components/schemas/ResourceMeta' - required: - - id - - name - - description - - attributes - - meta - ScimUsersListResponse: - title: ScimUsersListResponse - type: object - properties: - schemas: - type: array - items: - type: string - totalResults: - type: integer - startIndex: - type: integer - itemsPerPage: - type: integer - Resources: - type: array - items: - $ref: '#/components/schemas/ScimUser' - required: - - schemas - - totalResults - - startIndex - - itemsPerPage - - Resources - ScimUser: - title: ScimUser - type: object - properties: - schemas: - type: array - items: - type: string - id: - type: string - userName: - type: string - name: - $ref: '#/components/schemas/ScimName' - emails: - type: array - items: - $ref: '#/components/schemas/ScimEmail' - meta: - $ref: '#/components/schemas/UserMeta' - required: - - schemas - - id - - userName - - name - - emails - - meta - UserMeta: - title: UserMeta - type: object - properties: - resourceType: - type: string - created: - type: string - nullable: true - lastModified: - type: string - nullable: true - required: - - resourceType - ScimName: - title: ScimName - type: object - properties: - formatted: - type: string - nullable: true - ScimEmail: - title: ScimEmail - type: object - properties: - primary: - type: boolean - value: - type: string - type: - type: string - required: - - primary - - value - - type - EmptyResponse: - title: EmptyResponse - type: object - description: Empty response for 204 No Content responses - properties: {} - ScoreConfigs: - title: ScoreConfigs - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/ScoreConfig' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - CreateScoreConfigRequest: - title: CreateScoreConfigRequest - type: object - properties: - name: - type: string - description: >- - Name of the score config. Max 35 characters. Only letters, numbers, - underscores, spaces, periods, parentheses, and hyphens are allowed. - dataType: - $ref: '#/components/schemas/ScoreConfigDataType' - categories: - type: array - items: - $ref: '#/components/schemas/ConfigCategory' - nullable: true - description: >- - Configure custom categories for categorical scores. Pass a list of - objects with `label` and `value` properties. Categories are - autogenerated for boolean configs and cannot be passed - minValue: - type: number - format: double - nullable: true - description: >- - Configure a minimum value for numerical scores. If not set, the - minimum value defaults to -∞ - maxValue: - type: number - format: double - nullable: true - description: >- - Configure a maximum value for numerical scores. If not set, the - maximum value defaults to +∞ - description: - type: string - nullable: true - description: >- - Description is shown across the Langfuse UI and can be used to e.g. - explain the config categories in detail, why a numeric range was - set, or provide additional context on config name or usage - required: - - name - - dataType - UpdateScoreConfigRequest: - title: UpdateScoreConfigRequest - type: object - properties: - isArchived: - type: boolean - nullable: true - description: The status of the score config showing if it is archived or not - name: - type: string - nullable: true - description: >- - Name of the score config. Max 35 characters. Only letters, numbers, - underscores, spaces, periods, parentheses, and hyphens are allowed. - categories: - type: array - items: - $ref: '#/components/schemas/ConfigCategory' - nullable: true - description: >- - Configure custom categories for categorical scores. Pass a list of - objects with `label` and `value` properties. Categories are - autogenerated for boolean configs and cannot be passed - minValue: - type: number - format: double - nullable: true - description: >- - Configure a minimum value for numerical scores. If not set, the - minimum value defaults to -∞ - maxValue: - type: number - format: double - nullable: true - description: >- - Configure a maximum value for numerical scores. If not set, the - maximum value defaults to +∞ - description: - type: string - nullable: true - description: >- - Description is shown across the Langfuse UI and can be used to e.g. - explain the config categories in detail, why a numeric range was - set, or provide additional context on config name or usage - ScoreSubjectTraceV3: - title: ScoreSubjectTraceV3 - type: object - properties: - id: - type: string - description: The trace ID. - required: - - id - ScoreSubjectObservationV3: - title: ScoreSubjectObservationV3 - type: object - properties: - id: - type: string - description: The observation ID. - traceId: - type: string - nullable: true - description: The parent trace ID, if available. - required: - - id - ScoreSubjectSessionV3: - title: ScoreSubjectSessionV3 - type: object - properties: - id: - type: string - description: The session ID. - required: - - id - ScoreSubjectExperimentV3: - title: ScoreSubjectExperimentV3 - type: object - properties: - id: - type: string - description: The dataset run ID (experiment ID). - required: - - id - ScoreSubjectV3: - title: ScoreSubjectV3 - type: object - properties: - kind: - type: string - enum: - - trace - - observation - - session - - experiment - id: - type: string - description: The trace ID. - traceId: - type: string - nullable: true - description: The parent trace ID, if available. - required: - - kind - - id - BaseScoreV3: - title: BaseScoreV3 - type: object - properties: - id: - type: string - projectId: - type: string - name: - type: string - source: - $ref: '#/components/schemas/ScoreSource' - timestamp: - type: string - format: date-time - environment: - type: string - description: The environment from which this score originated. - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - comment: - type: string - nullable: true - description: >- - Optional comment attached to the score. Present when "details" is - included in the fields parameter. - configId: - type: string - nullable: true - description: >- - The score config ID, if this score was created from a config. - Present when "details" is included in the fields parameter. - metadata: - type: object - additionalProperties: true - nullable: true - description: >- - Arbitrary metadata attached to the score. Present when "details" is - included in the fields parameter. - authorUserId: - type: string - nullable: true - description: >- - The user who created this score, if available. Present when - "annotation" is included in the fields parameter. - queueId: - type: string - nullable: true - description: >- - The annotation queue this score belongs to, if any. Present when - "annotation" is included in the fields parameter. - subject: - $ref: '#/components/schemas/ScoreSubjectV3' - nullable: true - description: >- - The entity this score is attached to (trace, observation, session, - or experiment). Present when "subject" is included in the fields - parameter. - required: - - id - - projectId - - name - - source - - timestamp - - environment - - createdAt - - updatedAt - NumericScoreV3: - title: NumericScoreV3 - type: object - properties: - value: - type: number - format: double - description: The numeric value of the score. - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScoreV3' - BooleanScoreV3: - title: BooleanScoreV3 - type: object - properties: - value: - type: boolean - description: The boolean value of the score. - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScoreV3' - CategoricalScoreV3: - title: CategoricalScoreV3 - type: object - properties: - value: - type: string - description: The string category value of the score. - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScoreV3' - TextScoreV3: - title: TextScoreV3 - type: object - properties: - value: - type: string - description: The text content of the score. - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScoreV3' - CorrectionScoreV3: - title: CorrectionScoreV3 - type: object - properties: - value: - type: string - description: The correction content of the score. Empty string if not set. - required: - - value - allOf: - - $ref: '#/components/schemas/BaseScoreV3' - ScoreV3: - title: ScoreV3 - type: object - properties: - dataType: - type: string - enum: - - NUMERIC - - BOOLEAN - - CATEGORICAL - - TEXT - - CORRECTION - value: - type: string - description: The numeric value of the score. - required: - - dataType - - value - GetScoresV3Meta: - title: GetScoresV3Meta - type: object - properties: - limit: - type: integer - cursor: - type: string - nullable: true - description: >- - URL-safe base64 (base64url) cursor for the next page. Absent when - there are no more results. - required: - - limit - GetScoresV3Response: - title: GetScoresV3Response - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/ScoreV3' - meta: - $ref: '#/components/schemas/GetScoresV3Meta' - required: - - data - - meta - GetScoresResponseTraceData: - title: GetScoresResponseTraceData - type: object - properties: - userId: - type: string - nullable: true - description: The user ID associated with the trace referenced by score - tags: - type: array - items: - type: string - nullable: true - description: A list of tags associated with the trace referenced by score - environment: - type: string - nullable: true - description: The environment of the trace referenced by score - sessionId: - type: string - nullable: true - description: The session ID associated with the trace referenced by score - GetScoresResponseDataNumeric: - title: GetScoresResponseDataNumeric - type: object - properties: - trace: - $ref: '#/components/schemas/GetScoresResponseTraceData' - nullable: true - allOf: - - $ref: '#/components/schemas/NumericScore' - GetScoresResponseDataCategorical: - title: GetScoresResponseDataCategorical - type: object - properties: - trace: - $ref: '#/components/schemas/GetScoresResponseTraceData' - nullable: true - allOf: - - $ref: '#/components/schemas/CategoricalScore' - GetScoresResponseDataBoolean: - title: GetScoresResponseDataBoolean - type: object - properties: - trace: - $ref: '#/components/schemas/GetScoresResponseTraceData' - nullable: true - allOf: - - $ref: '#/components/schemas/BooleanScore' - GetScoresResponseDataCorrection: - title: GetScoresResponseDataCorrection - type: object - properties: - trace: - $ref: '#/components/schemas/GetScoresResponseTraceData' - nullable: true - allOf: - - $ref: '#/components/schemas/CorrectionScore' - GetScoresResponseDataText: - title: GetScoresResponseDataText - type: object - properties: - trace: - $ref: '#/components/schemas/GetScoresResponseTraceData' - nullable: true - allOf: - - $ref: '#/components/schemas/TextScore' - GetScoresResponseData: - title: GetScoresResponseData - type: object - properties: - dataType: - type: string - enum: - - NUMERIC - - CATEGORICAL - - BOOLEAN - - CORRECTION - - TEXT - trace: - $ref: '#/components/schemas/GetScoresResponseTraceData' - required: - - dataType - GetScoresResponse: - title: GetScoresResponse - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/GetScoresResponseData' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - PaginatedSessions: - title: PaginatedSessions - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Session' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - Traces: - title: Traces - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/TraceWithDetails' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - DeleteTraceResponse: - title: DeleteTraceResponse - type: object - properties: - message: - type: string - required: - - message - Sort: - title: Sort - type: object - properties: - id: - type: string - required: - - id - unstableEvaluatorType: - title: unstableEvaluatorType - type: string - enum: - - llm_as_judge - - code - description: |- - The evaluator engine type. - - The unstable public API supports LLM-as-a-judge and code evaluators. - unstableCodeEvaluatorSourceCodeLanguage: - title: unstableCodeEvaluatorSourceCodeLanguage - type: string - enum: - - PYTHON - - TYPESCRIPT - description: Code evaluator runtime language. - unstableEvaluatorScope: - title: unstableEvaluatorScope - type: string - enum: - - project - - managed - description: |- - Where an evaluator comes from. - - - `project`: created in your project - - `managed`: provided by Langfuse - unstableEvaluationRuleTarget: - title: unstableEvaluationRuleTarget - type: string - enum: - - observation - - experiment - description: >- - The ingestion object type that should trigger evaluation runs. - - - Choose the target first, because it changes both the valid filter - columns and the valid variable-mapping sources: - - - `observation` evaluates live-ingested observations such as - generations, spans, and events. - It supports mapping from `input`, `output`, and `metadata`. - - `experiment` evaluates live experiment executions and can additionally - map `expected_output` and `experiment_item_metadata`. - It currently supports filtering by `datasetId`. - Discover valid dataset IDs with `GET /api/public/v2/datasets`, then use the returned dataset `id` values in your filter. - unstableEvaluationRuleStatus: - title: unstableEvaluationRuleStatus - type: string - enum: - - active - - inactive - - paused - description: >- - Effective runtime status of the evaluation rule. - - - - `active`: enabled and currently runnable. - - - `inactive`: disabled by configuration. - - - `paused`: enabled, but Langfuse has blocked execution until the - underlying issue is resolved. - unstableEvaluationRuleMappingSource: - title: unstableEvaluationRuleMappingSource - type: string - enum: - - input - - output - - metadata - - expected_output - - experiment_item_metadata - description: >- - Source field used to populate a prompt variable. - - - Use these values when mapping evaluator prompt variables to live data. - - - Target-specific rules: - - - `target=observation` supports `input`, `output`, and `metadata` - - - `target=experiment` supports `input`, `output`, `metadata`, - `expected_output`, and `experiment_item_metadata` - - - Source semantics: - - - `input`: the observation or experiment input payload - - - `output`: the observation or experiment output payload - - - `metadata`: the metadata object for the target. Combine with - `jsonPath` when you need one nested field instead of the whole object. - - - `expected_output`: the experiment item's expected output. Only valid - for `target=experiment`. - - - `experiment_item_metadata`: the experiment item's metadata object. - Only valid for `target=experiment`. - unstableEvaluatorModelConfig: - title: unstableEvaluatorModelConfig - type: object - description: >- - Optional explicit model configuration for an evaluator. - - - If omitted, Langfuse uses the project's default evaluation model. - - If provided, the model must be available to the project when the - evaluator or evaluation rule is enabled. - - - To discover valid configured `provider` values for a project, call `GET - /api/public/llm-connections` and read the `provider` field from the - returned connections. - - Use a `provider` value that matches one of the connections already - configured in the same project. - - - Recovery guidance: - - - If evaluator creation returns `422` with - `code=evaluator_preflight_failed`, either provide a valid explicit - `modelConfig` here or configure the project's default evaluation model, - then retry the same request. - properties: - provider: - type: string - description: >- - Provider identifier to use for this evaluator, for example `openai` - or `anthropic`. - - - To discover valid values for the current project, call `GET - /api/public/llm-connections` and use one of the returned `provider` - values. - model: - type: string - description: >- - Model identifier exposed by the provider, for example `gpt-4.1-mini`. - required: - - provider - - model - unstableEvaluatorOutputDataType: - title: unstableEvaluatorOutputDataType - type: string - enum: - - NUMERIC - - BOOLEAN - - CATEGORICAL - description: >- - Structured score type returned by an evaluator. - - - This controls the type of score value Langfuse stores for evaluation - results: - - - `NUMERIC`: a numeric score such as `0.82` - - - `BOOLEAN`: a boolean score such as `true` - - - `CATEGORICAL`: one or more category labels from a fixed list - unstableEvaluatorOutputFieldDefinition: - title: unstableEvaluatorOutputFieldDefinition - type: object - properties: - description: - type: string - description: >- - Human-readable instructions for what the evaluator should return in - this field. - required: - - description - unstableEvaluatorOutputDefinition: - title: unstableEvaluatorOutputDefinition - type: object - properties: - dataType: - type: string - enum: - - NUMERIC - - BOOLEAN - - CATEGORICAL - reasoning: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - score: - type: string - required: - - dataType - - reasoning - - score - unstablePublicNumericEvaluatorOutputDefinition: - title: unstablePublicNumericEvaluatorOutputDefinition - type: object - properties: - dataType: - $ref: '#/components/schemas/unstableEvaluatorOutputDataType' - description: Always `NUMERIC`. - reasoning: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - score: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - required: - - dataType - - reasoning - - score - unstablePublicBooleanEvaluatorOutputDefinition: - title: unstablePublicBooleanEvaluatorOutputDefinition - type: object - properties: - dataType: - $ref: '#/components/schemas/unstableEvaluatorOutputDataType' - description: Always `BOOLEAN`. - reasoning: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - score: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - required: - - dataType - - reasoning - - score - unstablePublicCategoricalEvaluatorOutputScoreDefinition: - title: unstablePublicCategoricalEvaluatorOutputScoreDefinition - type: object - properties: - description: - type: string - categories: - type: array - items: - type: string - shouldAllowMultipleMatches: - type: boolean - required: - - description - - categories - - shouldAllowMultipleMatches - unstablePublicCategoricalEvaluatorOutputDefinition: - title: unstablePublicCategoricalEvaluatorOutputDefinition - type: object - properties: - dataType: - $ref: '#/components/schemas/unstableEvaluatorOutputDataType' - description: Always `CATEGORICAL`. - reasoning: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - score: - $ref: >- - #/components/schemas/unstablePublicCategoricalEvaluatorOutputScoreDefinition - required: - - dataType - - reasoning - - score - unstablePublicEvaluatorOutputDefinition: - title: unstablePublicEvaluatorOutputDefinition - type: object - properties: - dataType: - type: string - enum: - - NUMERIC - - BOOLEAN - - CATEGORICAL - reasoning: - $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' - score: - type: string - required: - - dataType - - reasoning - - score - unstableEvaluationRuleStringFilterOperator: - title: unstableEvaluationRuleStringFilterOperator - type: string - enum: - - '=' - - contains - - does not contain - - starts with - - ends with - unstableEvaluationRuleNumberFilterOperator: - title: unstableEvaluationRuleNumberFilterOperator - type: string - enum: - - '=' - - '>' - - < - - '>=' - - <= - unstableEvaluationRuleOptionsFilterOperator: - title: unstableEvaluationRuleOptionsFilterOperator - type: string - enum: - - any of - - none of - unstableEvaluationRuleArrayOptionsFilterOperator: - title: unstableEvaluationRuleArrayOptionsFilterOperator - type: string - enum: - - any of - - none of - - all of - unstableEvaluationRuleBooleanFilterOperator: - title: unstableEvaluationRuleBooleanFilterOperator - type: string - enum: - - '=' - - <> - unstableEvaluationRuleNullFilterOperator: - title: unstableEvaluationRuleNullFilterOperator - type: string - enum: - - is null - - is not null - unstableDateTimeEvaluationRuleFilter: - title: unstableDateTimeEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleNumberFilterOperator' - description: Comparison operator for datetime values. - value: - type: string - format: date-time - description: Datetime value to compare against. - required: - - column - - operator - - value - unstableStringEvaluationRuleFilter: - title: unstableStringEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleStringFilterOperator' - value: - type: string - required: - - column - - operator - - value - unstableNumberEvaluationRuleFilter: - title: unstableNumberEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleNumberFilterOperator' - value: - type: number - format: double - required: - - column - - operator - - value - unstableStringOptionsEvaluationRuleFilter: - title: unstableStringOptionsEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleOptionsFilterOperator' - value: - type: array - items: - type: string - description: One or more allowed string values. - required: - - column - - operator - - value - unstableArrayOptionsEvaluationRuleFilter: - title: unstableArrayOptionsEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Column to filter on. - operator: - $ref: >- - #/components/schemas/unstableEvaluationRuleArrayOptionsFilterOperator - value: - type: array - items: - type: string - description: One or more array elements to match. - required: - - column - - operator - - value - unstableStringObjectEvaluationRuleFilter: - title: unstableStringObjectEvaluationRuleFilter - type: object - properties: - column: - type: string - description: >- - Object-valued column to filter on. In the unstable public API this - is currently `metadata`. - key: - type: string - description: Top-level key inside the object-valued column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleStringFilterOperator' - value: - type: string - required: - - column - - key - - operator - - value - unstableNumberObjectEvaluationRuleFilter: - title: unstableNumberObjectEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Object-valued column to filter on. - key: - type: string - description: Key inside the object-valued column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleNumberFilterOperator' - value: - type: number - format: double - required: - - column - - key - - operator - - value - unstableCategoryOptionsEvaluationRuleFilter: - title: unstableCategoryOptionsEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Object-valued column to filter on. - key: - type: string - description: Key inside the object-valued column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleOptionsFilterOperator' - value: - type: array - items: - type: string - required: - - column - - key - - operator - - value - unstableBooleanEvaluationRuleFilter: - title: unstableBooleanEvaluationRuleFilter - type: object - properties: - column: - type: string - description: Column to filter on. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleBooleanFilterOperator' - value: - type: boolean - required: - - column - - operator - - value - unstableNullEvaluationRuleFilter: - title: unstableNullEvaluationRuleFilter - type: object - properties: - column: - type: string - description: >- - Column to filter on. In the unstable public API this is currently - `parentObservationId`. - operator: - $ref: '#/components/schemas/unstableEvaluationRuleNullFilterOperator' - value: - type: string - nullable: true - description: >- - Ignored placeholder value. Clients may omit it or send an empty - string. - required: - - column - - operator - unstableEvaluationRuleMapping: - title: unstableEvaluationRuleMapping - type: object - description: >- - Maps one evaluator variable to one source field from the target object. - - - Manual mappings are used for `llm_as_judge` evaluators. `code` - evaluators use a fixed runtime mapping managed by Langfuse. - - - How to build a valid mapping list: - - 1. Create the evaluator or fetch it with `GET /evaluators/{id}`. - - 2. Read the evaluator `variables` array. - - 3. Add exactly one mapping object for each variable in that array. - - 4. Use the variable name exactly as returned, without braces such as - `{{` or `}}`. - - 5. Choose a `source` that is valid for the selected `target`. - - - `jsonPath` is optional. Use it only when the selected source is a JSON - object and you want to extract one nested field before inserting it into - the evaluator prompt. - - - Recovery guidance: - - - `invalid_variable_mapping`: the variable name is unknown for this - evaluator, or the selected `source` is not valid for the chosen `target` - - - `missing_variable_mapping`: one or more LLM-as-judge evaluator - variables are not mapped yet - - - `duplicate_variable_mapping`: the same evaluator variable appears more - than once - - - `invalid_json_path`: the JSONPath expression is malformed. Remove it - or correct it. - properties: - variable: - type: string - description: >- - Prompt variable name without braces. - - - Example: for the prompt `Judge {{input}} against {{output}}`, use - `input` and `output`. - source: - $ref: '#/components/schemas/unstableEvaluationRuleMappingSource' - description: >- - Source field that should populate the prompt variable. - - - Quick reference: - - - `target=observation`: `input`, `output`, `metadata` - - - `target=experiment`: `input`, `output`, `metadata`, - `expected_output`, `experiment_item_metadata` - jsonPath: - type: string - nullable: true - description: >- - Optional JSONPath selector applied to the selected source before it - is passed to the evaluator prompt. - - - Requirements: - - - Must start with `$` - - - Must be a syntactically valid JSONPath expression - - - Most useful with `source=metadata` - required: - - variable - - source - unstableEvaluationRuleFilter: - title: unstableEvaluationRuleFilter - type: object - properties: - type: - type: string - enum: - - datetime - - string - - number - - stringOptions - - categoryOptions - - arrayOptions - - stringObject - - numberObject - - boolean - - 'null' - column: - type: string - description: Column to filter on. - operator: - type: string - description: Comparison operator for datetime values. - value: - type: string - description: Datetime value to compare against. - key: - type: string - description: Key inside the object-valued column to filter on. - required: - - type - - column - - operator - unstableDashboardWidgetView: - title: unstableDashboardWidgetView - type: string - enum: - - observations - - scores-numeric - - scores-categorical - unstableDashboardWidgetChartType: - title: unstableDashboardWidgetChartType - type: string - enum: - - LINE_TIME_SERIES - - AREA_TIME_SERIES - - BAR_TIME_SERIES - - HORIZONTAL_BAR - - VERTICAL_BAR - - PIE - - NUMBER - - HISTOGRAM - - PIVOT_TABLE - unstableDashboardWidgetMetricAggregation: - title: unstableDashboardWidgetMetricAggregation - type: string - enum: - - sum - - avg - - count - - max - - min - - p50 - - p75 - - p90 - - p95 - - p99 - - histogram - - uniq - unstableDashboardWidgetDimension: - title: unstableDashboardWidgetDimension - type: object - properties: - field: - type: string - required: - - field - unstableDashboardWidgetMetric: - title: unstableDashboardWidgetMetric - type: object - properties: - measure: - type: string - agg: - $ref: '#/components/schemas/unstableDashboardWidgetMetricAggregation' - required: - - measure - - agg - unstableDashboardWidgetFilter: - title: unstableDashboardWidgetFilter - type: object - description: >- - A dashboard widget filter in Langfuse filter-state shape. - - - Filter shapes depend on `type`, for example string filters use a string - `value`, - - option filters use a list of strings, and object filters include `key`. - properties: - column: - type: string - operator: - type: string - type: - type: string - value: - nullable: true - key: - type: string - nullable: true - required: - - column - - operator - - type - unstableDashboardWidgetChartConfig: - title: unstableDashboardWidgetChartConfig - type: object - description: |- - Chart-specific widget configuration. - - `type` must match the top-level `chartType`. - `row_limit` applies to total-value charts and pivot tables. - `bins` applies to histograms. - `defaultSort` applies to pivot tables. - properties: - type: - $ref: '#/components/schemas/unstableDashboardWidgetChartType' - row_limit: - type: integer - nullable: true - show_value_labels: - type: boolean - nullable: true - bins: - type: integer - nullable: true - defaultSort: - $ref: '#/components/schemas/unstableDashboardWidgetDefaultSort' - nullable: true - required: - - type - unstableDashboardWidgetDefaultSort: - title: unstableDashboardWidgetDefaultSort - type: object - properties: - column: - type: string - order: - $ref: '#/components/schemas/unstableDashboardWidgetSortOrder' - required: - - column - - order - unstableDashboardWidgetSortOrder: - title: unstableDashboardWidgetSortOrder - type: string - enum: - - ASC - - DESC - unstableCreateDashboardWidgetRequest: - title: unstableCreateDashboardWidgetRequest - type: object - properties: - name: - type: string - description: - type: string - view: - $ref: '#/components/schemas/unstableDashboardWidgetView' - dimensions: - type: array - items: - $ref: '#/components/schemas/unstableDashboardWidgetDimension' - metrics: - type: array - items: - $ref: '#/components/schemas/unstableDashboardWidgetMetric' - filters: - type: array - items: - $ref: '#/components/schemas/unstableDashboardWidgetFilter' - chartType: - $ref: '#/components/schemas/unstableDashboardWidgetChartType' - chartConfig: - $ref: '#/components/schemas/unstableDashboardWidgetChartConfig' - minVersion: - type: integer - nullable: true - required: - - name - - description - - view - - dimensions - - metrics - - filters - - chartType - - chartConfig - unstableDashboardWidget: - title: unstableDashboardWidget - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - name: - type: string - description: - type: string - view: - $ref: '#/components/schemas/unstableDashboardWidgetView' - dimensions: - type: array - items: - $ref: '#/components/schemas/unstableDashboardWidgetDimension' - metrics: - type: array - items: - $ref: '#/components/schemas/unstableDashboardWidgetMetric' - filters: - type: array - items: - $ref: '#/components/schemas/unstableDashboardWidgetFilter' - chartType: - $ref: '#/components/schemas/unstableDashboardWidgetChartType' - chartConfig: - $ref: '#/components/schemas/unstableDashboardWidgetChartConfig' - minVersion: - type: integer - required: - - id - - createdAt - - updatedAt - - name - - description - - view - - dimensions - - metrics - - filters - - chartType - - chartConfig - - minVersion - unstablePublicApiErrorCode: - title: unstablePublicApiErrorCode - type: string - enum: - - authentication_failed - - access_denied - - invalid_request - - invalid_query - - invalid_body - - invalid_filter_value - - invalid_json_path - - invalid_variable_mapping - - missing_variable_mapping - - duplicate_variable_mapping - - resource_not_found - - name_conflict - - evaluator_preflight_failed - - conflict - - unprocessable_content - - rate_limited - - method_not_allowed - - internal_error - description: >- - Machine-readable error code returned by the unstable evaluators API. - - - SDKs, CLIs, and agents should branch on `code` rather than parsing the - human-readable `message`. - - The HTTP status still indicates the broad error class, while `code` - gives the specific failure reason. - unstablePublicApiValidationIssue: - title: unstablePublicApiValidationIssue - type: object - description: >- - One validation issue returned for malformed request bodies or query - parameters. - - - This mirrors the most important parts of a Zod issue: a machine-readable - `code`, - - a human-readable `message`, and a structured `path`. - properties: - code: - type: string - description: >- - Machine-readable validation issue code emitted by the server - validator. - message: - type: string - description: Human-readable explanation of the validation failure. - path: - type: array - items: {} - description: Path to the invalid field, for example `["mapping", 0, "jsonPath"]`. - required: - - code - - message - - path - unstablePublicApiErrorDetails: - title: unstablePublicApiErrorDetails - type: object - description: >- - Optional structured context attached to an unstable-evals error. - - - The populated fields depend on the error `code`: - - - request parsing failures populate `issues` - - - filter validation failures populate `field`, `column`, - `invalidValues`, and `allowedValues` - - - variable mapping failures populate `field`, `variable`, or `variables` - - - JSONPath validation failures populate `field`, `variable`, and `value` - - - evaluator preflight failures populate `evaluatorName`, `provider`, and - `model` - - - rate limiting populates `retryAfterSeconds`, `limit`, `remaining`, and - `resetAt` - properties: - issues: - type: array - items: - $ref: '#/components/schemas/unstablePublicApiValidationIssue' - nullable: true - description: Validation issues for malformed request bodies or query parameters. - field: - type: string - nullable: true - description: >- - Path-like reference to the failing field, for example - `mapping[1].jsonPath`. - column: - type: string - nullable: true - description: Filter column that failed validation. - invalidValues: - type: array - items: - type: string - nullable: true - description: Unsupported values supplied by the caller. - allowedValues: - type: array - items: - type: string - nullable: true - description: Allowed values for the failing filter column. - variable: - type: string - nullable: true - description: Evaluator variable involved in the failure. - variables: - type: array - items: - type: string - nullable: true - description: >- - Multiple evaluator variables involved in the failure, for example - missing mappings. - value: - type: string - nullable: true - description: Raw invalid value supplied by the caller. - evaluatorName: - type: string - nullable: true - description: Evaluator name used during preflight validation. - provider: - type: string - nullable: true - description: Provider resolved during evaluator preflight, if any. - model: - type: string - nullable: true - description: Model resolved during evaluator preflight, if any. - retryAfterSeconds: - type: integer - nullable: true - description: Suggested retry delay for rate-limited requests. - limit: - type: integer - nullable: true - description: >- - Numeric limit associated with the failure, for example the active - evaluation-rule cap or the current rate-limit window. - remaining: - type: integer - nullable: true - description: Remaining requests in the current rate-limit window. - resetAt: - type: string - nullable: true - description: ISO-8601 timestamp when the current rate-limit window resets. - unstablePublicApiError: - title: unstablePublicApiError - type: object - description: >- - Standard error envelope for the unstable evaluators API. - - - Response handling guidance: - - - Use the HTTP status code for the broad class of failure. - - - Use `code` for precise branching in SDKs, CLIs, or agents. - - - Inspect `details` for field-level validation context such as invalid - filter values, malformed JSONPath expressions, or missing variable - mappings. - - - Retry only after fixing the specific issue described by `code` and - `details`. - properties: - message: - type: string - description: Human-readable description of the failure. - code: - $ref: '#/components/schemas/unstablePublicApiErrorCode' - description: Stable machine-readable error code. - details: - $ref: '#/components/schemas/unstablePublicApiErrorDetails' - nullable: true - description: >- - Optional structured error context. Inspect the populated fields - based on `code`. - required: - - message - - code - unstableEvaluationRule: - title: unstableEvaluationRule - type: object - description: >- - Live evaluation rule for incoming data. - - - An evaluation rule answers: - - - which evaluator should be used - - - which target objects should trigger scoring - - - how often scoring should run - - - which target fields should populate each evaluator variable - - - whether the deployment is active, inactive, or paused - - - Important status semantics: - - - `enabled` is the desired on/off setting from the client - - - `status` is the effective runtime state after Langfuse applies - validation and blocking rules - - - `enabled=true` with `status=paused` means the rule should run, but - Langfuse has paused it until the underlying problem is fixed - properties: - id: - type: string - description: Stable evaluation rule identifier. - name: - type: string - description: >- - Human-readable deployment name. This is independent from the - evaluator name. - evaluator: - $ref: '#/components/schemas/unstableEvaluationRuleEvaluator' - description: >- - Evaluator currently used by this rule. - - - `name` and `scope` identify the evaluator family conceptually. - - `id` is the currently active evaluator version in that family. - - If you create a newer project version with the same evaluator name - later, existing evaluation rules are moved to it automatically. - target: - $ref: '#/components/schemas/unstableEvaluationRuleTarget' - description: Target object type that should trigger scoring. - enabled: - type: boolean - description: Desired enabled state configured by the client. - status: - $ref: '#/components/schemas/unstableEvaluationRuleStatus' - description: >- - Effective runtime status after Langfuse applies validation and - blocking rules. - pausedReason: - type: string - nullable: true - description: Machine-readable reason when `status=paused`, otherwise `null`. - pausedMessage: - type: string - nullable: true - description: Human-readable explanation when `status=paused`, otherwise `null`. - sampling: - type: number - format: double - description: |- - Fraction of matching target objects that should be evaluated. - - Must be greater than `0` and less than or equal to `1`. - - `1` means evaluate every matching target. - - `0.25` means evaluate approximately 25% of matching targets. - filter: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleFilter' - description: >- - List of filter conditions used to decide whether a target should be - evaluated. - mapping: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleMapping' - description: >- - Variable mappings used to populate evaluator runtime variables from - the live target object. - createdAt: - type: string - format: date-time - description: Timestamp when the evaluation rule was created. - updatedAt: - type: string - format: date-time - description: Timestamp when the evaluation rule was last updated. - required: - - id - - name - - evaluator - - target - - enabled - - status - - pausedReason - - pausedMessage - - sampling - - filter - - mapping - - createdAt - - updatedAt - unstableEvaluationRules: - title: unstableEvaluationRules - type: object - description: Paginated list of evaluation rules. - properties: - data: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRule' - description: Evaluation rules in the current page. - meta: - $ref: '#/components/schemas/utilsMetaResponse' - description: Standard pagination metadata. - required: - - data - - meta - unstableCreateEvaluationRuleRequest: - title: unstableCreateEvaluationRuleRequest - oneOf: - - $ref: '#/components/schemas/unstableCreateLlmAsJudgeEvaluationRuleRequest' - - $ref: '#/components/schemas/unstableCreateCodeEvaluationRuleRequest' - description: >- - Request body for creating an evaluation rule. - - - Checklist for agents and SDK clients: - - - reference an existing evaluator family by `evaluator.name` and - `evaluator.scope` - - - choose `target=observation` or `target=experiment` - - - if `target=experiment` and you want a dataset filter, call `GET - /api/public/v2/datasets` first and use dataset `id` values in - `filter[].value` - - - for `llm_as_judge`, fetch or inspect the evaluator first and provide a - complete variable mapping for every evaluator variable - - - for `code`, do not send variables or mappings; Langfuse stores the - fixed code runtime mapping automatically - - - optionally narrow execution with `filter` - - - set `enabled=true` only when you want live execution immediately - unstableCreateLlmAsJudgeEvaluationRuleRequest: - title: unstableCreateLlmAsJudgeEvaluationRuleRequest - type: object - properties: - name: - type: string - description: Human-readable deployment name. - evaluator: - $ref: >- - #/components/schemas/unstableLlmAsJudgeEvaluationRuleEvaluatorReference - description: >- - LLM-as-judge evaluator family to use. - - - Use `name`, `scope`, and `type` from the evaluator endpoints. If - `type` is omitted, Langfuse defaults it to `llm_as_judge` for - backwards compatibility. - - Langfuse resolves that family to its latest version before saving - the rule. - target: - $ref: '#/components/schemas/unstableEvaluationRuleTarget' - description: Target object type to evaluate. - enabled: - type: boolean - description: Whether the deployment should be active immediately after creation. - sampling: - type: number - format: double - nullable: true - description: Optional sampling fraction. Defaults to `1`. - filter: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleFilter' - nullable: true - description: >- - Optional filter list. - - - Omit or pass an empty list to evaluate all matching targets for the - selected `target`. - - Each filter object must use a column that is valid for that - `target`. - - For `target=experiment`, `column=datasetId` expects dataset `id` - values from `GET /api/public/v2/datasets`, not dataset names. - mapping: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleMapping' - description: >- - LLM-as-judge variable mappings. - - - Every evaluator variable must appear exactly once. - - Build this list from the evaluator `variables` array returned by the - evaluator endpoints. - required: - - name - - evaluator - - target - - enabled - - mapping - unstableCreateCodeEvaluationRuleRequest: - title: unstableCreateCodeEvaluationRuleRequest - type: object - properties: - name: - type: string - description: Human-readable deployment name. - evaluator: - $ref: '#/components/schemas/unstableCodeEvaluationRuleEvaluatorReference' - description: >- - Code evaluator family to use. - - - Use `name`, `scope`, and `type` from the evaluator endpoints. - - Langfuse resolves that family to its latest version before saving - the rule. - target: - $ref: '#/components/schemas/unstableEvaluationRuleTarget' - description: Target object type to evaluate. - enabled: - type: boolean - description: Whether the deployment should be active immediately after creation. - sampling: - type: number - format: double - nullable: true - description: Optional sampling fraction. Defaults to `1`. - filter: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleFilter' - nullable: true - description: >- - Optional filter list. - - - Omit or pass an empty list to evaluate all matching targets for the - selected `target`. - - Each filter object must use a column that is valid for that - `target`. - - For `target=experiment`, `column=datasetId` expects dataset `id` - values from `GET /api/public/v2/datasets`, not dataset names. - required: - - name - - evaluator - - target - - enabled - unstableUpdateEvaluationRuleRequest: - title: unstableUpdateEvaluationRuleRequest - type: object - description: >- - Partial update body for an evaluation rule. - - - Provide only the fields you want to change. - - An empty body is rejected. - - - Practical guidance: - - - If you only want to rename the rule or change sampling, send just - those fields. - - - If you change to an LLM-as-judge `evaluator`, send a fresh `mapping` - unless you are certain the existing mapping still matches the evaluator - variables. - - - If you change `target` for an LLM-as-judge rule, usually send both - `filter` and `mapping` in the same request. - - - For code evaluator rules, omit `mapping`; Langfuse stores the fixed - code runtime mapping automatically. - - - If you change an experiment `datasetId` filter, call `GET - /api/public/v2/datasets` and use dataset `id` values from that response. - properties: - name: - type: string - nullable: true - description: Updated deployment name. - evaluator: - $ref: '#/components/schemas/unstableEvaluationRuleEvaluatorReference' - nullable: true - description: >- - Updated evaluator family. - - - Langfuse resolves the provided evaluator family to its latest - version before saving the rule. - - A rule's evaluator type cannot be changed: provide `name` and - `scope` for an evaluator family of the rule's current type. To use a - different evaluator type, create a new rule. - target: - $ref: '#/components/schemas/unstableEvaluationRuleTarget' - nullable: true - description: Updated target object type. - enabled: - type: boolean - nullable: true - description: Updated desired enabled state. - sampling: - type: number - format: double - nullable: true - description: Updated sampling fraction. - filter: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleFilter' - nullable: true - description: >- - Updated filter list. - - - For `target=experiment`, `column=datasetId` expects dataset `id` - values from `GET /api/public/v2/datasets`, not dataset names. - mapping: - type: array - items: - $ref: '#/components/schemas/unstableEvaluationRuleMapping' - nullable: true - description: >- - Updated LLM-as-judge variable mappings. - - - Do not send this field for code evaluator rules. Langfuse stores the - fixed code runtime mapping automatically and returns it in the - response. - unstableDeleteEvaluationRuleResponse: - title: unstableDeleteEvaluationRuleResponse - type: object - description: Confirmation response returned after successful deletion. - properties: - message: - type: string - description: Always `Evaluation rule successfully deleted`. - required: - - message - unstableEvaluationRuleEvaluatorReference: - title: unstableEvaluationRuleEvaluatorReference - type: object - description: >- - Evaluator family reference used when updating an evaluation rule. - - - `name` and `scope` identify the evaluator family in the authenticated - project context. - - A rule's evaluator type cannot be changed, so this reference does not - accept a `type`; the family must match the rule's current evaluator - type. - properties: - name: - type: string - description: Evaluator family name. - scope: - $ref: '#/components/schemas/unstableEvaluatorScope' - description: Whether the evaluator family is project-owned or Langfuse-managed. - required: - - name - - scope - unstableLlmAsJudgeEvaluationRuleEvaluatorReference: - title: unstableLlmAsJudgeEvaluationRuleEvaluatorReference - type: object - description: >- - LLM-as-judge evaluator family reference used when creating an evaluation - rule. - properties: - name: - type: string - description: Evaluator family name. - scope: - $ref: '#/components/schemas/unstableEvaluatorScope' - description: Whether the evaluator family is project-owned or Langfuse-managed. - type: - $ref: '#/components/schemas/unstableLlmAsJudgeEvaluatorType' - nullable: true - description: Evaluator engine type. Defaults to `llm_as_judge` when omitted. - required: - - name - - scope - unstableCodeEvaluationRuleEvaluatorReference: - title: unstableCodeEvaluationRuleEvaluatorReference - type: object - description: Code evaluator family reference used when creating an evaluation rule. - properties: - name: - type: string - description: Evaluator family name. - scope: - $ref: '#/components/schemas/unstableEvaluatorScope' - description: Whether the evaluator family is project-owned or Langfuse-managed. - type: - type: string - const: code - description: Must be `code`. - required: - - name - - scope - - type - unstableLlmAsJudgeEvaluatorType: - title: unstableLlmAsJudgeEvaluatorType - type: string - enum: - - llm_as_judge - unstableEvaluationRuleEvaluator: - title: unstableEvaluationRuleEvaluator - type: object - description: |- - Resolved evaluator currently used by the evaluation rule. - - `id` is the exact active evaluator version. - `name`, `scope`, and `type` identify the evaluator family conceptually. - properties: - id: - type: string - description: >- - Identifier of the exact evaluator version currently used by the rule. - name: - type: string - description: Evaluator family name. - scope: - $ref: '#/components/schemas/unstableEvaluatorScope' - description: Whether the evaluator family is project-owned or Langfuse-managed. - type: - $ref: '#/components/schemas/unstableEvaluatorType' - description: Evaluator engine type. - required: - - id - - name - - scope - - type - unstableDeleteEvaluatorResponse: - title: unstableDeleteEvaluatorResponse - type: object - description: Confirmation response returned after successful deletion. - properties: - message: - type: string - description: Always `Evaluator successfully deleted`. - required: - - message - unstableEvaluator: - title: unstableEvaluator - type: object - properties: - type: - type: string - enum: - - llm_as_judge - - code - prompt: - type: string - description: Prompt template used during evaluation. - outputDefinition: - $ref: '#/components/schemas/unstablePublicEvaluatorOutputDefinition' - description: >- - Structured output schema returned by this evaluator. - - - Responses always include `dataType` and omit the internal - output-definition `version`. - - Use `dataType` to decide how future scores should be interpreted. - modelConfig: - $ref: '#/components/schemas/unstableEvaluatorModelConfig' - description: Explicit model configuration, or `null` when the project default - evaluation model is used. - sourceCode: - type: string - description: Source code executed for each matched observation. - sourceCodeLanguage: - $ref: '#/components/schemas/unstableCodeEvaluatorSourceCodeLanguage' - description: Runtime language for `sourceCode`. - required: - - type - unstableEvaluatorBase: - title: unstableEvaluatorBase - type: object - properties: - id: - type: string - description: Identifier of this evaluator. - name: - type: string - description: Evaluator name. - version: - type: integer - description: Version number of this evaluator. - scope: - $ref: '#/components/schemas/unstableEvaluatorScope' - description: >- - Where this evaluator comes from: your project or Langfuse-managed - defaults. - variables: - type: array - items: - type: string - description: >- - Variables that can be mapped when creating an evaluation rule. - - - LLM evaluators require every variable to be mapped exactly once. - Code evaluators always expose the fixed runtime payload fields and - Langfuse maps them automatically. - evaluationRuleCount: - type: integer - description: >- - Number of evaluation rules in the project that currently use this - evaluator version. - createdAt: - type: string - format: date-time - description: Timestamp when this evaluator was created. - updatedAt: - type: string - format: date-time - description: Timestamp when this evaluator was last updated. - required: - - id - - name - - version - - scope - - variables - - evaluationRuleCount - - createdAt - - updatedAt - unstableLlmAsJudgeEvaluator: - title: unstableLlmAsJudgeEvaluator - type: object - properties: - prompt: - type: string - description: Prompt template used during evaluation. - outputDefinition: - $ref: '#/components/schemas/unstablePublicEvaluatorOutputDefinition' - description: >- - Structured output schema returned by this evaluator. - - - Responses always include `dataType` and omit the internal - output-definition `version`. - - Use `dataType` to decide how future scores should be interpreted. - modelConfig: - $ref: '#/components/schemas/unstableEvaluatorModelConfig' - nullable: true - description: >- - Explicit model configuration, or `null` when the project default - evaluation model is used. - required: - - prompt - - outputDefinition - - modelConfig - allOf: - - $ref: '#/components/schemas/unstableEvaluatorBase' - unstableCodeEvaluator: - title: unstableCodeEvaluator - type: object - properties: - sourceCode: - type: string - description: Source code executed for each matched observation. - sourceCodeLanguage: - $ref: '#/components/schemas/unstableCodeEvaluatorSourceCodeLanguage' - description: Runtime language for `sourceCode`. - required: - - sourceCode - - sourceCodeLanguage - allOf: - - $ref: '#/components/schemas/unstableEvaluatorBase' - unstableEvaluators: - title: unstableEvaluators - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/unstableEvaluator' - meta: - $ref: '#/components/schemas/utilsMetaResponse' - required: - - data - - meta - unstableCreateEvaluatorRequest: - title: unstableCreateEvaluatorRequest - type: object - properties: - type: - type: string - enum: - - llm_as_judge - - code - name: - type: string - description: Evaluator name within the authenticated project. - prompt: - type: string - description: Prompt template used by the evaluator. - outputDefinition: - $ref: '#/components/schemas/unstableEvaluatorOutputDefinition' - description: >- - Structured output schema the evaluator must return. - - - Always send `dataType`. - - Do not send `version`; it is an internal storage detail and not part - of the public request contract. - modelConfig: - $ref: '#/components/schemas/unstableEvaluatorModelConfig' - description: Optional explicit model configuration. Omit or set to `null` to use - the project default evaluation model. - sourceCode: - type: string - description: Code executed for each matched observation. - sourceCodeLanguage: - $ref: '#/components/schemas/unstableCodeEvaluatorSourceCodeLanguage' - description: Runtime language for `sourceCode`. - required: - - type - - name - unstableCreateLlmAsJudgeEvaluatorRequest: - title: unstableCreateLlmAsJudgeEvaluatorRequest - type: object - properties: - name: - type: string - description: Evaluator name within the authenticated project. - prompt: - type: string - description: Prompt template used by the evaluator. - outputDefinition: - $ref: '#/components/schemas/unstableEvaluatorOutputDefinition' - description: >- - Structured output schema the evaluator must return. - - - Always send `dataType`. - - Do not send `version`; it is an internal storage detail and not part - of the public request contract. - modelConfig: - $ref: '#/components/schemas/unstableEvaluatorModelConfig' - nullable: true - description: >- - Optional explicit model configuration. Omit or set to `null` to use - the project default evaluation model. - required: - - name - - prompt - - outputDefinition - unstableCreateCodeEvaluatorRequest: - title: unstableCreateCodeEvaluatorRequest - type: object - properties: - name: - type: string - description: Evaluator name within the authenticated project. - sourceCode: - type: string - description: Code executed for each matched observation. - sourceCodeLanguage: - $ref: '#/components/schemas/unstableCodeEvaluatorSourceCodeLanguage' - description: Runtime language for `sourceCode`. - required: - - name - - sourceCode - - sourceCodeLanguage - utilsMetaResponse: - title: utilsMetaResponse - type: object - properties: - page: - type: integer - description: current page number - limit: - type: integer - description: number of items per page - totalItems: - type: integer - description: number of total items given the current filters/selection (if any) - totalPages: - type: integer - description: number of total pages given the current limit - required: - - page - - limit - - totalItems - - totalPages - securitySchemes: - BasicAuth: - type: http - scheme: basic diff --git a/package.json b/package.json index fba01be..e7d161b 100644 --- a/package.json +++ b/package.json @@ -23,30 +23,28 @@ "files": [ "bin", "dist", - "openapi.yml", "README.md" ], "scripts": { "test": "bun test", + "typecheck": "tsc --noEmit", "conformance:sync": "bun conformance/src/cli.ts sync", "conformance:run": "bun conformance/src/cli.ts run", - "patch-openapi": "bun scripts/patch-openapi.ts", - "refetch-openapi": "bun scripts/patch-openapi.ts --refetch", - "build": "bun run refetch-openapi && bun build src/cli.ts --outdir dist --target node --format esm", + "conformance:all": "bun conformance/src/all.ts", + "build": "bun scripts/build.ts", "release": "bun scripts/release.ts", - "prepublishOnly": "rm -rf dist && bun run build" - }, - "dependencies": { - "specli": "^0.0.39" + "prepublishOnly": "bun run build" }, + "dependencies": {}, "devDependencies": { "@apidevtools/swagger-parser": "^12.1.0", "@types/bun": "^1.3.14", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", + "typescript": "^7.0.2", "yaml": "^2.8.2" }, "engines": { - "node": ">=20" + "bun": ">=1.3.0" } } diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..604a715 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,54 @@ +import { mkdir, rm } from "node:fs/promises"; +import { resolve } from "node:path"; + +import { loadCatalog, readVerifiedSpec } from "../conformance/src/catalog"; +import { compileApiContract } from "../src/contracts/compiler"; +import type { ApiContractCatalog } from "../src/contracts/types"; + +const root = resolve(import.meta.dirname, ".."); +const dist = resolve(root, "dist"); +const contractsDirectory = resolve(dist, "contracts"); + +await rm(dist, { recursive: true, force: true }); +await mkdir(contractsDirectory, { recursive: true }); + +const sourceCatalog = await loadCatalog(); +const contractCatalog: ApiContractCatalog = { + schemaVersion: 1, + latest: sourceCatalog.versions.at(-1)!.version, + versions: sourceCatalog.versions.map((entry) => ({ + version: entry.version, + sourceSha256: entry.sha256, + })), +}; + +let totalOperations = 0; +for (const entry of sourceCatalog.versions) { + const raw = await readVerifiedSpec(entry); + const contract = compileApiContract(entry, raw); + totalOperations += contract.operations.length; + await Bun.write( + resolve(contractsDirectory, `${entry.version}.json`), + `${JSON.stringify(contract)}\n`, + ); +} +await Bun.write( + resolve(contractsDirectory, "catalog.json"), + `${JSON.stringify(contractCatalog)}\n`, +); + +const result = await Bun.build({ + entrypoints: [resolve(root, "src/cli.ts")], + outdir: dist, + target: "bun", + format: "esm", + minify: true, +}); +if (!result.success) { + for (const log of result.logs) process.stderr.write(`${log}\n`); + process.exit(1); +} + +process.stdout.write( + `Built native Bun CLI with ${sourceCatalog.versions.length} contracts and ${totalOperations} operations\n`, +); diff --git a/scripts/patch-openapi.ts b/scripts/patch-openapi.ts deleted file mode 100644 index ebe1774..0000000 --- a/scripts/patch-openapi.ts +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Flattens discriminated unions (oneOf) in the OpenAPI spec into flat objects. - * - * specli generates CLI flags from request body schemas but can't handle oneOf/allOf yet. - * This script detects discriminated unions in components.schemas and merges their - * branches into a single flat object with unioned properties and intersected required. - * - * Uses parseDocument to preserve original YAML formatting of untouched nodes. - */ - -import { readFileSync, writeFileSync } from "fs"; -import { parseDocument, type Document } from "yaml"; -import { resolve } from "path"; -import { parseArgs } from "util"; - -const DEFAULT_OPENAPI_URL = "https://cloud.langfuse.com/generated/api/openapi.yml"; - -const { values: args } = parseArgs({ - args: process.argv.slice(2), - options: { - refetch: { type: "boolean", default: false }, - openapi_url: { type: "string", default: DEFAULT_OPENAPI_URL }, - }, -}); - -const specPath = resolve(import.meta.dirname!, "../openapi.yml"); - -if (args.refetch) { - const url = args.openapi_url!; - console.log(`Fetching spec from ${url}...`); - const res = await fetch(url); - if (!res.ok) { - console.error(`Failed to fetch: ${res.status} ${res.statusText}`); - process.exit(1); - } - writeFileSync(specPath, await res.text()); - console.log(`Wrote fresh spec to ${specPath}`); -} - -const raw = readFileSync(specPath, "utf-8"); -const doc: Document = parseDocument(raw); - -const schemas = doc.getIn(["components", "schemas"], true) as any; -if (!schemas || !schemas.items) { - console.log("No components.schemas found, nothing to patch."); - process.exit(0); -} - -// Convert to JS for analysis (easier to work with) -const schemasJS = schemas.toJSON() as Record; -let patchCount = 0; - -for (const [name, schema] of Object.entries(schemasJS)) { - if (!schema.oneOf || !Array.isArray(schema.oneOf)) continue; - - // Check if every branch matches the discriminated union pattern: - // { allOf: [{ properties: { : { enum: [val] } } }, { $ref }], required: [] } - const branches: Array<{ - discriminatorKey: string; - discriminatorValue: string; - refSchemaName: string; - }> = []; - - let isDiscriminatedUnion = true; - for (const branch of schema.oneOf) { - if (!branch.allOf || branch.allOf.length !== 2) { - isDiscriminatedUnion = false; - break; - } - - const [inline, ref] = branch.allOf; - const props = inline?.properties; - if (!props || !ref?.$ref) { - isDiscriminatedUnion = false; - break; - } - - // Find the discriminator: a property with a single-value enum - const discEntries = Object.entries(props).filter( - ([, v]) => v.type === "string" && Array.isArray(v.enum) && v.enum.length === 1, - ); - if (discEntries.length !== 1) { - isDiscriminatedUnion = false; - break; - } - - const [discKey, discSchema] = discEntries[0]; - const refName = ref.$ref.replace("#/components/schemas/", ""); - - branches.push({ - discriminatorKey: discKey, - discriminatorValue: discSchema.enum[0], - refSchemaName: refName, - }); - } - - if (!isDiscriminatedUnion || branches.length === 0) continue; - - // all branches should use the same discriminator key - const discKey = branches[0].discriminatorKey; - if (!branches.every((b) => b.discriminatorKey === discKey)) continue; - - const mergedProperties: Record = {}; - const requiredSets: Set[] = []; - - // property to discriminate - mergedProperties[discKey] = { - type: "string", - enum: branches.map((b) => b.discriminatorValue), - }; - - for (const branch of branches) { - const branchSchema = schemasJS[branch.refSchemaName]; - if (!branchSchema?.properties) continue; - - const branchRequired = new Set(branchSchema.required ?? []); - requiredSets.push(branchRequired); - - for (const [propName, propSchema] of Object.entries(branchSchema.properties)) { - if (propName === discKey) continue; // already handled - - if (!(propName in mergedProperties)) { - mergedProperties[propName] = structuredClone(propSchema); - } else { - // property exists in multiple branches — check for type conflict - const existing = mergedProperties[propName]; - if (JSON.stringify(existing) !== JSON.stringify(propSchema)) { - // conflict: fall back to string so specli still exposes the flag - mergedProperties[propName] = { - type: "string", - ...(existing.description ? { description: existing.description } : {}), - ...(existing.nullable ? { nullable: true } : {}), - }; - } - } - } - } - - // Required = intersection of all branches' required fields + discriminator - const intersectedRequired = - requiredSets.length > 0 - ? [...requiredSets[0]].filter((r) => requiredSets.every((s) => s.has(r))) - : []; - const required = [discKey, ...intersectedRequired.filter((r) => r !== discKey)]; - - // Strip nullable from properties that have no type (specli errors on these) - for (const [propName, propSchema] of Object.entries(mergedProperties)) { - if (propSchema.nullable && !propSchema.type) { - delete propSchema.nullable; - } - } - - const patched: any = { - title: schema.title ?? name, - type: "object", - properties: mergedProperties, - }; - if (required.length > 0) { - patched.required = required; - } - - // Replace the schema node in the document (preserves rest of doc formatting) - doc.setIn(["components", "schemas", name], doc.createNode(patched)); - patchCount++; - console.log( - `Patched ${name}: merged ${branches.length} branches, ${required.length} required fields`, - ); -} - -// Remove paths that shouldn't be exposed to CLI users -// const hiddenPaths = ["/api/public/traces", "/api/public/traces/{traceId}"]; -const hiddenPaths: string[] = []; - -const paths = doc.getIn(["paths"], true) as any; -if (paths?.items) { - paths.items = paths.items.filter((pair: any) => { - const pathStr = pair.key?.value; - if (hiddenPaths.includes(pathStr)) { - console.log(`Removed path: ${pathStr}`); - return false; - } - return true; - }); -} - -// Patch operation descriptions with examples -const examples: Record = { - prompts_create: [ - "Create a new version for the prompt with the given `name`", - "", - "Example:", - " langfuse api prompts create --type text --name my-prompt --prompt 'Hello {{name}}'", - ].join("\n"), -}; - -if (paths?.items) { - for (const pathPair of paths.items) { - const methods = pathPair.value; - if (!methods?.items) continue; - for (const methodPair of methods.items) { - const op = methodPair.value; - if (!op?.items) continue; - for (const field of op.items) { - if (field.key?.value === "operationId" && examples[field.value?.value]) { - for (const descField of op.items) { - if (descField.key?.value === "description") { - descField.value = doc.createNode(examples[field.value.value]); - break; - } - } - } - } - } - } -} - -// Rename query parameters that collide with specli's global flags. -// specli (via commander.js) reserves "--version" for CLI version display, -// so any OpenAPI query parameter named "version" becomes unusable. -const paramRenames: Record> = { - prompts_get: { version: "prompt-version" }, -}; - -let renameCount = 0; -if (paths?.items) { - for (const pathPair of paths.items) { - const methods = pathPair.value; - if (!methods?.items) continue; - for (const methodPair of methods.items) { - const op = methodPair.value; - if (!op?.items) continue; - - let operationId = ""; - for (const field of op.items) { - if (field.key?.value === "operationId") { - operationId = field.value?.value ?? ""; - break; - } - } - - const renames = paramRenames[operationId]; - if (!renames) continue; - - for (const field of op.items) { - if (field.key?.value !== "parameters") continue; - const params = field.value; - if (!params?.items) continue; - - for (const param of params.items) { - if (!param?.items) continue; - let nameField: any = null; - let inValue = ""; - for (const pf of param.items) { - if (pf.key?.value === "name") nameField = pf; - if (pf.key?.value === "in") inValue = pf.value?.value ?? ""; - } - const oldName = nameField?.value?.value; - if (oldName && inValue === "query" && renames[oldName]) { - nameField.value = doc.createNode(renames[oldName]); - renameCount++; - console.log( - `Renamed ${operationId} query param '${oldName}' → '${renames[oldName]}'`, - ); - } - } - } - } - } -} - -const dirty = patchCount > 0 || renameCount > 0; -if (dirty) { - writeFileSync(specPath, doc.toString({ singleQuote: true })); - console.log(`\nWrote patched spec to ${specPath} (${patchCount} schema(s), ${renameCount} param rename(s))`); -} else { - console.log("No patches needed."); -} diff --git a/scripts/release.ts b/scripts/release.ts index d8fb059..a2d5699 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -15,10 +15,9 @@ const exactReleaseFiles = new Set([ "LICENSE", "README.md", "bun.lock", - "openapi.yml", "package.json", ]); -const releasePathPrefixes = ["bin/", "scripts/", "src/"]; +const releasePathPrefixes = ["bin/", "conformance/", "scripts/", "src/"]; const rawArgs = process.argv.slice(2); const isDryRun = rawArgs.includes("--dry-run"); const allowDirty = rawArgs.includes("--allow-dirty"); @@ -119,8 +118,14 @@ async function runCommand( stdout: options.capture ? "pipe" : "inherit", stderr: options.capture ? "pipe" : "inherit", }); - const stdoutPromise = options.capture ? proc.stdout.text() : Promise.resolve(""); - const stderrPromise = options.capture ? proc.stderr.text() : Promise.resolve(""); + const stdoutPromise = + proc.stdout instanceof ReadableStream + ? new Response(proc.stdout).text() + : Promise.resolve(""); + const stderrPromise = + proc.stderr instanceof ReadableStream + ? new Response(proc.stderr).text() + : Promise.resolve(""); exitCode = await proc.exited; stdout = await stdoutPromise; stderr = await stderrPromise; @@ -252,7 +257,7 @@ async function printPostBuildReview(): Promise { "LICENSE", "README.md", "bin", - "openapi.yml", + "conformance", "package.json", "scripts", "src", @@ -379,8 +384,9 @@ async function main(): Promise { await writePackageJson(pkg); console.log(`Updated package.json to ${pkg.name}@${nextVersion}`); + await runCommand("bun", ["run", "typecheck"]); await runCommand("bun", ["test"]); - await runCommand("bun", ["run", "prepublishOnly"]); + await runCommand("bun", ["run", "conformance:all"]); await runCommand("npm", ["pack", "--dry-run"]); await printPostBuildReview(); @@ -401,7 +407,7 @@ async function main(): Promise { return; } - // prepublishOnly already ran above, and npm pack --dry-run showed the package + // conformance:all already built above, and npm pack --dry-run showed the package // contents. Avoid a second lifecycle run producing a different publish. publishStarted = true; await runCommand("npm", ["publish", "--ignore-scripts"], { diff --git a/src/cli.ts b/src/cli.ts index 2bad7f1..ce3237a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,91 +1,163 @@ -import { readFileSync } from "node:fs"; +import packageJson from "../package.json"; + +import { createApiClient, renderCurl } from "./client"; +import { + loadApiContract, + loadContractCatalog, + resolveContractVersion, +} from "./contracts/loader"; +import type { + ApiBodyField, + ApiCallInput, + ApiContract, + ApiOperation, + ApiParameter, + ApiResult, + JsonValue, + ValueKind, +} from "./contracts/types"; const DEFAULT_HOST = "https://cloud.langfuse.com"; -const OPENAPI_FILE_URL = new URL("../openapi.yml", import.meta.url); +const DEFAULT_TIMEOUT_MS = 30_000; const LANGFUSE_SKILL_URL = "https://raw.githubusercontent.com/langfuse/skills/main/skills/langfuse/SKILL.md"; -const GET_SKILL_FETCH_TIMEOUT_MS = 5000; -const LANGFUSE_FLAGS = new Set([ +const GET_SKILL_FETCH_TIMEOUT_MS = 5_000; +const VALUE_FLAGS = new Set([ "--public-key", "--secret-key", "--host", "--env", + "--api-version", + "--timeout", + "--output", ]); -const LANGFUSE_BOOL_FLAGS = new Set(["--refetch-api-spec"]); +const BOOLEAN_FLAGS = new Set(["--json", "--curl", "--show-secrets"]); -function loadEnvFile(filePath: string): void { - const content = readFileSync(filePath, "utf-8"); - for (const line of content.split("\n")) { +interface ParsedGlobals { + values: Record; + booleans: Set; + args: string[]; +} + +interface RuntimeConfig { + publicKey?: string; + secretKey?: string; + host: string; + apiVersion?: string; + timeoutMs: number; + json: boolean; + curl: boolean; + showSecrets: boolean; + output?: string; +} + +class CliError extends Error { + constructor(message: string, readonly exitCode = 2) { + super(message); + } +} + +function flagKey(flag: string): string { + return flag.replace(/^--/, ""); +} + +function extractGlobals(args: string[]): ParsedGlobals { + const values: Record = {}; + const booleans = new Set(); + const remaining: string[] = []; + for (let index = 0; index < args.length; index++) { + const token = args[index]; + const equals = token.indexOf("="); + const name = equals === -1 ? token : token.slice(0, equals); + if (VALUE_FLAGS.has(name)) { + const value = equals === -1 ? args[index + 1] : token.slice(equals + 1); + if (value === undefined || (equals === -1 && value.startsWith("--"))) { + throw new CliError(`${name} requires a value`); + } + values[flagKey(name)] = value; + if (equals === -1) index++; + continue; + } + if (BOOLEAN_FLAGS.has(name)) { + booleans.add(flagKey(name)); + continue; + } + remaining.push(token); + } + return { values, booleans, args: remaining }; +} + +function parseEnv(content: string): Record { + const result: Record = {}; + for (const line of content.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; - const eqIdx = trimmed.indexOf("="); - if (eqIdx === -1) continue; - const key = trimmed.slice(0, eqIdx).trim(); - let val = trimmed.slice(eqIdx + 1).trim(); + const separator = trimmed.indexOf("="); + if (separator === -1) continue; + const key = trimmed.slice(0, separator).trim(); + let value = trimmed.slice(separator + 1).trim(); if ( - (val.startsWith('"') && val.endsWith('"')) || - (val.startsWith("'") && val.endsWith("'")) + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) ) { - val = val.slice(1, -1); + value = value.slice(1, -1); } - process.env[key] = val; + result[key] = value; } + return result; } -type MainFn = ( - argv: string[], - options?: { cliName?: string; auth?: string; embeddedSpecText?: string }, -) => Promise; - -async function loadMain(): Promise { - const specliEntry = import.meta.resolve("specli"); - const cliMainUrl = new URL("cli/main.js", specliEntry); - const mod = await import(cliMainUrl.href); - return mod.main; -} - -async function getSpecText(params: { - refetch: boolean; - host: string; -}): Promise { - if (params.refetch) { - const specUrl = `${params.host}/generated/api/openapi.yml`; - return fetchText(specUrl, "spec"); +async function runtimeConfig(globals: ParsedGlobals): Promise { + const fileEnv = globals.values.env + ? parseEnv(await Bun.file(globals.values.env).text()) + : {}; + const env = { ...process.env, ...fileEnv }; + const timeoutMs = Number(globals.values.timeout ?? DEFAULT_TIMEOUT_MS); + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + throw new CliError("--timeout must be a positive number of milliseconds"); } - - // Use bundled spec - return readFileSync(OPENAPI_FILE_URL, "utf-8"); + return { + publicKey: globals.values["public-key"] ?? env.LANGFUSE_PUBLIC_KEY, + secretKey: globals.values["secret-key"] ?? env.LANGFUSE_SECRET_KEY, + host: ( + globals.values.host ?? + env.LANGFUSE_BASE_URL ?? + env.LANGFUSE_HOST ?? + DEFAULT_HOST + ).replace(/\/+$/, ""), + apiVersion: globals.values["api-version"] ?? env.LANGFUSE_API_VERSION, + timeoutMs, + json: globals.booleans.has("json"), + curl: globals.booleans.has("curl"), + showSecrets: globals.booleans.has("show-secrets"), + output: globals.values.output, + }; } async function fetchText( url: string, label: string, - options?: { timeoutMs?: number }, + timeoutMs?: number, ): Promise { - const resp = await fetch(url, { - signal: - typeof options?.timeoutMs === "number" - ? AbortSignal.timeout(options.timeoutMs) - : undefined, + const response = await fetch(url, { + signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined, }); - if (!resp.ok) { + if (!response.ok) { throw new Error( - `Failed to fetch ${label} from ${url}: ${resp.status} ${resp.statusText}`, + `Failed to fetch ${label} from ${url}: ${response.status} ${response.statusText}`, ); } - return resp.text(); + return response.text(); } -async function getSkillText(): Promise { - return fetchText(LANGFUSE_SKILL_URL, "skill", { - timeoutMs: GET_SKILL_FETCH_TIMEOUT_MS, - }); -} - -function printGetSkillFetchError(err: unknown): void { - const reason = err instanceof Error ? err.message : String(err); - - process.stderr.write(`Failed to fetch the latest Langfuse skill from GitHub. +async function getSkill(): Promise { + try { + process.stdout.write( + await fetchText(LANGFUSE_SKILL_URL, "skill", GET_SKILL_FETCH_TIMEOUT_MS), + ); + } catch (error) { + const reason = error instanceof Error ? error.message : String(error); + process.stderr.write(`Failed to fetch the latest Langfuse skill from GitHub. This environment may block direct GitHub access. Download the skill manually from: @@ -95,68 +167,12 @@ Then add the downloaded SKILL.md to your agent context manually. Original error: ${reason} `); -} - -export async function run(argv: string[]): Promise { - const extracted: Record = {}; - const boolFlags: Record = {}; - const passthrough: string[] = [argv[0], argv[1]]; - - let i = 2; - while (i < argv.length) { - if (LANGFUSE_FLAGS.has(argv[i]) && i + 1 < argv.length) { - const key = argv[i].replace(/^--/, ""); - extracted[key] = argv[i + 1]; - i += 2; - } else if (LANGFUSE_BOOL_FLAGS.has(argv[i])) { - const key = argv[i].replace(/^--/, ""); - boolFlags[key] = true; - i++; - } else { - passthrough.push(argv[i]); - i++; - } - } - - if (extracted["env"]) { - loadEnvFile(extracted["env"]); - } - - const publicKey = - extracted["public-key"] ?? process.env.LANGFUSE_PUBLIC_KEY; - const secretKey = - extracted["secret-key"] ?? process.env.LANGFUSE_SECRET_KEY; - const host = ( - extracted["host"] ?? - process.env.LANGFUSE_BASE_URL ?? - process.env.LANGFUSE_HOST ?? - DEFAULT_HOST - ).replace(/\/$/, ""); - - // First positional arg determines the subcommand - const subcommand = passthrough[2]; - - if (subcommand === "api") { - passthrough.splice(2, 1); - return runApi({ passthrough, boolFlags, publicKey, secretKey, host }); - } - - if (subcommand === "get-skill") { - try { - process.stdout.write(await getSkillText()); - } catch (err) { - printGetSkillFetchError(err); - process.exitCode = 1; - } - return; + process.exitCode = 1; } - - // Show help for anything else (no args, --help, -h, unknown command) - printHelp(); } function printHelp(): void { - console.log(`langfuse-cli — Interact with Langfuse from the command line + process.stdout.write(`langfuse-cli — Interact with Langfuse from the command line Usage: langfuse [options] @@ -167,100 +183,531 @@ Commands: Options: --public-key Langfuse public key (or LANGFUSE_PUBLIC_KEY) --secret-key Langfuse secret key (or LANGFUSE_SECRET_KEY) - --host Langfuse host (or LANGFUSE_HOST/LANGFUSE_BASE_URL, default: ${DEFAULT_HOST}) - --env Load env vars from file - --refetch-api-spec Fetch latest API spec instead of bundled + --host Langfuse host (default: ${DEFAULT_HOST}) + --env Load env vars from a file + --api-version Select a bundled historical API contract + --timeout Request timeout (default: ${DEFAULT_TIMEOUT_MS}) + -h, --help Show help + --version Show CLI version Examples: - langfuse api __schema List all available resources - langfuse api --help Show actions for a resource - langfuse api traces list --limit 10 List traces - langfuse api prompts list List prompts - langfuse api scores create --name quality \\ - --traceId --value 0.9 Create a score - langfuse api datasets create --name my-dataset Create a dataset`); + langfuse api help + langfuse api prompts list + langfuse api prompts create --body-json '{"name":"my-prompt","type":"text","prompt":"Hello"}' + langfuse --api-version 3.150.0 api traces list +`); } -function printApiHelp(resources: string[]): void { - const sorted = [...resources].sort(); - console.log(`Usage: langfuse api [options] +function resourceMap(contract: ApiContract): Map { + const resources = new Map(); + for (const operation of contract.operations) { + const existing = resources.get(operation.command.resource) ?? []; + existing.push(operation); + resources.set(operation.command.resource, existing); + } + for (const operations of resources.values()) { + operations.sort((left, right) => + left.command.action.localeCompare(right.command.action), + ); + } + return resources; +} -Langfuse API Resources: -${sorted.map((r) => ` ${r}`).join("\n")} +function printApiHelp(contract: ApiContract): void { + const resources = [...resourceMap(contract).keys()].sort(); + process.stdout.write(`Usage: langfuse api [options] -Commands: - __schema Show API spec metadata - --help Show actions for a resource - --help Show options for an action +API snapshot: ${contract.apiVersion} + +Resources: +${resources.map((resource) => ` ${resource}`).join("\n")} + +Discovery: + api help [resource] [action] + api schema --json Machine-readable command schema + api __schema --json Backward-compatible alias + api versions list Bundled historical snapshots + +Action options: + --body-json Lossless JSON request body + --body-file Read JSON body from file or stdin + --json Stable JSON response envelope + --curl Print curl without executing +`); +} + +function printResourceHelp(contract: ApiContract, resource: string): void { + const operations = resourceMap(contract).get(resource); + if (!operations) throw new CliError(`Unknown API resource: ${resource}`); + process.stdout.write(`Usage: langfuse api ${resource} [options] + +Actions: +${operations + .map( + (operation) => + ` ${operation.command.action.padEnd(30)} ${operation.summary ?? operation.operationId}`, + ) + .join("\n")} +`); +} + +function kindLabel(kind: ValueKind): string { + return kind === "array" ? "value (repeatable)" : kind; +} +function printOperationHelp(operation: ApiOperation): void { + const positionals = operation.pathParameterOrder + .map((name) => `<${name}>`) + .join(" "); + const lines: string[] = []; + for (const parameter of operation.parameters) { + if (parameter.location === "path") continue; + lines.push( + ` --${parameter.cliName} <${kindLabel(parameter.kind)}>${parameter.required ? " (required)" : ""}`, + ); + } + if (operation.requestBody?.legacyFieldFlags) { + for (const field of operation.requestBody.fields) { + lines.push( + ` --${field.name} <${kindLabel(field.kind)}>${field.required ? " (required)" : ""}`, + ); + } + } + if (operation.requestBody) { + lines.push(" --body-json Lossless JSON body"); + lines.push(" --body-file JSON body from file or stdin"); + } + process.stdout.write(`Usage: langfuse api ${operation.command.resource} ${operation.command.action}${positionals ? ` ${positionals}` : ""} [options] + +${operation.summary ?? operation.operationId} +${operation.description ? `\n${operation.description}\n` : ""} Options: - --json Output as JSON - --curl Preview curl command without executing - -h, --help Show help +${lines.length ? lines.join("\n") : " (no operation-specific options)"} + --json JSON response envelope + --curl Print curl without executing +`); +} + +function operationByCommand( + contract: ApiContract, + resource: string, + action: string, +): ApiOperation { + const operation = contract.operations.find( + (candidate) => + candidate.command.resource === resource && candidate.command.action === action, + ); + if (!operation) { + if (!resourceMap(contract).has(resource)) { + throw new CliError(`Unknown API resource: ${resource}`); + } + throw new CliError(`Unknown action ${resource} ${action}`); + } + return operation; +} + +function parseJsonValue(value: string, kind?: ValueKind): JsonValue { + if (kind === "string") return value; + if (kind === "boolean") { + if (value === "true") return true; + if (value === "false") return false; + throw new CliError(`Expected boolean, got ${value}`); + } + if (kind === "number") { + const number = Number(value); + if (!Number.isFinite(number)) throw new CliError(`Expected number, got ${value}`); + return number; + } + if (kind === "object" || kind === "array" || kind === "null") { + let parsed: JsonValue; + try { + parsed = JSON.parse(value) as JsonValue; + } catch { + throw new CliError(`Expected ${kind} as JSON, got ${value}`); + } + if ( + (kind === "object" && + (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))) || + (kind === "array" && !Array.isArray(parsed)) || + (kind === "null" && parsed !== null) + ) { + throw new CliError(`Expected ${kind} as JSON, got ${value}`); + } + return parsed; + } + try { + return JSON.parse(value) as JsonValue; + } catch { + return value; + } +} -Workflow: - 1) langfuse api __schema - 2) langfuse api --help - 3) langfuse api --help - 4) langfuse api [options]`); +function addParameterValue( + input: ApiCallInput, + parameter: ApiParameter, + raw: string | undefined, +): void { + const target = + parameter.location === "path" + ? input.path + : parameter.location === "query" + ? input.query + : parameter.location === "header" + ? input.headers + : input.cookies; + if (raw === undefined && parameter.kind !== "boolean") { + throw new CliError(`--${parameter.cliName} requires a value`); + } + const parsed = parseJsonValue(raw ?? "true", parameter.itemKind ?? parameter.kind); + if (parameter.kind === "array") { + const existing = target[parameter.name]; + if (Array.isArray(existing)) existing.push(parsed); + else target[parameter.name] = [parsed]; + } else { + target[parameter.name] = parsed; + } +} + +function setBodyValue( + body: Record, + path: string[], + raw: string | undefined, + field?: ApiBodyField, +): void { + let target = body; + for (const segment of path.slice(0, -1)) { + const existing = target[segment]; + if (!existing || typeof existing !== "object" || Array.isArray(existing)) { + target[segment] = {}; + } + target = target[segment] as Record; + } + const name = path.at(-1)!; + const kind = path.length > 1 || field?.kind === "array" ? undefined : field?.kind; + const parsed = parseJsonValue(raw ?? "true", kind); + const existing = target[name]; + if (field?.kind === "array") { + if (Array.isArray(parsed)) target[name] = parsed; + else if (Array.isArray(existing)) existing.push(parsed); + else target[name] = [parsed]; + } else if (existing !== undefined) { + target[name] = Array.isArray(existing) ? [...existing, parsed] : [existing, parsed]; + } else { + target[name] = parsed; + } } -async function getResources(specText: string): Promise { - // Run specli's __schema --json to get the canonical resource list - const main = await loadMain(); - const chunks: string[] = []; - const origWrite = process.stdout.write.bind(process.stdout); - process.stdout.write = (chunk: any) => { - chunks.push(String(chunk)); - return true; +function splitOption(token: string): { name: string; inline?: string; negated: boolean } { + const separator = token.indexOf("="); + const rawName = separator === -1 ? token.slice(2) : token.slice(2, separator); + return { + name: rawName.startsWith("no-") ? rawName.slice(3) : rawName, + ...(separator === -1 ? {} : { inline: token.slice(separator + 1) }), + negated: rawName.startsWith("no-"), }; +} + +async function readBodyFile(path: string): Promise { + const text = path === "-" ? await Bun.stdin.text() : await Bun.file(path).text(); try { - await main(["node", "langfuse", "__schema", "--json"], { - cliName: "langfuse api", - auth: "BasicAuth", - embeddedSpecText: specText, - }); - } finally { - process.stdout.write = origWrite; - } - const output = JSON.parse(chunks.join("")); - return (output.data?.resources ?? []).map((r: any) => r.name); + return JSON.parse(text) as JsonValue; + } catch (error) { + throw new CliError( + `Invalid JSON in ${path === "-" ? "stdin" : path}: ${error instanceof Error ? error.message : String(error)}`, + ); + } } -async function runApi(params: { - passthrough: string[]; - boolFlags: Record; - publicKey: string | undefined; - secretKey: string | undefined; - host: string; -}): Promise { - const { passthrough, boolFlags, publicKey, secretKey, host } = params; +async function parseOperationInput( + operation: ApiOperation, + tokens: string[], +): Promise { + const input: ApiCallInput = { + path: {}, + query: {}, + headers: {}, + cookies: {}, + }; + const parameterByFlag = new Map(); + for (const parameter of operation.parameters) { + if (parameter.location !== "path") { + parameterByFlag.set(parameter.cliName, parameter); + } + } + if (operation.operationId === "prompts_get") { + const version = operation.parameters.find( + (parameter) => parameter.location === "query" && parameter.name === "version", + ); + if (version) parameterByFlag.set("prompt-version", version); + } + const positionals: string[] = []; + let fieldBody: Record | undefined; + let completeBody: JsonValue | undefined; + for (let index = 0; index < tokens.length; index++) { + const token = tokens[index]; + if (!token.startsWith("--")) { + positionals.push(token); + continue; + } + const option = splitOption(token); + let raw = option.inline; + if ( + raw === undefined && + tokens[index + 1] !== undefined && + !tokens[index + 1].startsWith("--") + ) { + raw = tokens[++index]; + } + if (option.name === "body-json") { + if (raw === undefined) throw new CliError("--body-json requires a value"); + try { + completeBody = JSON.parse(raw) as JsonValue; + } catch (error) { + throw new CliError( + `Invalid --body-json: ${error instanceof Error ? error.message : String(error)}`, + ); + } + continue; + } + if (option.name === "body-file") { + if (raw === undefined) throw new CliError("--body-file requires a path or -"); + completeBody = await readBodyFile(raw); + continue; + } + const parameter = parameterByFlag.get(option.name); + if (parameter) { + if (option.negated && parameter.kind !== "boolean") { + throw new CliError(`--no-${option.name} is only valid for boolean options`); + } + addParameterValue(input, parameter, option.negated ? "false" : raw); + continue; + } + if (!operation.requestBody) { + throw new CliError(`Unknown option --${option.name}`); + } + if (!operation.requestBody.legacyFieldFlags) { + throw new CliError( + `${operation.operationId} requires --body-json or --body-file for request bodies`, + ); + } + const path = option.name.split(".").filter(Boolean); + const field = operation.requestBody.fields.find( + (candidate) => candidate.name === path[0], + ); + if (!field) throw new CliError(`Unknown option --${option.name}`); + if (option.negated && field.kind !== "boolean") { + throw new CliError(`--no-${option.name} is only valid for boolean options`); + } + if (raw === undefined && field.kind !== "boolean") { + throw new CliError(`--${option.name} requires a value`); + } + fieldBody ??= {}; + setBodyValue(fieldBody, path, option.negated ? "false" : raw, field); + } + if (completeBody !== undefined && fieldBody !== undefined) { + throw new CliError("Do not mix --body-json/--body-file with body field flags"); + } + if (positionals.length !== operation.pathParameterOrder.length) { + throw new CliError( + `${operation.operationId} expects ${operation.pathParameterOrder.length} path argument(s), got ${positionals.length}`, + ); + } + for (let index = 0; index < operation.pathParameterOrder.length; index++) { + const name = operation.pathParameterOrder[index]; + const parameter = operation.parameters.find( + (candidate) => candidate.location === "path" && candidate.name === name, + ); + if (!parameter) throw new CliError(`Missing path parameter contract: ${name}`); + input.path[name] = parseJsonValue(positionals[index], parameter.kind); + } + for (const parameter of operation.parameters) { + const target = + parameter.location === "path" + ? input.path + : parameter.location === "query" + ? input.query + : parameter.location === "header" + ? input.headers + : input.cookies; + if (parameter.required && target[parameter.name] === undefined) { + throw new CliError(`Missing required option --${parameter.cliName}`); + } + } + let body = completeBody ?? fieldBody; + if (completeBody === undefined && operation.requestBody?.legacyFieldFlags) { + const missing = operation.requestBody.fields + .filter((field) => field.required && fieldBody?.[field.name] === undefined) + .map((field) => `--${field.name}`); + if (missing.length > 0) { + throw new CliError(`Missing required body option(s): ${missing.join(", ")}`); + } + if (body === undefined && operation.requestBody.required) body = {}; + } + if (operation.requestBody?.required && body === undefined) { + throw new CliError(`${operation.operationId} requires a request body`); + } + if (body !== undefined) input.body = body; + return input; +} - const specText = await getSpecText({ - refetch: boolFlags["refetch-api-spec"] ?? false, - host, - }); +function schemaOutput(contract: ApiContract) { + return { + schemaVersion: 1, + apiVersion: contract.apiVersion, + sourceSha256: contract.sourceSha256, + resources: [...resourceMap(contract)].map(([name, operations]) => ({ + name, + actions: operations.map((operation) => ({ + name: operation.command.action, + canonicalName: operation.command.canonicalAction, + ...(operation.command.aliasOf + ? { aliasOf: operation.command.aliasOf } + : {}), + operationId: operation.operationId, + method: operation.method, + path: operation.path, + auth: operation.auth, + pathParameterOrder: operation.pathParameterOrder, + parameters: operation.parameters, + ...(operation.requestBody + ? { requestBody: operation.requestBody } + : {}), + ...(operation.summary ? { summary: operation.summary } : {}), + ...(operation.description + ? { description: operation.description } + : {}), + })), + })), + }; +} + +async function writeResult( + result: ApiResult, + config: RuntimeConfig, +): Promise { + if (config.output) { + const content = + typeof result.body === "string" + ? result.body + : JSON.stringify(result.body, null, 2); + await Bun.write(config.output, content ?? ""); + } else if (config.json) { + process.stdout.write( + `${JSON.stringify({ status: result.status, headers: result.headers, body: result.body })}\n`, + ); + } else if (typeof result.body === "string") { + process.stdout.write(result.body.endsWith("\n") ? result.body : `${result.body}\n`); + } else if (result.body !== null) { + process.stdout.write(`${JSON.stringify(result.body, null, 2)}\n`); + } + if (!result.ok) process.exitCode = 1; +} - // Intercept help: no args, --help, or -h - const args = passthrough.slice(2); +async function runApi(config: RuntimeConfig, args: string[]): Promise { + const catalog = await loadContractCatalog(); + if (args[0] === "versions") { + const action = args[1] ?? "list"; + if (action === "list") { + process.stdout.write( + `${catalog.versions.map((entry) => entry.version).join("\n")}\n`, + ); + return; + } + if (action === "current") { + process.stdout.write(`${config.apiVersion ?? catalog.latest}\n`); + return; + } + if (action === "detect") { + const resolved = await resolveContractVersion({ + requested: "auto", + host: config.host, + timeoutMs: config.timeoutMs, + catalog, + }); + process.stdout.write( + `${resolved.detected} -> ${resolved.version}\n`, + ); + return; + } + throw new CliError(`Unknown versions action: ${action}`); + } + const resolved = await resolveContractVersion({ + requested: config.apiVersion, + host: config.host, + timeoutMs: config.timeoutMs, + catalog, + }); + const contract = await loadApiContract(resolved.version); if ( args.length === 0 || - (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) + (args[0] === "help" && args.length === 1) || + args[0] === "--help" || + args[0] === "-h" ) { - printApiHelp(await getResources(specText)); + printApiHelp(contract); return; } - - const specliArgv = [...passthrough]; - const inject: string[] = ["--server", host]; - if (publicKey) inject.push("--username", publicKey); - if (secretKey) inject.push("--password", secretKey); - specliArgv.splice(2, 0, ...inject); - - const main = await loadMain(); - await main(specliArgv, { - cliName: "langfuse api", - auth: "BasicAuth", - embeddedSpecText: specText, + if (["schema", "__schema", "__spec"].includes(args[0])) { + const schema = schemaOutput(contract); + if (config.json) process.stdout.write(`${JSON.stringify(schema)}\n`); + else printApiHelp(contract); + return; + } + if (args[0] === "help") { + if (!args[1]) printApiHelp(contract); + else if (!args[2]) printResourceHelp(contract, args[1]); + else printOperationHelp(operationByCommand(contract, args[1], args[2])); + return; + } + const resource = args[0]; + if (!args[1] || args[1] === "help" || args[1] === "--help" || args[1] === "-h") { + printResourceHelp(contract, resource); + return; + } + const operation = operationByCommand(contract, resource, args[1]); + if (args[2] === "help" || args[2] === "--help" || args[2] === "-h") { + printOperationHelp(operation); + return; + } + const input = await parseOperationInput(operation, args.slice(2)); + const client = createApiClient({ + host: config.host, + publicKey: config.publicKey, + secretKey: config.secretKey, + timeoutMs: config.timeoutMs, }); + if (config.curl) { + process.stdout.write( + `${renderCurl(client.prepare(operation, input), { showSecrets: config.showSecrets })}\n`, + ); + return; + } + await writeResult(await client.call(operation, input), config); +} + +export async function run(argv: string[]): Promise { + try { + const globals = extractGlobals(argv.slice(2)); + const [command, ...args] = globals.args; + if (command === "--version") { + process.stdout.write(`${packageJson.version}\n`); + return; + } + if (!command || command === "--help" || command === "-h") { + printHelp(); + return; + } + if (command === "get-skill") { + await getSkill(); + return; + } + if (command !== "api") { + throw new CliError(`Unknown command: ${command}`); + } + await runApi(await runtimeConfig(globals), args); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exitCode = error instanceof CliError ? error.exitCode : 1; + } } diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..43afc74 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,191 @@ +import type { + ApiCallInput, + ApiClientConfig, + ApiOperation, + ApiResult, + JsonValue, +} from "./contracts/types"; + +export interface PreparedRequest { + url: URL; + method: string; + headers: Headers; + body?: string; +} + +function primitive(value: JsonValue): string { + if (value === null) return ""; + if (typeof value === "object") return JSON.stringify(value); + return String(value); +} + +function pathValue(value: JsonValue, style: string, explode: boolean): string { + if (style !== "simple") throw new Error(`Unsupported path style: ${style}`); + if (Array.isArray(value)) return value.map(primitive).join(","); + if (value && typeof value === "object") { + const entries = Object.entries(value); + return explode + ? entries.map(([key, item]) => `${key}=${primitive(item)}`).join(",") + : entries.flatMap(([key, item]) => [key, primitive(item)]).join(","); + } + return primitive(value); +} + +function queryValues( + name: string, + value: JsonValue, + style: string, + explode: boolean, +): Array<[string, string]> { + if (style !== "form") throw new Error(`Unsupported query style: ${style}`); + if (Array.isArray(value)) { + return explode + ? value.map((item) => [name, primitive(item)]) + : [[name, value.map(primitive).join(",")]]; + } + if (value && typeof value === "object") { + const entries = Object.entries(value); + return explode + ? entries.map(([key, item]) => [key, primitive(item)]) + : [[name, entries.flatMap(([key, item]) => [key, primitive(item)]).join(",")]]; + } + return [[name, primitive(value)]]; +} + +function encodePathComponent(value: string): string { + return encodeURIComponent(value); +} + +export function prepareRequest( + config: ApiClientConfig, + operation: ApiOperation, + input: ApiCallInput, +): PreparedRequest { + let pathname = operation.path; + const headers = new Headers(); + const cookies: string[] = []; + const query: Array<[string, string]> = []; + for (const parameter of operation.parameters) { + const source = + parameter.location === "path" + ? input.path + : parameter.location === "query" + ? input.query + : parameter.location === "header" + ? input.headers + : input.cookies; + const value = source[parameter.name]; + if (value === undefined) continue; + if (parameter.location === "path") { + pathname = pathname.replace( + `{${parameter.name}}`, + encodePathComponent(pathValue(value, parameter.style, parameter.explode)), + ); + } else if (parameter.location === "query") { + query.push( + ...queryValues( + parameter.name, + value, + parameter.style, + parameter.explode, + ), + ); + } else if (parameter.location === "header") { + headers.set(parameter.name, primitive(value)); + } else { + cookies.push(`${parameter.name}=${primitive(value)}`); + } + } + if (cookies.length > 0) headers.set("cookie", cookies.join("; ")); + if (operation.auth.required && operation.auth.schemes.includes("BasicAuth")) { + if (!config.publicKey || !config.secretKey) { + throw new Error( + "This operation requires LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY", + ); + } + headers.set( + "authorization", + `Basic ${Buffer.from(`${config.publicKey}:${config.secretKey}`).toString("base64")}`, + ); + } + let body: string | undefined; + if (input.body !== undefined) { + if (!operation.requestBody) { + throw new Error(`${operation.operationId} does not accept a request body`); + } + headers.set("content-type", operation.requestBody.contentType); + body = JSON.stringify(input.body); + } + headers.set("accept", "application/json"); + const host = config.host.endsWith("/") ? config.host : `${config.host}/`; + const url = new URL(pathname.replace(/^\//, ""), host); + for (const [name, value] of query) url.searchParams.append(name, value); + return { + url, + method: operation.method, + headers, + ...(body !== undefined ? { body } : {}), + }; +} + +async function responseBody(response: Response): Promise { + if (response.status === 204 || response.status === 205) return null; + const text = await response.text(); + if (!text) return null; + const contentType = response.headers.get("content-type") ?? ""; + if (contentType.includes("json") || contentType.includes("+json")) { + try { + return JSON.parse(text) as JsonValue; + } catch { + return text; + } + } + return text; +} + +export function createApiClient(config: ApiClientConfig) { + return { + prepare(operation: ApiOperation, input: ApiCallInput): PreparedRequest { + return prepareRequest(config, operation, input); + }, + + async call(operation: ApiOperation, input: ApiCallInput): Promise { + const prepared = prepareRequest(config, operation, input); + const response = await fetch(prepared.url, { + method: prepared.method, + headers: prepared.headers, + body: prepared.body, + signal: AbortSignal.timeout(config.timeoutMs), + }); + return { + status: response.status, + headers: Object.fromEntries(response.headers.entries()), + body: await responseBody(response), + ok: response.ok, + }; + }, + }; +} + +function shellQuote(value: string): string { + if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value; + return `'${value.replaceAll("'", `'"'"'`)}'`; +} + +export function renderCurl( + prepared: PreparedRequest, + options: { showSecrets: boolean }, +): string { + const parts = ["curl", "--request", prepared.method, shellQuote(prepared.url.href)]; + for (const [name, value] of prepared.headers.entries()) { + const rendered = + name.toLowerCase() === "authorization" && !options.showSecrets + ? "Basic " + : value; + parts.push("--header", shellQuote(`${name}: ${rendered}`)); + } + if (prepared.body !== undefined) { + parts.push("--data", shellQuote(prepared.body)); + } + return parts.join(" "); +} diff --git a/src/contracts/compiler.ts b/src/contracts/compiler.ts new file mode 100644 index 0000000..7794ebe --- /dev/null +++ b/src/contracts/compiler.ts @@ -0,0 +1,337 @@ +import { parse } from "yaml"; + +import { planCommandNames } from "../../conformance/src/naming"; +import type { + ApiBodyField, + ApiContract, + ApiOperation, + ApiParameter, + HttpMethod, + ValueKind, +} from "./types"; + +interface ContractSource { + version: string; + ref: string; + sha256: string; +} + +const HTTP_METHODS = [ + "get", + "post", + "put", + "patch", + "delete", + "options", + "head", + "trace", +] as const; + +const LEGACY_FIELD_FLAGS_UNSUPPORTED = new Set([ + "annotationQueues_createQueue", + "datasetItems_create", + "datasetRunItems_create", + "datasets_create", + "ingestion_batch", + "legacy_scoreV1_create", + "models_create", + "opentelemetry_exportTraces", + "promptVersion_update", + "prompts_create", + "scim_createUser", + "score_create", + "trace_deleteMultiple", + "unstable_dashboardWidgets_create", + "unstable_dashboards_addPlacement", + "unstable_dashboards_create", + "unstable_evaluationRules_create", + "unstable_evaluators_create", +]); + +function kebabCase(input: string): string { + return input + .trim() + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/[\s_.:/]+/g, "-") + .replace(/[^a-zA-Z0-9-]/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") + .toLowerCase(); +} + +function resolveLocalRef( + document: Record, + value: Record, +): Record { + let current = value; + const seen = new Set(); + while (current?.$ref) { + const ref = String(current.$ref); + if (!ref.startsWith("#/")) { + throw new Error(`External OpenAPI references are unsupported: ${ref}`); + } + if (seen.has(ref)) throw new Error(`Circular OpenAPI reference: ${ref}`); + seen.add(ref); + current = ref + .slice(2) + .split("/") + .map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")) + .reduce((node, segment) => node?.[segment], document); + if (!current) throw new Error(`Unresolved OpenAPI reference: ${ref}`); + } + return current; +} + +function schemaKind( + document: Record, + rawSchema: Record = {}, +): ValueKind { + const schema = resolveLocalRef(document, rawSchema); + const type = Array.isArray(schema.type) + ? schema.type.find((candidate: string) => candidate !== "null") + : schema.type; + if (type === "integer" || type === "number") return "number"; + if (type === "boolean") return "boolean"; + if (type === "array") return "array"; + if (type === "object" || schema.properties || schema.additionalProperties) { + return "object"; + } + if (type === "null") return "null"; + if (schema.allOf || schema.oneOf || schema.anyOf) { + const branches = schema.allOf ?? schema.oneOf ?? schema.anyOf; + const kinds = new Set( + branches.map((branch: Record) => schemaKind(document, branch)), + ); + return kinds.size === 1 ? [...kinds][0] : "object"; + } + return "string"; +} + +function parameterDefaults(location: string): { style: string; explode: boolean } { + if (location === "query" || location === "cookie") { + return { style: "form", explode: true }; + } + return { style: "simple", explode: false }; +} + +function mergeParameters( + document: Record, + pathParameters: any[] = [], + operationParameters: any[] = [], +): ApiParameter[] { + const merged = new Map(); + for (const raw of [...pathParameters, ...operationParameters]) { + const parameter = resolveLocalRef(document, raw); + const location = parameter.in; + if (!parameter.name || !["path", "query", "header", "cookie"].includes(location)) { + continue; + } + const schema = resolveLocalRef(document, parameter.schema ?? { type: "string" }); + const defaults = parameterDefaults(location); + const kind = schemaKind(document, schema); + merged.set(`${location}:${parameter.name}`, { + location, + name: String(parameter.name), + cliName: kebabCase(String(parameter.name)), + required: location === "path" || Boolean(parameter.required), + style: parameter.style ?? defaults.style, + explode: parameter.explode ?? defaults.explode, + kind, + ...(kind === "array" + ? { itemKind: schemaKind(document, schema.items ?? { type: "string" }) } + : {}), + }); + } + return [...merged.values()].sort((left, right) => { + if (left.location !== right.location) { + return left.location.localeCompare(right.location); + } + return left.name.localeCompare(right.name); + }); +} + +function collectBodyFields( + document: Record, + rawSchema: Record, +): ApiBodyField[] { + const fields = new Map(); + const visit = (raw: Record, inheritedRequired = new Set()) => { + const schema = resolveLocalRef(document, raw); + const required = new Set([ + ...inheritedRequired, + ...(schema.required ?? []), + ]); + for (const branch of [ + ...(schema.allOf ?? []), + ...(schema.oneOf ?? []), + ...(schema.anyOf ?? []), + ]) { + visit(branch, required); + } + for (const [name, rawProperty] of Object.entries>( + schema.properties ?? {}, + )) { + const property = resolveLocalRef(document, rawProperty); + const existing = fields.get(name); + fields.set(name, { + name, + required: Boolean(existing?.required || required.has(name)), + kind: existing?.kind ?? schemaKind(document, property), + ...(property.description + ? { description: String(property.description) } + : existing?.description + ? { description: existing.description } + : {}), + }); + } + }; + visit(rawSchema); + return [...fields.values()].sort((left, right) => + left.name.localeCompare(right.name), + ); +} + +function normalizeAuth( + document: Record, + operation: Record, +): ApiOperation["auth"] { + const requirements = operation.security ?? document.security ?? []; + const schemes = [ + ...new Set( + requirements.flatMap((requirement: Record) => + Object.keys(requirement ?? {}), + ), + ), + ].sort(); + for (const name of schemes) { + const scheme = resolveLocalRef( + document, + document.components?.securitySchemes?.[name] ?? {}, + ); + if (scheme.type !== "http" || scheme.scheme !== "basic") { + throw new Error(`Unsupported authentication scheme: ${name}`); + } + } + return { + required: + requirements.length > 0 && + !requirements.some( + (requirement: Record) => + Object.keys(requirement ?? {}).length === 0, + ), + schemes, + }; +} + +function normalizeRequestBody( + document: Record, + operationId: string, + raw: Record | undefined, +): ApiOperation["requestBody"] { + if (!raw) return undefined; + const body = resolveLocalRef(document, raw); + const contentTypes = Object.keys(body.content ?? {}); + const contentType = + contentTypes.find((candidate) => candidate === "application/json") ?? + contentTypes.find((candidate) => candidate.includes("+json")); + if (!contentType) { + throw new Error( + `${operationId}: only JSON request bodies are supported (${contentTypes.join(", ")})`, + ); + } + const rawSchema = body.content?.[contentType]?.schema; + if (!rawSchema) throw new Error(`${operationId}: request body has no schema`); + return { + required: Boolean(body.required), + contentType, + legacyFieldFlags: !LEGACY_FIELD_FLAGS_UNSUPPORTED.has(operationId), + fields: collectBodyFields(document, rawSchema), + }; +} + +export function compileApiContract( + source: ContractSource, + raw: string, +): ApiContract { + const document = parse(raw, { + maxAliasCount: 100_000, + uniqueKeys: true, + }) as Record; + if (!String(document.openapi ?? "").startsWith("3.0.")) { + throw new Error(`${source.ref}: expected OpenAPI 3.0.x`); + } + const pending: Array<{ + key: string; + operationId: string; + method: HttpMethod; + path: string; + tags: string[]; + auth: ApiOperation["auth"]; + pathParameterOrder: string[]; + parameters: ApiParameter[]; + requestBody?: ApiOperation["requestBody"]; + summary?: string; + description?: string; + }> = []; + for (const [path, rawPathItem] of Object.entries>( + document.paths ?? {}, + )) { + const pathItem = resolveLocalRef(document, rawPathItem); + for (const method of HTTP_METHODS) { + if (!pathItem[method]) continue; + const operation = resolveLocalRef(document, pathItem[method]); + if (operation.callbacks) { + throw new Error(`${method.toUpperCase()} ${path}: callbacks are unsupported`); + } + const operationId = String( + operation.operationId ?? `${method.toUpperCase()}:${path}`, + ); + pending.push({ + key: `${method.toUpperCase()} ${path}`, + operationId, + method: method.toUpperCase() as HttpMethod, + path, + tags: (operation.tags ?? []).map(String), + auth: normalizeAuth(document, operation), + pathParameterOrder: [...path.matchAll(/\{([^}]+)\}/g)].map( + (match) => match[1], + ), + parameters: mergeParameters( + document, + pathItem.parameters, + operation.parameters, + ), + requestBody: normalizeRequestBody( + document, + operationId, + operation.requestBody, + ), + ...(operation.summary ? { summary: String(operation.summary) } : {}), + ...(operation.description + ? { description: String(operation.description) } + : {}), + }); + } + } + pending.sort((left, right) => { + if (left.path !== right.path) return left.path.localeCompare(right.path); + return left.method.localeCompare(right.method); + }); + const names = planCommandNames(pending); + const operations: ApiOperation[] = pending.map( + ({ tags: _tags, ...operation }, index) => ({ + ...operation, + command: names[index], + }), + ); + const operationIds = new Set(operations.map((operation) => operation.operationId)); + if (operationIds.size !== operations.length) { + throw new Error(`${source.ref}: duplicate operationId`); + } + return { + schemaVersion: 1, + apiVersion: source.version, + sourceSha256: source.sha256, + operations, + }; +} diff --git a/src/contracts/loader.ts b/src/contracts/loader.ts new file mode 100644 index 0000000..c08ba7c --- /dev/null +++ b/src/contracts/loader.ts @@ -0,0 +1,101 @@ +import type { + ApiContract, + ApiContractCatalog, + ApiContractCatalogEntry, +} from "./types"; + +const CATALOG_URL = new URL("./contracts/catalog.json", import.meta.url); + +function parseVersion(version: string): [number, number, number] | undefined { + const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version); + if (!match) return undefined; + return [Number(match[1]), Number(match[2]), Number(match[3])]; +} + +function compareVersion(left: string, right: string): number { + const a = parseVersion(left); + const b = parseVersion(right); + if (!a || !b) return left.localeCompare(right); + for (let index = 0; index < 3; index++) { + if (a[index] !== b[index]) return a[index] - b[index]; + } + return 0; +} + +export async function loadContractCatalog(): Promise { + const catalog = (await Bun.file(CATALOG_URL).json()) as ApiContractCatalog; + if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.versions)) { + throw new Error("Invalid bundled API contract catalog"); + } + return catalog; +} + +async function detectServerVersion(host: string, timeoutMs: number): Promise { + const response = await fetch(`${host}/api/public/health`, { + signal: AbortSignal.timeout(timeoutMs), + }); + if (!response.ok) { + throw new Error(`API version detection failed: HTTP ${response.status}`); + } + const body = (await response.json()) as { version?: unknown }; + if (typeof body.version !== "string" || !parseVersion(body.version)) { + throw new Error("API version detection returned no semantic version"); + } + return body.version.replace(/^v/, ""); +} + +function compatibleEntry( + entries: ApiContractCatalogEntry[], + serverVersion: string, +): ApiContractCatalogEntry | undefined { + const target = parseVersion(serverVersion); + if (!target) return undefined; + return [...entries] + .filter((entry) => { + const version = parseVersion(entry.version); + return version?.[0] === target[0] && compareVersion(entry.version, serverVersion) <= 0; + }) + .sort((left, right) => compareVersion(right.version, left.version))[0]; +} + +export async function resolveContractVersion(params: { + requested?: string; + host: string; + timeoutMs: number; + catalog?: ApiContractCatalog; +}): Promise<{ catalog: ApiContractCatalog; version: string; detected?: string }> { + const catalog = params.catalog ?? (await loadContractCatalog()); + const requested = params.requested ?? "latest"; + if (requested === "latest") { + return { catalog, version: catalog.latest }; + } + if (requested === "auto") { + const detected = await detectServerVersion(params.host, params.timeoutMs); + const exact = catalog.versions.find((entry) => entry.version === detected); + const compatible = exact ?? compatibleEntry(catalog.versions, detected); + if (!compatible) { + throw new Error( + `No bundled API contract is compatible with detected server ${detected}`, + ); + } + return { catalog, version: compatible.version, detected }; + } + const exact = catalog.versions.find((entry) => entry.version === requested); + if (!exact) { + throw new Error( + `Unknown API version ${requested}. Available: ${catalog.versions + .map((entry) => entry.version) + .join(", ")}`, + ); + } + return { catalog, version: exact.version }; +} + +export async function loadApiContract(version: string): Promise { + const url = new URL(`./contracts/${encodeURIComponent(version)}.json`, import.meta.url); + const contract = (await Bun.file(url).json()) as ApiContract; + if (contract.schemaVersion !== 1 || contract.apiVersion !== version) { + throw new Error(`Invalid bundled API contract for ${version}`); + } + return contract; +} diff --git a/src/contracts/types.ts b/src/contracts/types.ts new file mode 100644 index 0000000..9f39db7 --- /dev/null +++ b/src/contracts/types.ts @@ -0,0 +1,112 @@ +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = + | JsonPrimitive + | JsonValue[] + | { [key: string]: JsonValue }; + +export type HttpMethod = + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE" + | "OPTIONS" + | "HEAD" + | "TRACE"; + +export type ValueKind = + | "string" + | "number" + | "boolean" + | "array" + | "object" + | "null"; + +export interface CommandName { + resource: string; + action: string; + canonicalAction: string; + aliasOf?: string; +} + +export interface ApiParameter { + location: "path" | "query" | "header" | "cookie"; + name: string; + cliName: string; + required: boolean; + style: string; + explode: boolean; + kind: ValueKind; + itemKind?: ValueKind; +} + +export interface ApiBodyField { + name: string; + required: boolean; + kind: ValueKind; + description?: string; +} + +export interface ApiRequestBody { + required: boolean; + contentType: string; + legacyFieldFlags: boolean; + fields: ApiBodyField[]; +} + +export interface ApiOperation { + key: string; + operationId: string; + method: HttpMethod; + path: string; + auth: { + required: boolean; + schemes: string[]; + }; + command: CommandName; + pathParameterOrder: string[]; + parameters: ApiParameter[]; + requestBody?: ApiRequestBody; + summary?: string; + description?: string; +} + +export interface ApiContract { + schemaVersion: 1; + apiVersion: string; + sourceSha256: string; + operations: ApiOperation[]; +} + +export interface ApiContractCatalogEntry { + version: string; + sourceSha256: string; +} + +export interface ApiContractCatalog { + schemaVersion: 1; + latest: string; + versions: ApiContractCatalogEntry[]; +} + +export interface ApiCallInput { + path: Record; + query: Record; + headers: Record; + cookies: Record; + body?: JsonValue; +} + +export interface ApiClientConfig { + host: string; + publicKey?: string; + secretKey?: string; + timeoutMs: number; +} + +export interface ApiResult { + status: number; + headers: Record; + body: JsonValue | string | null; + ok: boolean; +} From 7e5acab68f956d5225136b92af1abc60b80dd28c Mon Sep 17 00:00:00 2001 From: Nimar Date: Fri, 17 Jul 2026 17:14:44 +0200 Subject: [PATCH 02/11] fix(ci): add TypeScript project config --- tsconfig.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tsconfig.json diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0d2a1ef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Preserve", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "strict": true, + "noEmit": true, + "types": ["bun"], + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts", + "scripts/**/*.ts", + "conformance/src/**/*.ts" + ], + "exclude": ["**/*.test.ts"] +} From 3a7199fcc98730ff55af6410fef004101b4c6a5c Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 21:31:55 +0200 Subject: [PATCH 03/11] fixes --- README.md | 58 +++++++--- conformance/README.md | 26 +++-- conformance/catalog.json | 7 ++ conformance/src/cli.ts | 6 +- conformance/tests/catalog.test.ts | 14 +-- conformance/tests/multi-version.test.ts | 1 + package.json | 1 + src/cli.test.ts | 137 +++++++++++++++++++++++- src/cli.ts | 39 +++++-- src/contracts/compiler.ts | 11 +- src/contracts/types.ts | 1 + 11 files changed, 251 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 691022b..c4f7713 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,13 @@ Interact with the [Langfuse](https://langfuse.com) API from the command line. ```sh # Run directly npx langfuse-cli api +# or bunx langfuse-cli api # Or install globally npm i -g langfuse-cli +# or +bun add --global langfuse-cli langfuse api ``` @@ -47,11 +50,14 @@ langfuse --public-key pk-lf-... --secret-key sk-lf-... api prompts list ## Usage ```sh -# Discover all resources -langfuse api __schema +# Discover resources naturally +langfuse api help +langfuse api help prompts +langfuse api help prompts create -# List actions for a resource -langfuse api traces --help +# Machine-readable discovery (legacy command alias) +langfuse api schema --json +langfuse api __schema --json # List traces langfuse api traces list --limit 10 @@ -67,7 +73,8 @@ langfuse api traces list --limit 5 --curl # Prompts langfuse api prompts list -langfuse api prompts get --name my-prompt +langfuse api prompts get my-prompt +langfuse api prompts create --body-json '{"name":"my-prompt","type":"text","prompt":"Hello {{name}}"}' # Datasets langfuse api datasets list @@ -75,6 +82,12 @@ langfuse api dataset-items list --dataset-name my-dataset # Scores langfuse api score-v2s get-scores --limit 20 + +# Use an API snapshot compatible with an older self-hosted deployment +langfuse --api-version 3.150.0 api traces list + +# Detect the server version through /api/public/health +langfuse --api-version auto api traces list ``` ## Agent Usage @@ -91,30 +104,43 @@ This fetches the latest skill from GitHub, so it stays up to date. Pipe it into See the full [Langfuse API Reference](https://api.reference.langfuse.com/). -## OpenAPI Patch Script +## OpenAPI conformance suite + +The version-pinned black-box suite lives in [`conformance/`](conformance/README.md). It makes one minimally valid mocked API call per operation through the real CLI across historical Langfuse specs. -The bundled `openapi.yml` is post-processed by `scripts/patch-openapi.ts` to flatten discriminated unions (`oneOf` with `allOf` branches) into plain objects. This is needed because specli can only generate CLI flags from flat `type: object` schemas — it doesn't handle `oneOf`/`allOf`. Without the patch, endpoints like `prompts create` produce zero flags. +```sh +bun test +bun run conformance:all +``` -The patch runs automatically as part of `bun run build`. To fetch a fresh spec and patch it: +`bun test` verifies the generator, schemas, serialization, capture oracle, and legacy CLI compatibility. `bun run conformance:all` builds the package and fake-calls every operation through the native CLI using its lossless JSON input path. CI runs both. + +## Native OpenAPI contracts + +The CLI is implemented in TypeScript and runs natively on Bun. It has zero external runtime dependencies and never parses OpenAPI during invocation. + +Builds compile the committed, byte-for-byte upstream OpenAPI snapshots into compact versioned contracts under ignored `dist/contracts/`. Generated contracts are packaged on npm but are not committed. ```sh -# From cloud (default) -bun run refetch-openapi +# Build the Bun CLI and all versioned contracts +bun run build -# From a custom URL (e.g. local dev server) -bun run patch-openapi -- --refetch --openapi_url http://localhost:3000/generated/api/openapi.yml +# Add or refresh an immutable upstream snapshot +bun run conformance:sync -- --version -# Patch only (no fetch) -bun run patch-openapi +# Add a stable release snapshot, update metadata, and verify it +bun run conformance:add-version -- v4.11.0 ``` +`--body-json` and `--body-file` provide a lossless input path for nested objects, arrays, unions, and free-form JSON. Simple historical field flags remain supported where they were previously expressible. + ## Release ```sh bun run release ``` -This interactively selects the package version, verifies it is not already on npm, checks npm auth/registry, runs tests, rebuilds the CLI via `prepublishOnly`, checks the npm package contents with `npm pack --dry-run`, shows the post-build git status, then asks before publishing to npm. +This interactively selects the package version, verifies it is not already on npm, checks npm auth/registry, typechecks, runs both test suites, rebuilds the CLI, checks the npm package contents with `npm pack --dry-run`, shows the post-build git status, then asks before publishing to npm. To test the flow without publishing: @@ -122,7 +148,7 @@ To test the flow without publishing: bun run release -- --dry-run ``` -Dry-run still runs the full rebuild path, so it may update generated release artifacts like `dist/` and `openapi.yml`; it restores the package version before exiting. +Dry-run still runs the full reproducible build path and restores the package version before exiting. Generated `dist/` artifacts remain ignored. If you are testing local changes to the release script itself, add `--allow-dirty`. Do not use `--allow-dirty` for a real publish. diff --git a/conformance/README.md b/conformance/README.md index e0e524b..5fdb09c 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -12,7 +12,7 @@ The primary test fake-calls every operation in every cataloged spec through the - compares the received method, path, query, headers, authentication, and JSON body - compares the CLI's response status, body, and exit status -The black-box oracle does not share request-building code with the CLI. `bun test` currently attempts all 678 operations across all eight v3 snapshots using the historical field-flag adapter. The 18 operations that require lossless JSON bodies remain an explicit compatibility baseline; any additional failure fails the test. The native `contract-v1` adapter passes all 678 operations through `--body-json`. +The black-box oracle does not share request-building code with the CLI. `bun test` currently attempts all 792 operations across 9 pinned snapshots using the historical field-flag adapter. Operations that require lossless JSON bodies remain an explicit compatibility baseline; any additional failure fails the test. The native `contract-v1` adapter passes all 792 operations through `--body-json`. Supporting unit tests verify immutable spec hashes, valid sampling, serialization, naming, adapters, and the capture runner itself. @@ -30,10 +30,11 @@ OpenAPI cannot describe database setup, generated IDs, cross-request bindings, c | 3.200.0 | 61 | 98 | | 3.212.0 | 64 | 101 | | 3.216.0 | 69 | 113 | +| 4.10.0 | 70 | 114 | Each source file is downloaded by immutable commit and verified against the SHA-256 in `catalog.json`. Tests are network-free after sync. -Langfuse 3.200.0, 3.212.0, and 3.216.0 use the JSON Schema `const` keyword while declaring OpenAPI 3.0.1. `swagger-parser` correctly reports those sources as invalid OAS 3.0 documents. The catalog records this as `oas3.0-const-keyword`; request sampling still preserves and tests the constraint with the independent JSON Schema validator. +Some pinned specs use the JSON Schema `const` keyword while declaring OpenAPI 3.0.1. `swagger-parser` correctly reports those sources as invalid OAS 3.0 documents. The catalog records this as `oas3.0-const-keyword`; request sampling still preserves and tests the constraint with the independent JSON Schema validator. ## Files @@ -96,18 +97,25 @@ The native adapter uses lossless JSON body input via `--body-json`: ```sh bun run conformance:run -- \ - --version 3.216.0 \ + --version 4.10.0 \ --adapter contract-v1 \ - -- bun bin/langfuse.mjs --api-version 3.216.0 + -- bun bin/langfuse.mjs --api-version 4.10.0 ``` The command after the second `--` is treated as an external black-box executable. ## Add a version -1. Resolve the release tag to its immutable commit. -2. Add the tag, commit, and SHA-256 to `catalog.json`. -3. Run `bun run conformance:sync -- --version `. -4. Run `bun test` and `bun run conformance:all`. +```sh +bun run conformance:add-version -- v4.11.0 +``` + +The command accepts only stable semantic release tags. It verifies the published GitHub release, resolves the tag to an immutable commit, downloads the exact OpenAPI bytes, records their SHA-256, checks both compilers, updates the catalog and this table, then runs typecheck, tests, build, and focused black-box conformance. If validation fails, it restores the catalog, spec directory, and documentation. + +Preview without writing files: + +```sh +bun run conformance:add-version -- v4.11.0 --dry-run +``` -Never point a committed catalog entry at mutable `main` or `latest`. +Review the resulting source diff and live-test added or changed endpoints before committing. Never catalog mutable `main` or `latest`. diff --git a/conformance/catalog.json b/conformance/catalog.json index 0c48b77..839ff59 100644 --- a/conformance/catalog.json +++ b/conformance/catalog.json @@ -53,6 +53,13 @@ "commit": "706f1bb4231ba4433c8fc101b85167471693180a", "sha256": "551e824dd11fc137557d7092596eba09ae342698c7ce63ceb86d90909afd7ee0", "knownIssues": ["oas3.0-const-keyword"] + }, + { + "version": "4.10.0", + "ref": "v4.10.0", + "commit": "ddc198b49f1439f95ecf674c68863bfb0c2bd07f", + "sha256": "fa4d21a83c49bd0bc3d3f2f751e12fbfce96f72b422267726606ff67bfacf01f", + "knownIssues": ["oas3.0-const-keyword"] } ] } diff --git a/conformance/src/cli.ts b/conformance/src/cli.ts index 52dfa8e..fa7d766 100644 --- a/conformance/src/cli.ts +++ b/conformance/src/cli.ts @@ -17,9 +17,9 @@ function option(args: string[], name: string): string | undefined { function usage(): never { process.stderr.write(`Usage: - bun run conformance:sync [--version 3.216.0] - bun run conformance:run --version 3.216.0 --adapter specli-v0 --current-cli [filters] - bun run conformance:run --version 3.216.0 --adapter contract-v1 [filters] -- + bun run conformance:sync [--version 4.10.0] + bun run conformance:run --version 4.10.0 --adapter specli-v0 --current-cli [filters] + bun run conformance:run --version 4.10.0 --adapter contract-v1 [filters] -- Run filters: --operation Restrict one operation diff --git a/conformance/tests/catalog.test.ts b/conformance/tests/catalog.test.ts index 2a14229..c2bd79e 100644 --- a/conformance/tests/catalog.test.ts +++ b/conformance/tests/catalog.test.ts @@ -3,20 +3,14 @@ import SwaggerParser from "@apidevtools/swagger-parser"; import { loadCatalog, readVerifiedSpec, specPath } from "../src/catalog"; import { generateCorpus } from "../src/generator"; +import { compareVersions } from "../src/add-version"; describe("immutable OpenAPI catalog", () => { test("all pinned snapshots pass SHA-256 verification", async () => { const catalog = await loadCatalog(); - expect(catalog.versions.map((entry) => entry.version)).toEqual([ - "3.0.0", - "3.50.0", - "3.100.0", - "3.150.0", - "3.176.0", - "3.200.0", - "3.212.0", - "3.216.0", - ]); + const versions = catalog.versions.map((entry) => entry.version); + expect(new Set(versions).size).toBe(versions.length); + expect(versions).toEqual([...versions].sort(compareVersions)); for (const entry of catalog.versions) { const raw = await readVerifiedSpec(entry); expect(raw.startsWith("openapi: 3.0.")).toBe(true); diff --git a/conformance/tests/multi-version.test.ts b/conformance/tests/multi-version.test.ts index 7f4d6c2..9a93544 100644 --- a/conformance/tests/multi-version.test.ts +++ b/conformance/tests/multi-version.test.ts @@ -17,6 +17,7 @@ const CURRENT_CLI_UNSUPPORTED_OPERATIONS = new Set([ "prompts_create", "scim_createUser", "score_create", + "scores_create", "trace_deleteMultiple", "unstable_dashboardWidgets_create", "unstable_dashboards_addPlacement", diff --git a/package.json b/package.json index e7d161b..cea6147 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "test": "bun test", "typecheck": "tsc --noEmit", "conformance:sync": "bun conformance/src/cli.ts sync", + "conformance:add-version": "bun conformance/src/add-version.ts", "conformance:run": "bun conformance/src/cli.ts run", "conformance:all": "bun conformance/src/all.ts", "build": "bun scripts/build.ts", diff --git a/src/cli.test.ts b/src/cli.test.ts index 0adde4c..1a954b8 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -1,6 +1,11 @@ import { describe, expect, test } from "bun:test"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; -import { run } from "./cli"; +import { parseOperationInput, run, writeResult } from "./cli"; +import { compileApiContract } from "./contracts/compiler"; +import type { ApiOperation } from "./contracts/types"; async function captureOutput( fn: () => Promise, @@ -66,3 +71,133 @@ describe("langfuse get-skill", () => { } }); }); + +describe("operation input parsing", () => { + const promptGet: ApiOperation = { + key: "GET /api/public/v2/prompts/{promptName}", + operationId: "prompts_get", + method: "GET", + path: "/api/public/v2/prompts/{promptName}", + auth: { required: false, schemes: [] }, + command: { + resource: "prompts", + action: "get", + canonicalAction: "get", + }, + pathParameterOrder: ["promptName"], + parameters: [ + { + location: "path", + name: "promptName", + cliName: "prompt-name", + required: true, + style: "simple", + explode: false, + kind: "string", + }, + { + location: "query", + name: "resolve", + cliName: "resolve", + required: false, + style: "form", + explode: true, + kind: "boolean", + }, + ], + }; + + test("does not consume a positional after a bare boolean flag", async () => { + const input = await parseOperationInput(promptGet, [ + "--resolve", + "my-prompt-name", + ]); + + expect(input.path.promptName).toBe("my-prompt-name"); + expect(input.query.resolve).toBe(true); + }); + + test("does not consume a positional after a negated boolean flag", async () => { + const input = await parseOperationInput(promptGet, [ + "--no-resolve", + "my-prompt-name", + ]); + + expect(input.path.promptName).toBe("my-prompt-name"); + expect(input.query.resolve).toBe(false); + }); + + test("accepts an explicit inline boolean before a positional", async () => { + const input = await parseOperationInput(promptGet, [ + "--resolve=false", + "my-prompt-name", + ]); + + expect(input.path.promptName).toBe("my-prompt-name"); + expect(input.query.resolve).toBe(false); + }); + + test("preserves the item type of array request-body flags", async () => { + const contract = compileApiContract( + { version: "test", ref: "test", sha256: "test" }, + `openapi: 3.0.3 +info: + title: Test + version: test +paths: + /models: + put: + operationId: models_put + tags: [Models] + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + customModels: + type: array + items: + type: string + responses: + "200": + description: OK +`, + ); + const operation = contract.operations[0]; + const field = operation.requestBody?.fields.find( + (candidate) => candidate.name === "customModels", + ); + + expect(field?.itemKind).toBe("string"); + expect( + await parseOperationInput(operation, ["--customModels", "123"]), + ).toMatchObject({ body: { customModels: ["123"] } }); + }); +}); + +describe("result output", () => { + test("writes an empty file for an empty response body", async () => { + const directory = await mkdtemp(join(tmpdir(), "langfuse-cli-test-")); + const output = join(directory, "response.json"); + try { + await writeResult( + { status: 204, headers: {}, body: null, ok: true }, + { + host: "http://localhost", + timeoutMs: 1_000, + json: false, + curl: false, + showSecrets: false, + output, + }, + ); + + expect(await Bun.file(output).text()).toBe(""); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); +}); diff --git a/src/cli.ts b/src/cli.ts index ce3237a..0f4da49 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -255,6 +255,12 @@ function kindLabel(kind: ValueKind): string { return kind === "array" ? "value (repeatable)" : kind; } +function flagUsage(name: string, kind: ValueKind): string { + return kind === "boolean" + ? `--${name}[=true|false] / --no-${name}` + : `--${name} <${kindLabel(kind)}>`; +} + function printOperationHelp(operation: ApiOperation): void { const positionals = operation.pathParameterOrder .map((name) => `<${name}>`) @@ -263,13 +269,13 @@ function printOperationHelp(operation: ApiOperation): void { for (const parameter of operation.parameters) { if (parameter.location === "path") continue; lines.push( - ` --${parameter.cliName} <${kindLabel(parameter.kind)}>${parameter.required ? " (required)" : ""}`, + ` ${flagUsage(parameter.cliName, parameter.kind)}${parameter.required ? " (required)" : ""}`, ); } if (operation.requestBody?.legacyFieldFlags) { for (const field of operation.requestBody.fields) { lines.push( - ` --${field.name} <${kindLabel(field.kind)}>${field.required ? " (required)" : ""}`, + ` ${flagUsage(field.name, field.kind)}${field.required ? " (required)" : ""}`, ); } } @@ -383,7 +389,12 @@ function setBodyValue( target = target[segment] as Record; } const name = path.at(-1)!; - const kind = path.length > 1 || field?.kind === "array" ? undefined : field?.kind; + const kind = + path.length > 1 + ? undefined + : field?.kind === "array" + ? field.itemKind + : field?.kind; const parsed = parseJsonValue(raw ?? "true", kind); const existing = target[name]; if (field?.kind === "array") { @@ -418,7 +429,7 @@ async function readBodyFile(path: string): Promise { } } -async function parseOperationInput( +export async function parseOperationInput( operation: ApiOperation, tokens: string[], ): Promise { @@ -450,9 +461,18 @@ async function parseOperationInput( continue; } const option = splitOption(token); + const parameter = parameterByFlag.get(option.name); + const bodyField = operation.requestBody?.legacyFieldFlags + ? operation.requestBody.fields.find( + (candidate) => candidate.name === option.name.split(".")[0], + ) + : undefined; + const isBoolean = + parameter?.kind === "boolean" || bodyField?.kind === "boolean"; let raw = option.inline; if ( raw === undefined && + !isBoolean && tokens[index + 1] !== undefined && !tokens[index + 1].startsWith("--") ) { @@ -474,7 +494,6 @@ async function parseOperationInput( completeBody = await readBodyFile(raw); continue; } - const parameter = parameterByFlag.get(option.name); if (parameter) { if (option.negated && parameter.kind !== "boolean") { throw new CliError(`--no-${option.name} is only valid for boolean options`); @@ -491,9 +510,7 @@ async function parseOperationInput( ); } const path = option.name.split(".").filter(Boolean); - const field = operation.requestBody.fields.find( - (candidate) => candidate.name === path[0], - ); + const field = bodyField; if (!field) throw new CliError(`Unknown option --${option.name}`); if (option.negated && field.kind !== "boolean") { throw new CliError(`--no-${option.name} is only valid for boolean options`); @@ -581,13 +598,15 @@ function schemaOutput(contract: ApiContract) { }; } -async function writeResult( +export async function writeResult( result: ApiResult, config: RuntimeConfig, ): Promise { if (config.output) { const content = - typeof result.body === "string" + result.body === null + ? "" + : typeof result.body === "string" ? result.body : JSON.stringify(result.body, null, 2); await Bun.write(config.output, content ?? ""); diff --git a/src/contracts/compiler.ts b/src/contracts/compiler.ts index 7794ebe..543d2f2 100644 --- a/src/contracts/compiler.ts +++ b/src/contracts/compiler.ts @@ -40,6 +40,7 @@ const LEGACY_FIELD_FLAGS_UNSUPPORTED = new Set([ "prompts_create", "scim_createUser", "score_create", + "scores_create", "trace_deleteMultiple", "unstable_dashboardWidgets_create", "unstable_dashboards_addPlacement", @@ -173,10 +174,18 @@ function collectBodyFields( )) { const property = resolveLocalRef(document, rawProperty); const existing = fields.get(name); + const kind = existing?.kind ?? schemaKind(document, property); fields.set(name, { name, required: Boolean(existing?.required || required.has(name)), - kind: existing?.kind ?? schemaKind(document, property), + kind, + ...(kind === "array" + ? { + itemKind: + existing?.itemKind ?? + schemaKind(document, property.items ?? { type: "string" }), + } + : {}), ...(property.description ? { description: String(property.description) } : existing?.description diff --git a/src/contracts/types.ts b/src/contracts/types.ts index 9f39db7..e41fba9 100644 --- a/src/contracts/types.ts +++ b/src/contracts/types.ts @@ -44,6 +44,7 @@ export interface ApiBodyField { name: string; required: boolean; kind: ValueKind; + itemKind?: ValueKind; description?: string; } From 04ee9316872b702837d4320730def2ea7bc7b95a Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 21:33:03 +0200 Subject: [PATCH 04/11] add v4 --- conformance/specs/4.10.0/openapi.yml | 16768 ++++++++++++++++++++++++ conformance/src/add-version.ts | 328 + conformance/tests/add-version.test.ts | 79 + 3 files changed, 17175 insertions(+) create mode 100644 conformance/specs/4.10.0/openapi.yml create mode 100644 conformance/src/add-version.ts create mode 100644 conformance/tests/add-version.test.ts diff --git a/conformance/specs/4.10.0/openapi.yml b/conformance/specs/4.10.0/openapi.yml new file mode 100644 index 0000000..347afcb --- /dev/null +++ b/conformance/specs/4.10.0/openapi.yml @@ -0,0 +1,16768 @@ +openapi: 3.0.1 +info: + title: server + version: '' + description: >- + ## Authentication + + + Authenticate with the API using [Basic + Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API + keys in the project settings: + + + - username: Langfuse Public Key + + - password: Langfuse Secret Key + + + ## Exports + + + - OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml +paths: + /api/public/annotation-queues: + get: + description: Get all annotation queues + operationId: annotationQueues_listQueues + tags: + - AnnotationQueues + parameters: + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAnnotationQueues' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: &ref_0 + - BasicAuth: [] + post: + description: Create an annotation queue + operationId: annotationQueues_createQueue + tags: + - AnnotationQueues + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueue' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateAnnotationQueueRequest' + /api/public/annotation-queues/{queueId}: + get: + description: Get an annotation queue by ID + operationId: annotationQueues_getQueue + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueue' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/annotation-queues/{queueId}/items: + get: + description: Get items for a specific annotation queue + operationId: annotationQueues_listQueueItems + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + - name: status + in: query + description: Filter by status + required: false + schema: + $ref: '#/components/schemas/AnnotationQueueStatus' + nullable: true + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAnnotationQueueItems' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + post: + description: Add an item to an annotation queue + operationId: annotationQueues_createQueueItem + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueueItem' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateAnnotationQueueItemRequest' + /api/public/annotation-queues/{queueId}/items/{itemId}: + get: + description: Get a specific item from an annotation queue + operationId: annotationQueues_getQueueItem + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + - name: itemId + in: path + description: The unique identifier of the annotation queue item + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueueItem' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + patch: + description: Update an annotation queue item + operationId: annotationQueues_updateQueueItem + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + - name: itemId + in: path + description: The unique identifier of the annotation queue item + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueueItem' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateAnnotationQueueItemRequest' + delete: + description: Remove an item from an annotation queue + operationId: annotationQueues_deleteQueueItem + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + - name: itemId + in: path + description: The unique identifier of the annotation queue item + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteAnnotationQueueItemResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/annotation-queues/{queueId}/assignments: + post: + description: Create an assignment for a user to an annotation queue + operationId: annotationQueues_createQueueAssignment + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CreateAnnotationQueueAssignmentResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueueAssignmentRequest' + delete: + description: Delete an assignment for a user to an annotation queue + operationId: annotationQueues_deleteQueueAssignment + tags: + - AnnotationQueues + parameters: + - name: queueId + in: path + description: The unique identifier of the annotation queue + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteAnnotationQueueAssignmentResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AnnotationQueueAssignmentRequest' + /api/public/integrations/blob-storage: + get: + description: >- + Get all blob storage integrations for the organization (requires + organization-scoped API key) + operationId: blobStorageIntegrations_getBlobStorageIntegrations + tags: + - BlobStorageIntegrations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/BlobStorageIntegrationsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + put: + description: >- + Create or update a blob storage integration for a specific project + (requires organization-scoped API key). The configuration is validated + by performing a test upload to the bucket. + operationId: blobStorageIntegrations_upsertBlobStorageIntegration + tags: + - BlobStorageIntegrations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/BlobStorageIntegrationResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBlobStorageIntegrationRequest' + /api/public/integrations/blob-storage/{id}: + get: + description: >- + Get the sync status of a blob storage integration by integration ID + (requires organization-scoped API key) + operationId: blobStorageIntegrations_getBlobStorageIntegrationStatus + tags: + - BlobStorageIntegrations + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/BlobStorageIntegrationStatusResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: >- + Delete a blob storage integration by ID (requires organization-scoped + API key) + operationId: blobStorageIntegrations_deleteBlobStorageIntegration + tags: + - BlobStorageIntegrations + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/BlobStorageIntegrationDeletionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/comments: + post: + description: >- + Create a comment. Comments may be attached to different object types + (trace, observation, session, prompt). + operationId: comments_create + tags: + - Comments + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCommentResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCommentRequest' + get: + description: Get all comments + operationId: comments_get + tags: + - Comments + parameters: + - name: page + in: query + description: Page number, starts at 1. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: >- + Limit of items per page. If you encounter api issues due to too + large page sizes, try to reduce the limit + required: false + schema: + type: integer + nullable: true + - name: objectType + in: query + description: >- + Filter comments by object type (trace, observation, session, + prompt). + required: false + schema: + type: string + nullable: true + - name: objectId + in: query + description: >- + Filter comments by object id. If objectType is not provided, an + error will be thrown. + required: false + schema: + type: string + nullable: true + - name: authorUserId + in: query + description: Filter comments by author user id. + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/GetCommentsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/comments/{commentId}: + get: + description: Get a comment by id + operationId: comments_get-by-id + tags: + - Comments + parameters: + - name: commentId + in: path + description: The unique langfuse identifier of a comment + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/dataset-items: + post: + description: Create a dataset item + operationId: datasetItems_create + tags: + - DatasetItems + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetItem' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDatasetItemRequest' + get: + description: >- + Get dataset items. Optionally specify a version to get the items as they + existed at that point in time. + + Note: If version parameter is provided, datasetName must also be + provided. + operationId: datasetItems_list + tags: + - DatasetItems + parameters: + - name: datasetName + in: query + required: false + schema: + type: string + nullable: true + - name: sourceTraceId + in: query + required: false + schema: + type: string + nullable: true + - name: sourceObservationId + in: query + required: false + schema: + type: string + nullable: true + - name: version + in: query + description: >- + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., + "2026-01-21T14:35:42Z"). + + If provided, returns state of dataset at this timestamp. + + If not provided, returns the latest version. Requires datasetName to + be specified. + required: false + schema: + type: string + format: date-time + nullable: true + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDatasetItems' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/dataset-items/{id}: + get: + description: Get a dataset item + operationId: datasetItems_get + tags: + - DatasetItems + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetItem' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: >- + Delete a dataset item and all its run items. This action is + irreversible. + operationId: datasetItems_delete + tags: + - DatasetItems + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteDatasetItemResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/dataset-run-items: + post: + description: Create a dataset run item + operationId: datasetRunItems_create + tags: + - DatasetRunItems + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetRunItem' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDatasetRunItemRequest' + get: + description: List dataset run items + operationId: datasetRunItems_list + tags: + - DatasetRunItems + parameters: + - name: datasetId + in: query + required: true + schema: + type: string + - name: runName + in: query + required: true + schema: + type: string + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDatasetRunItems' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/v2/datasets: + get: + description: Get all datasets + operationId: datasets_list + tags: + - Datasets + parameters: + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDatasets' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + post: + description: Create a dataset + operationId: datasets_create + tags: + - Datasets + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDatasetRequest' + /api/public/v2/datasets/{datasetName}: + get: + description: Get a dataset + operationId: datasets_get + tags: + - Datasets + parameters: + - name: datasetName + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/datasets/{datasetName}/runs/{runName}: + get: + description: Get a dataset run and its items + operationId: datasets_getRun + tags: + - Datasets + parameters: + - name: datasetName + in: path + required: true + schema: + type: string + - name: runName + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetRunWithItems' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: Delete a dataset run and all its run items. This action is irreversible. + operationId: datasets_deleteRun + tags: + - Datasets + parameters: + - name: datasetName + in: path + required: true + schema: + type: string + - name: runName + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteDatasetRunResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/datasets/{datasetName}/runs: + get: + description: Get dataset runs + operationId: datasets_getRuns + tags: + - Datasets + parameters: + - name: datasetName + in: path + required: true + schema: + type: string + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDatasetRuns' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/experiments: + get: + description: |- + List experiments with cursor-based pagination. Results are ordered by + latest experiment activity descending. + operationId: experiments_list + tags: + - Experiments + parameters: + - name: fields + in: query + description: |- + Comma-separated list of field groups to include. Available groups: + `core`, `metadata`, `scores`. If omitted, `core` is returned. + required: false + schema: + type: string + nullable: true + - name: limit + in: query + description: Number of experiments to return per page. Maximum 100, default 50. + required: false + schema: + type: integer + nullable: true + - name: scoreLimit + in: query + description: >- + Number of scores to return per experiment when `fields=scores` is + requested. Maximum 50, default 50. + required: false + schema: + type: integer + nullable: true + - name: cursor + in: query + description: Versioned base64url cursor from the previous response page. + required: false + schema: + type: string + nullable: true + - name: fromStartTime + in: query + description: Retrieve only experiments on or after this datetime. + required: true + schema: + type: string + format: date-time + - name: toStartTime + in: query + description: Retrieve only experiments before this datetime. + required: false + schema: + type: string + format: date-time + nullable: true + - name: id + in: query + description: Comma-separated list of experiment IDs. + required: false + schema: + type: string + nullable: true + - name: name + in: query + description: Comma-separated list of experiment names. + required: false + schema: + type: string + nullable: true + - name: datasetId + in: query + description: Comma-separated list of dataset IDs. + required: false + schema: + type: string + nullable: true + - name: filter + in: query + description: |- + JSON string containing an array of structured filter conditions. + Supported columns are `id`, `name`, and `datasetId`. + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ExperimentsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/experiment-items: + get: + description: |- + List experiment items with cursor-based pagination. Use this endpoint + to export experiment item inputs, outputs, expected outputs, metadata, + and optionally item/trace scores. Results are ordered by time + descending. + operationId: experiments_listItems + tags: + - Experiments + parameters: + - name: fields + in: query + description: |- + Comma-separated list of field groups to include. Available groups: + `core`, `dataset`, `io`, `metadata`, `itemMetadata`, + `experimentMetadata`, `scores`. If omitted, `core,dataset` is + returned. + required: false + schema: + type: string + nullable: true + - name: limit + in: query + description: >- + Number of experiment items to return per page. Maximum 100, default + 50. + required: false + schema: + type: integer + nullable: true + - name: scoreLimit + in: query + description: >- + Number of scores to return per experiment item when `fields=scores` + is requested. Maximum 50, default 50. + required: false + schema: + type: integer + nullable: true + - name: cursor + in: query + description: Versioned base64url cursor from the previous response page. + required: false + schema: + type: string + nullable: true + - name: fromStartTime + in: query + description: Retrieve only experiment items started on or after this datetime. + required: true + schema: + type: string + format: date-time + - name: toStartTime + in: query + description: Retrieve only experiment items started before this datetime. + required: false + schema: + type: string + format: date-time + nullable: true + - name: experimentId + in: query + description: Comma-separated list of experiment IDs. + required: false + schema: + type: string + nullable: true + - name: experimentName + in: query + description: Comma-separated list of experiment names. + required: false + schema: + type: string + nullable: true + - name: experimentItemId + in: query + description: Comma-separated list of experiment item IDs. + required: false + schema: + type: string + nullable: true + - name: datasetId + in: query + description: Comma-separated list of dataset IDs. + required: false + schema: + type: string + nullable: true + - name: filter + in: query + description: |- + JSON string containing an array of structured filter conditions. + Supported columns are `experimentId`, `experimentName`, + `experimentItemId`, and `datasetId`. + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ExperimentItemsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/feedback: + post: + description: >- + Submit explicit user-approved feedback about Langfuse skills, MCP tools, + CLI, docs, or public API. Do not include secrets, credentials, customer + data, trace payloads, or unrelated use-case details. + operationId: feedback_submit + tags: + - Feedback + parameters: [] + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/SubmitFeedbackResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '409': + description: '' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SubmitFeedbackRequest' + /api/public/health: + get: + description: Check health of API and database + operationId: health_health + tags: + - Health + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/HealthResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '503': + description: '' + /api/public/ingestion: + post: + description: >- + **Legacy endpoint for batch ingestion for Langfuse Observability.** + + + -> Please use the OpenTelemetry endpoint (`/api/public/otel/v1/traces`). + Learn more: https://langfuse.com/integrations/native/opentelemetry + + + Within each batch, there can be multiple events. + + Each event has a type, an id, a timestamp, metadata and a body. + + Internally, we refer to this as the "event envelope" as it tells us + something about the event but not the trace. + + We use the event id within this envelope to deduplicate messages to + avoid processing the same event twice, i.e. the event id should be + unique per request. + + The event.body.id is the ID of the actual trace and will be used for + updates and will be visible within the Langfuse App. + + I.e. if you want to update a trace, you'd use the same body id, but + separate event IDs. + + + Notes: + + - Introduction to data model: + https://langfuse.com/docs/observability/data-model + + - Batch sizes are limited to 3.5 MB in total. You need to adjust the + number of events per batch accordingly. + + - The API does not return a 4xx status code for input errors. Instead, + it responds with a 207 status code, which includes a list of the + encountered errors. + operationId: ingestion_batch + tags: + - Ingestion + parameters: [] + responses: + '207': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/IngestionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + batch: + type: array + items: + $ref: '#/components/schemas/IngestionEvent' + description: >- + Batch of tracing events to be ingested. Discriminated by + attribute `type`. + metadata: + nullable: true + description: >- + Optional. Metadata field used by the Langfuse SDKs for + debugging. + required: + - batch + /api/public/metrics: + get: + description: >- + Get metrics from the Langfuse project using a query object. + + + Consider using the [v2 metrics + endpoint](/api-reference#tag/metricsv2/GET/api/public/v2/metrics) for + better performance. + + + For more details, see the [Metrics API + documentation](https://langfuse.com/docs/metrics/features/metrics-api). + operationId: legacy_metricsV1_metrics + tags: + - LegacyMetricsV1 + parameters: + - name: query + in: query + description: >- + JSON string containing the query parameters with the following + structure: + + ```json + + { + "view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-boolean", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by, e.g. "name", "userId", "sessionId" + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure, e.g. "count", "latency", "value" + "aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on + "operator": string, // Operator, e.g. "=", ">", "<", "contains" + "value": any, // Value to compare against + "type": string, // Data type, e.g. "string", "number", "stringObject" + "key": string // Required only when filtering on metadata + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "minute", "hour", "day", "week", "month", "auto" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram (1-100), default: 10 + "row_limit": number // Optional. Row limit for results (1-1000) + } + } + + ``` + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/legacyMetricsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/observations/{observationId}: + get: + description: Get a observation + operationId: legacy_observationsV1_get + tags: + - LegacyObservationsV1 + parameters: + - name: observationId + in: path + description: >- + The unique langfuse identifier of an observation, can be an event, + span or generation + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ObservationsViewSingle' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/observations: + get: + description: >- + Get a list of observations. + + + Consider using the [v2 observations + endpoint](/api-reference#tag/observationsv2/GET/api/public/v2/observations) + for cursor-based pagination and field selection. + operationId: legacy_observationsV1_getMany + tags: + - LegacyObservationsV1 + parameters: + - name: page + in: query + description: Page number, starts at 1. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: >- + Limit of items per page. If you encounter api issues due to too + large page sizes, try to reduce the limit. + required: false + schema: + type: integer + nullable: true + - name: name + in: query + required: false + schema: + type: string + nullable: true + - name: userId + in: query + required: false + schema: + type: string + nullable: true + - name: type + in: query + required: false + schema: + type: string + nullable: true + - name: traceId + in: query + required: false + schema: + type: string + nullable: true + - name: level + in: query + description: >- + Optional filter for observations with a specific level (e.g. + "DEBUG", "DEFAULT", "WARNING", "ERROR"). + required: false + schema: + $ref: '#/components/schemas/ObservationLevel' + nullable: true + - name: parentObservationId + in: query + required: false + schema: + type: string + nullable: true + - name: environment + in: query + description: >- + Optional filter for observations where the environment is one of the + provided values. + required: false + schema: + type: array + items: + type: string + nullable: true + - name: fromStartTime + in: query + description: >- + Retrieve only observations with a start_time on or after this + datetime (ISO 8601). + required: false + schema: + type: string + format: date-time + nullable: true + - name: toStartTime + in: query + description: >- + Retrieve only observations with a start_time before this datetime + (ISO 8601). + required: false + schema: + type: string + format: date-time + nullable: true + - name: version + in: query + description: Optional filter to only include observations with a certain version. + required: false + schema: + type: string + nullable: true + - name: filter + in: query + description: >- + JSON string containing an array of filter conditions. When provided, + this takes precedence over query parameter filters (userId, name, + type, level, environment, fromStartTime, ...). + + + ## Filter Structure + + Each filter condition has the following structure: + + ```json + + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + + ``` + + + ## Available Columns + + + ### Core Observation Fields + + - `id` (string) - Observation ID + + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + + - `name` (string) - Observation name + + - `traceId` (string) - Associated trace ID + + - `startTime` (datetime) - Observation start time + + - `endTime` (datetime) - Observation end time + + - `environment` (string) - Environment tag + + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + + - `statusMessage` (string) - Status message + + - `version` (string) - Version tag + + + ### Performance Metrics + + - `latency` (number) - Latency in seconds (calculated: end_time - + start_time) + + - `timeToFirstToken` (number) - Time to first token in seconds + + - `tokensPerSecond` (number) - Output tokens per second + + + ### Token Usage + + - `inputTokens` (number) - Number of input tokens + + - `outputTokens` (number) - Number of output tokens + + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + + ### Cost Metrics + + - `inputCost` (number) - Input cost in USD + + - `outputCost` (number) - Output cost in USD + + - `totalCost` (number) - Total cost in USD + + + ### Model Information + + - `model` (string) - Provided model name + + - `promptName` (string) - Associated prompt name + + - `promptVersion` (number) - Associated prompt version + + + ### Structured Data + + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata + key-value pairs. Use `key` parameter to filter on specific metadata + keys. + + + ### Associated Trace Fields (requires join with traces table) + + - `userId` (string) - User ID from associated trace + + - `traceName` (string) - Name from associated trace + + - `traceEnvironment` (string) - Environment from associated trace + + - `traceTags` (arrayOptions) - Tags from associated trace + + + ## Filter Examples + + ```json + + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + } + ] + + ``` + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/legacyObservationsViews' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/scores/{scoreId}: + delete: + description: Delete a score (supports both trace and session scores) + operationId: legacy_scoreV1_delete + tags: + - LegacyScoreV1 + parameters: + - name: scoreId + in: path + description: The unique langfuse identifier of a score + required: true + schema: + type: string + responses: + '204': + description: '' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/llm-connections: + get: + description: Get all LLM connections in a project + operationId: llmConnections_list + tags: + - LlmConnections + parameters: + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedLlmConnections' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + put: + description: >- + Create or update an LLM connection. The connection is upserted on + provider. + operationId: llmConnections_upsert + tags: + - LlmConnections + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/LlmConnection' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpsertLlmConnectionRequest' + /api/public/llm-connections/{id}: + delete: + description: >- + Delete an LLM connection by id. Evaluators that depend on the deleted + connection are automatically paused. + operationId: llmConnections_delete + tags: + - LlmConnections + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteLlmConnectionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/media/{mediaId}: + get: + description: Get a media record + operationId: media_get + tags: + - Media + parameters: + - name: mediaId + in: path + description: The unique langfuse identifier of a media record + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/GetMediaResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + patch: + description: Patch a media record + operationId: media_patch + tags: + - Media + parameters: + - name: mediaId + in: path + description: The unique langfuse identifier of a media record + required: true + schema: + type: string + responses: + '204': + description: '' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PatchMediaBody' + /api/public/media: + post: + description: Get a presigned upload URL for a media record + operationId: media_getUploadUrl + tags: + - Media + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/GetMediaUploadUrlResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetMediaUploadUrlRequest' + /api/public/v2/metrics: + get: + description: >- + Get metrics from the Langfuse project using a query object. V2 endpoint + with optimized performance. + + + ## V2 Differences + + - Supports `observations`, `scores-numeric`, `scores-boolean`, and + `scores-categorical` views only (traces view not supported) + + - Direct access to tags and release fields on observations + + - Semantic-root filtering and grouping through the v2-only + `isRootObservation` dimension + + - Backwards-compatible: traceName, traceRelease, traceVersion dimensions + are still available on observations view + + - High cardinality dimensions are not supported and will return a 400 + error (see below) + + + For more details, see the [Metrics API + documentation](https://langfuse.com/docs/metrics/features/metrics-api). + + + ## Available Views + + + ### observations + + Query observation-level data (spans, generations, events). + + + **Dimensions:** + + - `environment` - Deployment environment (e.g., production, staging) + + - `type` - Type of observation (SPAN, GENERATION, EVENT) + + - `name` - Name of the observation + + - `level` - Logging level of the observation + + - `version` - Version of the observation + + - `tags` - User-defined tags + + - `release` - Release version + + - `traceName` - Name of the parent trace (backwards-compatible) + + - `traceRelease` - Release version of the parent trace + (backwards-compatible, maps to release) + + - `traceVersion` - Version of the parent trace (backwards-compatible, + maps to version) + + - `providedModelName` - Name of the model used + + - `promptName` - Name of the prompt used + + - `promptVersion` - Version of the prompt used + + - `isRootObservation` - Boolean semantic-root status. `true` includes + physical roots and app roots whose SDK parent is external (so + `parentObservationId` may be non-null). + + - `startTimeMonth` - Month of start_time in YYYY-MM format + + + **Measures:** + + - `count` - Total number of observations + + - `latency` - Observation latency (milliseconds) + + - `streamingLatency` - Generation latency from completion start to end + (milliseconds) + + - `inputTokens` - Sum of input tokens consumed + + - `outputTokens` - Sum of output tokens produced + + - `totalTokens` - Sum of all tokens consumed + + - `outputTokensPerSecond` - Output tokens per second + + - `tokensPerSecond` - Total tokens per second + + - `inputCost` - Input cost (USD) + + - `outputCost` - Output cost (USD) + + - `totalCost` - Total cost (USD) + + - `timeToFirstToken` - Time to first token (milliseconds) + + - `countScores` - Number of scores attached to the observation + + + ### scores-numeric + + Query numeric and boolean score data. + + + **Dimensions:** + + - `environment` - Deployment environment + + - `name` - Name of the score (e.g., accuracy, toxicity) + + - `source` - Origin of the score (API, ANNOTATION, EVAL) + + - `dataType` - Data type (NUMERIC, BOOLEAN) + + - `configId` - Identifier of the score config + + - `timestampMonth` - Month in YYYY-MM format + + - `timestampDay` - Day in YYYY-MM-DD format + + - `value` - Numeric value of the score + + - `traceName` - Name of the parent trace + + - `tags` - Tags + + - `traceRelease` - Release version + + - `traceVersion` - Version + + - `observationName` - Name of the associated observation + + - `observationModelName` - Model name of the associated observation + + - `observationPromptName` - Prompt name of the associated observation + + - `observationPromptVersion` - Prompt version of the associated + observation + + + **Measures:** + + - `count` - Total number of scores + + - `value` - Score value (for aggregations) + + + ### scores-boolean + + Query boolean score data. It has the same score and parent + trace/observation dimensions as scores-numeric, plus: + + + **Dimensions:** + + - `booleanValue` - Boolean value for true/false grouping and filtering + + + **Measures:** + + - `count` - Total number of boolean scores + + - `value` - Numeric 0/1 score value; `avg` returns the true-rate + + + ### scores-categorical + + Query categorical score data. Same dimensions as scores-numeric except + uses `stringValue` instead of `value`. + + + **Measures:** + + - `count` - Total number of scores + + + ## High Cardinality Dimensions + + The following dimensions cannot be used as grouping dimensions in v2 + metrics API as they can cause performance issues. + + Use them in filters instead. + + + **observations view:** + + - `id` - Use traceId filter to narrow down results + + - `traceId` - Use traceId filter instead + + - `userId` - Use userId filter instead + + - `sessionId` - Use sessionId filter instead + + - `parentObservationId` - Use parentObservationId filter instead + + + **scores-numeric / scores-boolean / scores-categorical views:** + + - `id` - Use specific filters to narrow down results + + - `traceId` - Use traceId filter instead + + - `userId` - Use userId filter instead + + - `sessionId` - Use sessionId filter instead + + - `observationId` - Use observationId filter instead + + + ## Aggregations + + Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, + `p50`, `p75`, `p90`, `p95`, `p99`, `histogram` + + + ## Time Granularities + + Available granularities for timeDimension: `auto`, `minute`, `hour`, + `day`, `week`, `month` + + - `auto` bins the data into approximately 50 buckets based on the time + range + operationId: metrics_metrics + tags: + - Metrics + parameters: + - name: query + in: query + description: >- + JSON string containing the query parameters with the following + structure: + + ```json + + { + "view": string, // Required. One of "observations", "scores-numeric", "scores-boolean", "scores-categorical" + "dimensions": [ // Optional. Default: [] + { + "field": string // Field to group by (see available dimensions above) + } + ], + "metrics": [ // Required. At least one metric must be provided + { + "measure": string, // What to measure (see available measures above) + "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram" + } + ], + "filters": [ // Optional. Default: [] + { + "column": string, // Column to filter on (any dimension field) + "operator": string, // Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject/numberObject: same as string/number with required "key" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Value to compare against + "type": string, // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "key": string // Required only for stringObject/numberObject types (e.g., metadata filtering) + } + ], + "timeDimension": { // Optional. Default: null. If provided, results will be grouped by time + "granularity": string // One of "auto", "minute", "hour", "day", "week", "month" + }, + "fromTimestamp": string, // Required. ISO datetime string for start of time range + "toTimestamp": string, // Required. ISO datetime string for end of time range (must be after fromTimestamp) + "orderBy": [ // Optional. Default: null + { + "field": string, // Field to order by (dimension or metric alias) + "direction": string // "asc" or "desc" + } + ], + "config": { // Optional. Query-specific configuration + "bins": number, // Optional. Number of bins for histogram aggregation (1-100), default: 10 + "row_limit": number // Optional. Maximum number of rows to return (1-1000), default: 100 + } + } + + ``` + + + For example, to count semantic roots (including app roots with a + non-null external parent), use a boolean filter: + + ```json + + { + "view": "observations", + "metrics": [{"measure": "count", "aggregation": "count"}], + "filters": [{"column": "isRootObservation", "operator": "=", "value": true, "type": "boolean"}], + "fromTimestamp": "2025-01-01T00:00:00.000Z", + "toTimestamp": "2025-02-01T00:00:00.000Z" + } + + ``` + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MetricsV2Response' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/models: + post: + description: Create a model + operationId: models_create + tags: + - Models + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Model' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateModelRequest' + get: + description: Get all models + operationId: models_list + tags: + - Models + parameters: + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedModels' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/models/{id}: + get: + description: Get a model + operationId: models_get + tags: + - Models + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Model' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: >- + Delete a model. Cannot delete models managed by Langfuse. You can create + your own definition with the same modelName to override the definition + though. + operationId: models_delete + tags: + - Models + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '204': + description: '' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/v2/observations: + get: + description: >- + Get a list of observations with cursor-based pagination and flexible + field selection. + + + ## Cursor-based Pagination + + This endpoint uses cursor-based pagination for efficient traversal of + large datasets. + + The cursor is returned in the response metadata and should be passed in + subsequent requests + + to retrieve the next page of results. + + + ## Field Selection + + Use the `fields` parameter to control which observation fields are + returned: + + - `core` - Always included: id, traceId, startTime, endTime, projectId, + parentObservationId, type + + - `basic` - name, level, statusMessage, version, environment, + bookmarked, public, userId, sessionId, isRootObservation + + - `time` - completionStartTime, createdAt, updatedAt + + - `io` - input, output + + - `metadata` - metadata (truncated to 200 chars by default, use + `expandMetadata` to get full values) + + - `model` - providedModelName, internalModelId, modelParameters + + - `usage` - usageDetails, costDetails, totalCost, usagePricingTierName + + - `prompt` - promptId, promptName, promptVersion + + - `metrics` - latency, timeToFirstToken + + - `trace_context` - tags, release, traceName + + + If not specified, `core` and `basic` field groups are returned. + + + ## Filters + + Multiple filtering options are available via query parameters or the + structured `filter` parameter. + + When using the `filter` parameter, it takes precedence over individual + query parameter filters. + operationId: observations_getMany + tags: + - Observations + parameters: + - name: fields + in: query + description: >- + Comma-separated list of field groups to include in the response. + + Available groups: core, basic, time, io, metadata, model, usage, + prompt, metrics, trace_context. + + If not specified, `core` and `basic` field groups are returned. + + Example: "basic,usage,model" + required: false + schema: + type: string + nullable: true + - name: expandMetadata + in: query + description: |- + Comma-separated list of metadata keys to return non-truncated. + By default, metadata values over 200 characters are truncated. + Use this parameter to retrieve full values for specific keys. + Example: "key1,key2" + required: false + schema: + type: string + nullable: true + - name: limit + in: query + description: Number of items to return per page. Maximum 1000, default 50. + required: false + schema: + type: integer + nullable: true + - name: cursor + in: query + description: >- + Base64-encoded cursor for pagination. Use the cursor from the + previous response to get the next page. + required: false + schema: + type: string + nullable: true + - name: parseIoAsJson + in: query + description: |- + **Deprecated.** Setting this to `true` will return a 400 error. + Input/output fields are always returned as raw strings. + Remove this parameter or set it to `false`. + required: false + schema: + type: boolean + nullable: true + - name: name + in: query + required: false + schema: + type: string + nullable: true + - name: userId + in: query + required: false + schema: + type: string + nullable: true + - name: sessionId + in: query + description: Filter by session ID. + required: false + schema: + type: string + nullable: true + - name: type + in: query + description: >- + Filter by observation type (e.g., "GENERATION", "SPAN", "EVENT", + "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EVALUATOR", "EMBEDDING", + "GUARDRAIL") + required: false + schema: + type: string + nullable: true + - name: traceId + in: query + required: false + schema: + type: string + nullable: true + - name: level + in: query + description: >- + Optional filter for observations with a specific level (e.g. + "DEBUG", "DEFAULT", "WARNING", "ERROR"). + required: false + schema: + $ref: '#/components/schemas/ObservationLevel' + nullable: true + - name: parentObservationId + in: query + description: >- + Filter by the physical parent observation ID. + + An empty value matches only observations without a physical parent. + Use `isRootObservation` to include observations marked as app roots + by the SDK, which may retain a non-null `parentObservationId`. + required: false + schema: + type: string + nullable: true + - name: isRootObservation + in: query + description: >- + Filter by whether an observation is a logical root. + + Root observations include observations without a physical parent and + observations marked as app roots by the SDK. + + An app-root observation may have `isRootObservation=true` and a + non-null `parentObservationId`. + required: false + schema: + type: boolean + nullable: true + - name: environment + in: query + description: >- + Optional filter for observations where the environment is one of the + provided values. + required: false + schema: + type: array + items: + type: string + nullable: true + - name: fromStartTime + in: query + description: >- + Retrieve only observations with a start_time on or after this + datetime (ISO 8601). + required: false + schema: + type: string + format: date-time + nullable: true + - name: toStartTime + in: query + description: >- + Retrieve only observations with a start_time before this datetime + (ISO 8601). + required: false + schema: + type: string + format: date-time + nullable: true + - name: version + in: query + description: Optional filter to only include observations with a certain version. + required: false + schema: + type: string + nullable: true + - name: filter + in: query + description: >- + JSON string containing an array of filter conditions. When provided, + this takes precedence over query parameter filters (userId, name, + type, level, environment, fromStartTime, ...). + + + ## Filter Structure + + Each filter condition has the following structure: + + ```json + + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches" + // - numberObject: "=", ">", "<", ">=", "<=" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + } + ] + + ``` + + + ## Available Columns + + + ### Core Observation Fields + + - `id` (string) - Observation ID + + - `type` (string) - Observation type (SPAN, GENERATION, EVENT) + + - `name` (string) - Observation name + + - `traceId` (string) - Associated trace ID + + - `startTime` (datetime) - Observation start time + + - `endTime` (datetime) - Observation end time + + - `environment` (string) - Environment tag + + - `level` (string) - Log level (DEBUG, DEFAULT, WARNING, ERROR) + + - `statusMessage` (string) - Status message + + - `version` (string) - Version tag + + - `userId` (string) - User ID + + - `sessionId` (string) - Session ID + + - `isRootObservation` (boolean) - Whether the observation is a + logical root. Observations marked as app roots by the SDK may retain + a non-null parentObservationId. + + + ### Trace-Related Fields + + - `traceName` (string) - Name of the parent trace + + - `traceTags` (arrayOptions) - Tags from the parent trace + + - `tags` (arrayOptions) - Alias for traceTags + + + ### Performance Metrics + + - `latency` (number) - Latency in seconds (calculated: end_time - + start_time) + + - `timeToFirstToken` (number) - Time to first token in seconds + + - `tokensPerSecond` (number) - Output tokens per second + + + ### Token Usage + + - `inputTokens` (number) - Number of input tokens + + - `outputTokens` (number) - Number of output tokens + + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + + ### Cost Metrics + + - `inputCost` (number) - Input cost in USD + + - `outputCost` (number) - Output cost in USD + + - `totalCost` (number) - Total cost in USD + + + ### Model Information + + - `model` (string) - Provided model name (alias: + `providedModelName`) + + - `promptName` (string) - Associated prompt name + + - `promptVersion` (number) - Associated prompt version + + + ### Structured Data + + - `input` (string) - Observation input. Supports accelerated indexed + literal search with the `matches` operator. + + - `output` (string) - Observation output. Supports accelerated + indexed literal search with the `matches` operator. + + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata + key-value pairs. Use `key` parameter to filter on specific metadata + keys. + + + The `matches` operator is only supported for `input`, `output`, and + stringObject `metadata` filters. It performs indexed literal search + with token-boundary pruning using the events table text indexes. + Case sensitivity differs by target: `input` and `output` matches are + case-insensitive, while metadata value matches are case-sensitive. + Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. + Use `contains` for legacy substring semantics where the API allows + it. Any v2 `input` or `output` filter must be accompanied by at + least one `=` or `matches` filter on `input` or `output`; standalone + `contains`, `starts with`, `ends with`, and `does not contain` + filters on these columns are rejected. + + + ## Filter Examples + + ```json + + [ + { + "type": "string", + "column": "type", + "operator": "=", + "value": "GENERATION" + }, + { + "type": "number", + "column": "latency", + "operator": ">=", + "value": 2.5 + }, + { + "type": "stringObject", + "column": "metadata", + "key": "environment", + "operator": "=", + "value": "production" + }, + { + "type": "string", + "column": "output", + "operator": "matches", + "value": "needle" + }, + { + "type": "boolean", + "column": "isRootObservation", + "operator": "=", + "value": true + } + ] + + ``` + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ObservationsV2Response' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/otel/v1/traces: + post: + description: >- + **OpenTelemetry Traces Ingestion Endpoint** + + + This endpoint implements the OTLP/HTTP specification for trace + ingestion, providing native OpenTelemetry integration for Langfuse + Observability. + + + **Supported Formats:** + + - Binary Protobuf: `Content-Type: application/x-protobuf` + + - JSON Protobuf: `Content-Type: application/json` + + - Supports gzip compression via `Content-Encoding: gzip` header + + + **Specification Compliance:** + + - Conforms to [OTLP/HTTP Trace + Export](https://opentelemetry.io/docs/specs/otlp/#otlphttp) + + - Implements `ExportTraceServiceRequest` message format + + + **Documentation:** + + - Integration guide: + https://langfuse.com/integrations/native/opentelemetry + + - Data model: https://langfuse.com/docs/observability/data-model + operationId: opentelemetry_exportTraces + tags: + - Opentelemetry + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/OtelTraceResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + resourceSpans: + type: array + items: + $ref: '#/components/schemas/OtelResourceSpan' + description: >- + Array of resource spans containing trace data as defined in + the OTLP specification + required: + - resourceSpans + /api/public/organizations/memberships: + get: + description: >- + Get all memberships for the organization associated with the API key + (requires organization-scoped API key) + operationId: organizations_getOrganizationMemberships + tags: + - Organizations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + put: + description: >- + Create or update a membership for the organization associated with the + API key (requires organization-scoped API key) + operationId: organizations_updateOrganizationMembership + tags: + - Organizations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipRequest' + delete: + description: >- + Delete a membership from the organization associated with the API key + (requires organization-scoped API key) + operationId: organizations_deleteOrganizationMembership + tags: + - Organizations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipDeletionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteMembershipRequest' + /api/public/projects/{projectId}/memberships: + get: + description: >- + Get all memberships for a specific project (requires organization-scoped + API key) + operationId: organizations_getProjectMemberships + tags: + - Organizations + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + put: + description: >- + Create or update a membership for a specific project (requires + organization-scoped API key). The user must already be a member of the + organization. + operationId: organizations_updateProjectMembership + tags: + - Organizations + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipRequest' + delete: + description: >- + Delete a membership from a specific project (requires + organization-scoped API key). The user must be a member of the + organization. + operationId: organizations_deleteProjectMembership + tags: + - Organizations + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/MembershipDeletionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteMembershipRequest' + /api/public/organizations/projects: + get: + description: >- + Get all projects for the organization associated with the API key + (requires organization-scoped API key) + operationId: organizations_getOrganizationProjects + tags: + - Organizations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationProjectsResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/organizations/apiKeys: + get: + description: >- + Get all API keys for the organization associated with the API key + (requires organization-scoped API key) + operationId: organizations_getOrganizationApiKeys + tags: + - Organizations + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationApiKeysResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/projects: + get: + description: >- + Get Project associated with API key (requires project-scoped API key). + You can use GET /api/public/organizations/projects to get all projects + with an organization-scoped key. + operationId: projects_get + tags: + - Projects + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Projects' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + post: + description: Create a new project (requires organization-scoped API key) + operationId: projects_create + tags: + - Projects + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + metadata: + type: object + additionalProperties: true + nullable: true + description: Optional metadata for the project + retention: + type: integer + description: >- + Number of days to retain data. Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. + required: + - name + - retention + /api/public/projects/{projectId}: + put: + description: Update a project by ID (requires organization-scoped API key). + operationId: projects_update + tags: + - Projects + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + metadata: + type: object + additionalProperties: true + nullable: true + description: Optional metadata for the project + retention: + type: integer + nullable: true + description: |- + Number of days to retain data. + Must be 0 or at least 3 days. + Requires data-retention entitlement for non-zero values. + Optional. Will retain existing retention setting if omitted. + required: + - name + delete: + description: >- + Delete a project by ID (requires organization-scoped API key). Project + deletion is processed asynchronously. + operationId: projects_delete + tags: + - Projects + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '202': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectDeletionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/projects/{projectId}/apiKeys: + get: + description: Get all API keys for a project (requires organization-scoped API key) + operationId: projects_getApiKeys + tags: + - Projects + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyList' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + post: + description: >- + Create a new API key for a project (requires organization-scoped API + key) + operationId: projects_createApiKey + tags: + - Projects + parameters: + - name: projectId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + note: + type: string + nullable: true + description: Optional note for the API key + publicKey: + type: string + nullable: true + description: >- + Optional predefined public key. Must start with 'pk-lf-'. If + provided, secretKey must also be provided. + secretKey: + type: string + nullable: true + description: >- + Optional predefined secret key. Must start with 'sk-lf-'. If + provided, publicKey must also be provided. + /api/public/projects/{projectId}/apiKeys/{apiKeyId}: + delete: + description: Delete an API key for a project (requires organization-scoped API key) + operationId: projects_deleteApiKey + tags: + - Projects + parameters: + - name: projectId + in: path + required: true + schema: + type: string + - name: apiKeyId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyDeletionResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/v2/prompts/{name}/versions/{version}: + patch: + description: Update labels for a specific prompt version + operationId: promptVersion_update + tags: + - PromptVersion + parameters: + - name: name + in: path + description: >- + The name of the prompt. If the prompt is in a folder (e.g., + "folder/subfolder/prompt-name"), + + the folder path must be URL encoded. + required: true + schema: + type: string + - name: version + in: path + description: Version of the prompt to update + required: true + schema: + type: integer + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Prompt' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + newLabels: + type: array + items: + type: string + description: >- + New labels for the prompt version. Labels are unique across + versions. The "latest" label is reserved and managed by + Langfuse. + required: + - newLabels + /api/public/v2/prompts/{promptName}: + get: + description: Get a prompt + operationId: prompts_get + tags: + - Prompts + parameters: + - name: promptName + in: path + description: >- + The name of the prompt. If the prompt is in a folder (e.g., + "folder/subfolder/prompt-name"), + + the folder path must be URL encoded. + required: true + schema: + type: string + - name: version + in: query + description: Version of the prompt to be retrieved. + required: false + schema: + type: integer + nullable: true + - name: label + in: query + description: >- + Label of the prompt to be retrieved. Defaults to "production" if no + label or version is set. + required: false + schema: + type: string + nullable: true + - name: resolve + in: query + description: >- + Resolve prompt dependencies before returning the prompt. Defaults to + `true`. Set to `false` to return the raw stored prompt with + dependency tags intact. This bypasses prompt caching and is intended + for debugging or one-off jobs, not production runtime fetches. + required: false + schema: + type: boolean + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Prompt' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: >- + Delete prompt versions. If neither version nor label is specified, all + versions of the prompt are deleted. + operationId: prompts_delete + tags: + - Prompts + parameters: + - name: promptName + in: path + description: The name of the prompt + required: true + schema: + type: string + - name: label + in: query + description: >- + Optional label to filter deletion. If specified, deletes all prompt + versions that have this label. + required: false + schema: + type: string + nullable: true + - name: version + in: query + description: >- + Optional version to filter deletion. If specified, deletes only this + specific version of the prompt. + required: false + schema: + type: integer + nullable: true + responses: + '204': + description: '' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/v2/prompts: + get: + description: Get a list of prompt names with versions and labels + operationId: prompts_list + tags: + - Prompts + parameters: + - name: name + in: query + required: false + schema: + type: string + nullable: true + - name: label + in: query + required: false + schema: + type: string + nullable: true + - name: tag + in: query + required: false + schema: + type: string + nullable: true + - name: page + in: query + description: page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: limit of items per page + required: false + schema: + type: integer + nullable: true + - name: fromUpdatedAt + in: query + description: >- + Optional filter to only include prompt versions created/updated on + or after a certain datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: toUpdatedAt + in: query + description: >- + Optional filter to only include prompt versions created/updated + before a certain datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PromptMetaListResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + post: + description: Create a new version for the prompt with the given `name` + operationId: prompts_create + tags: + - Prompts + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Prompt' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePromptRequest' + /api/public/scim/ServiceProviderConfig: + get: + description: >- + Get SCIM Service Provider Configuration (requires organization-scoped + API key) + operationId: scim_getServiceProviderConfig + tags: + - Scim + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceProviderConfig' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/scim/ResourceTypes: + get: + description: Get SCIM Resource Types (requires organization-scoped API key) + operationId: scim_getResourceTypes + tags: + - Scim + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceTypesResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/scim/Schemas: + get: + description: Get SCIM Schemas (requires organization-scoped API key) + operationId: scim_getSchemas + tags: + - Scim + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/SchemasResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/scim/Users: + get: + description: List users in the organization (requires organization-scoped API key) + operationId: scim_listUsers + tags: + - Scim + parameters: + - name: filter + in: query + description: Filter expression (e.g. userName eq "value") + required: false + schema: + type: string + nullable: true + - name: startIndex + in: query + description: 1-based index of the first result to return (default 1) + required: false + schema: + type: integer + nullable: true + - name: count + in: query + description: Maximum number of results to return (default 100) + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScimUsersListResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + post: + description: >- + Create a new user in the organization (requires organization-scoped API + key) + operationId: scim_createUser + tags: + - Scim + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScimUser' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + userName: + type: string + description: User's email address (required) + name: + $ref: '#/components/schemas/ScimName' + description: User's name information + emails: + type: array + items: + $ref: '#/components/schemas/ScimEmail' + nullable: true + description: User's email addresses + active: + type: boolean + nullable: true + description: Whether the user is active + password: + type: string + nullable: true + description: Initial password for the user + required: + - userName + - name + /api/public/scim/Users/{userId}: + get: + description: Get a specific user by ID (requires organization-scoped API key) + operationId: scim_getUser + tags: + - Scim + parameters: + - name: userId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScimUser' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: >- + Remove a user from the organization (requires organization-scoped API + key). Note that this only removes the user from the organization but + does not delete the user entity itself. + operationId: scim_deleteUser + tags: + - Scim + parameters: + - name: userId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/score-configs: + post: + description: >- + Create a score configuration (config). Score configs are used to define + the structure of scores + operationId: scoreConfigs_create + tags: + - ScoreConfigs + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScoreConfig' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateScoreConfigRequest' + get: + description: Get all score configs + operationId: scoreConfigs_get + tags: + - ScoreConfigs + parameters: + - name: page + in: query + description: Page number, starts at 1. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: >- + Limit of items per page. If you encounter api issues due to too + large page sizes, try to reduce the limit + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScoreConfigs' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/score-configs/{configId}: + get: + description: Get a score config + operationId: scoreConfigs_get-by-id + tags: + - ScoreConfigs + parameters: + - name: configId + in: path + description: The unique langfuse identifier of a score config + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScoreConfig' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + patch: + description: Update a score config + operationId: scoreConfigs_update + tags: + - ScoreConfigs + parameters: + - name: configId + in: path + description: The unique langfuse identifier of a score config + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ScoreConfig' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateScoreConfigRequest' + /api/public/v3/scores: + get: + description: |- + Get a list of scores with a polymorphic `value` field (v3). + + The `value` field type depends on `dataType`: + - `NUMERIC` → number + - `BOOLEAN` → boolean + - `CATEGORICAL`, `TEXT`, `CORRECTION` → string + + The response always includes the core fields: id, projectId, name, + value, dataType, source, timestamp, environment, createdAt, updatedAt. + + Additional field groups can be requested via the `fields` parameter: + - `details` — adds comment, configId, metadata + - `subject` — adds the subject object describing the entity the score + is attached to: kind (trace, observation, session, or experiment), + id, and traceId for observation-level scores + - `annotation` — adds authorUserId, queueId + + Unknown group names return HTTP 400. + operationId: scoresV3_getManyV3 + tags: + - ScoresV3 + parameters: + - name: limit + in: query + description: >- + Number of items per page. Maximum 100, default 50. Requests with a + limit greater than 100 return HTTP 400. + required: false + schema: + type: integer + nullable: true + - name: cursor + in: query + description: >- + URL-safe base64 (base64url) cursor for pagination. Use the cursor + from the previous response to get the next page. Absent on the final + page. + required: false + schema: + type: string + nullable: true + - name: fields + in: query + description: >- + Comma-separated field groups to include in addition to the + always-returned core fields. Allowed: details, subject, annotation — + see the endpoint description for the fields each group adds. Unknown + names return HTTP 400. + required: false + schema: + type: string + nullable: true + - name: id + in: query + description: >- + Comma-separated list of score IDs to filter by (OR within, AND + across filters). + required: false + schema: + type: string + nullable: true + - name: name + in: query + description: Comma-separated list of score names to filter by. + required: false + schema: + type: string + nullable: true + - name: source + in: query + description: >- + Comma-separated list of score sources to filter by (e.g. API, + ANNOTATION, EVAL). Case-insensitive — `api` and `API` are + equivalent. + required: false + schema: + type: string + nullable: true + - name: dataType + in: query + description: >- + Comma-separated list of data types to filter by (NUMERIC, BOOLEAN, + CATEGORICAL, TEXT, CORRECTION). Case-insensitive — `numeric` and + `NUMERIC` are equivalent. Must be a single value when used with + value, valueMin, or valueMax; otherwise the request returns HTTP + 400. Must be NUMERIC when used with valueMin or valueMax. + required: false + schema: + type: string + nullable: true + - name: environment + in: query + description: Comma-separated list of environments to filter by. + required: false + schema: + type: string + nullable: true + - name: configId + in: query + description: Comma-separated list of score config IDs to filter by. + required: false + schema: + type: string + nullable: true + - name: queueId + in: query + description: Comma-separated list of annotation queue IDs to filter by. + required: false + schema: + type: string + nullable: true + - name: authorUserId + in: query + description: Comma-separated list of author user IDs to filter by. + required: false + schema: + type: string + nullable: true + - name: value + in: query + description: >- + Comma-separated list of exact values to filter by. Requires a single + dataType from NUMERIC, BOOLEAN, or CATEGORICAL; any other dataType, + multiple dataTypes, or omitting dataType returns HTTP 400. For + BOOLEAN, each value must be "true" or "false"; for NUMERIC, each + value must be a finite number. Otherwise the request returns HTTP + 400. + required: false + schema: + type: string + nullable: true + - name: valueMin + in: query + description: >- + Inclusive lower bound on the numeric value. Requires + dataType=NUMERIC as a single value; otherwise the request returns + HTTP 400. + required: false + schema: + type: number + format: double + nullable: true + - name: valueMax + in: query + description: >- + Inclusive upper bound on the numeric value. Requires + dataType=NUMERIC as a single value; otherwise the request returns + HTTP 400. + required: false + schema: + type: number + format: double + nullable: true + - name: traceId + in: query + description: >- + Comma-separated list of trace IDs to filter by. Mutually exclusive + with sessionId, experimentId. May be combined with observationId to + scope the observation lookup to a specific trace. + required: false + schema: + type: string + nullable: true + - name: sessionId + in: query + description: >- + Comma-separated list of session IDs to filter by. Mutually exclusive + with traceId, observationId, experimentId. + required: false + schema: + type: string + nullable: true + - name: observationId + in: query + description: >- + Comma-separated list of observation IDs to filter by. Requires + traceId to be specified, because observation IDs are scoped to a + trace. Mutually exclusive with sessionId, experimentId. Returns HTTP + 400 when used without traceId. + required: false + schema: + type: string + nullable: true + - name: experimentId + in: query + description: >- + Comma-separated list of dataset run IDs (experiment IDs) to filter + by. Mutually exclusive with traceId, sessionId, observationId. + required: false + schema: + type: string + nullable: true + - name: fromTimestamp + in: query + description: Inclusive lower bound on the score timestamp. + required: false + schema: + type: string + format: date-time + nullable: true + - name: toTimestamp + in: query + description: Exclusive upper bound on the score timestamp. + required: false + schema: + type: string + format: date-time + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/GetScoresV3Response' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/scores: + post: + description: >- + Create a score (supports trace, observation, session, and dataset run + scores) + operationId: scores_create + tags: + - Scores + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CreateScoreResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateScoreRequest' + /api/public/v2/scores: + get: + description: |- + **Deprecated.** Use `GET /api/public/v3/scores` instead. This endpoint + is no longer available on Langfuse v4 and later. + + Get a list of scores (supports both trace and session scores) + operationId: scores_get-many + tags: + - Scores + parameters: + - name: page + in: query + description: Page number, starts at 1. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: >- + Limit of items per page. Maximum 100. Defaults to 50. Requests with + a limit greater than 100 return HTTP 400. If you encounter api + issues due to too large page sizes, try to reduce the limit. + required: false + schema: + type: integer + nullable: true + - name: userId + in: query + description: Retrieve only scores with this userId associated to the trace. + required: false + schema: + type: string + nullable: true + - name: name + in: query + description: Retrieve only scores with this name. + required: false + schema: + type: string + nullable: true + - name: fromTimestamp + in: query + description: >- + Optional filter to only include scores created on or after a certain + datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: toTimestamp + in: query + description: >- + Optional filter to only include scores created before a certain + datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: environment + in: query + description: >- + Optional filter for scores where the environment is one of the + provided values. + required: false + schema: + type: array + items: + type: string + nullable: true + - name: source + in: query + description: Retrieve only scores from a specific source. + required: false + schema: + $ref: '#/components/schemas/ScoreSource' + nullable: true + - name: operator + in: query + description: Retrieve only scores with value. + required: false + schema: + type: string + nullable: true + - name: value + in: query + description: Retrieve only scores with value. + required: false + schema: + type: number + format: double + nullable: true + - name: scoreIds + in: query + description: Comma-separated list of score IDs to limit the results to. + required: false + schema: + type: string + nullable: true + - name: configId + in: query + description: Retrieve only scores with a specific configId. + required: false + schema: + type: string + nullable: true + - name: sessionId + in: query + description: Retrieve only scores with a specific sessionId. + required: false + schema: + type: string + nullable: true + - name: datasetRunId + in: query + description: Retrieve only scores with a specific datasetRunId. + required: false + schema: + type: string + nullable: true + - name: traceId + in: query + description: Retrieve only scores with a specific traceId. + required: false + schema: + type: string + nullable: true + - name: observationId + in: query + description: Comma-separated list of observation IDs to filter scores by. + required: false + schema: + type: string + nullable: true + - name: queueId + in: query + description: Retrieve only scores with a specific annotation queueId. + required: false + schema: + type: string + nullable: true + - name: dataType + in: query + description: Retrieve only scores with a specific dataType. + required: false + schema: + $ref: '#/components/schemas/ScoreDataType' + nullable: true + - name: traceTags + in: query + description: >- + Only scores linked to traces that include all of these tags will be + returned. + required: false + schema: + type: array + items: + type: string + nullable: true + - name: fields + in: query + description: >- + Comma-separated list of field groups to include in the response. + Available field groups: 'score' (core score fields), 'trace' (trace + properties: userId, tags, environment, sessionId). If not specified, + both 'score' and 'trace' are returned by default. Example: 'score' + to exclude trace data, 'score,trace' to include both. Note: When + filtering by trace properties (using userId or traceTags + parameters), the 'trace' field group must be included, otherwise a + 400 error will be returned. + required: false + schema: + type: string + nullable: true + - name: filter + in: query + description: >- + A JSON stringified array of filter objects. Each object requires + type, column, operator, and value. Supports filtering by score + metadata using the stringObject type. Example: + [{"type":"stringObject","column":"metadata","key":"user_id","operator":"=","value":"abc123"}]. + Supported types: stringObject (metadata key-value filtering), + string, number, datetime, stringOptions, arrayOptions. Supported + operators for stringObject: =, contains, does not contain, starts + with, ends with. + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/GetScoresResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/v2/scores/{scoreId}: + get: + description: |- + **Deprecated.** Use `GET /api/public/v3/scores` with the `id` filter + instead. This endpoint is no longer available on Langfuse v4 and later. + + Get a score (supports both trace and session scores) + operationId: scores_get-by-id + tags: + - Scores + parameters: + - name: scoreId + in: path + description: The unique langfuse identifier of a score + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Score' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/sessions: + get: + description: >- + Get sessions. + + + This legacy endpoint is not recommended for new data extraction + workflows. + + Use the v2 observations endpoint with a bounded time range and group + rows by + + `sessionId` instead: + + `GET /api/public/v2/observations?fromStartTime=&toStartTime=`. + operationId: sessions_list + tags: + - Sessions + parameters: + - name: page + in: query + description: Page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: >- + Limit of items per page. If you encounter api issues due to too + large page sizes, try to reduce the limit. + required: false + schema: + type: integer + nullable: true + - name: fromTimestamp + in: query + description: >- + Optional filter to only include sessions created on or after a + certain datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: toTimestamp + in: query + description: >- + Optional filter to only include sessions created before a certain + datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: environment + in: query + description: >- + Optional filter for sessions where the environment is one of the + provided values. + required: false + schema: + type: array + items: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedSessions' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/sessions/{sessionId}: + get: + description: >- + Get a session. + + + Please note that `traces` on this endpoint are not paginated. For large + + sessions or new data extraction workflows, use the v2 observations + endpoint + + with a URL-encoded `sessionId` filter and a bounded time range: + + `GET /api/public/v2/observations?filter=&fromStartTime=&toStartTime=`. + operationId: sessions_get + tags: + - Sessions + parameters: + - name: sessionId + in: path + description: The unique id of a session + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/SessionWithTraces' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/traces/{traceId}: + get: + description: Get a specific trace + operationId: trace_get + tags: + - Trace + parameters: + - name: traceId + in: path + description: The unique langfuse identifier of a trace + required: true + schema: + type: string + - name: fields + in: query + description: >- + Comma-separated list of fields to include in the response. Available + field groups: 'core' (always included), 'io' (input, output, + metadata), 'scores', 'observations', 'metrics'. If not specified, + all fields are returned. Example: 'core,scores,metrics'. Note: + Excluded 'observations' or 'scores' fields return empty arrays; + excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/TraceWithFullDetails' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: Delete a specific trace + operationId: trace_delete + tags: + - Trace + parameters: + - name: traceId + in: path + description: The unique langfuse identifier of the trace to delete + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteTraceResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + /api/public/traces: + get: + description: Get list of traces + operationId: trace_list + tags: + - Trace + parameters: + - name: page + in: query + description: Page number, starts at 1 + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: >- + Limit of items per page. If you encounter api issues due to too + large page sizes, try to reduce the limit. + required: false + schema: + type: integer + nullable: true + - name: userId + in: query + required: false + schema: + type: string + nullable: true + - name: name + in: query + required: false + schema: + type: string + nullable: true + - name: sessionId + in: query + required: false + schema: + type: string + nullable: true + - name: fromTimestamp + in: query + description: >- + Optional filter to only include traces with a trace.timestamp on or + after a certain datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: toTimestamp + in: query + description: >- + Optional filter to only include traces with a trace.timestamp before + a certain datetime (ISO 8601) + required: false + schema: + type: string + format: date-time + nullable: true + - name: orderBy + in: query + description: >- + Format of the string [field].[asc/desc]. Fields: id, timestamp, + name, userId, release, version, public, bookmarked, sessionId. + Example: timestamp.asc + required: false + schema: + type: string + nullable: true + - name: tags + in: query + description: Only traces that include all of these tags will be returned. + required: false + schema: + type: array + items: + type: string + nullable: true + - name: version + in: query + description: Optional filter to only include traces with a certain version. + required: false + schema: + type: string + nullable: true + - name: release + in: query + description: Optional filter to only include traces with a certain release. + required: false + schema: + type: string + nullable: true + - name: environment + in: query + description: >- + Optional filter for traces where the environment is one of the + provided values. + required: false + schema: + type: array + items: + type: string + nullable: true + - name: fields + in: query + description: >- + Comma-separated list of fields to include in the response. Available + field groups: 'core' (always included), 'io' (input, output, + metadata), 'scores', 'observations', 'metrics'. If not specified, + all fields are returned. Example: 'core,scores,metrics'. Note: + Excluded 'observations' or 'scores' fields return empty arrays; + excluded 'metrics' returns -1 for 'totalCost' and 'latency'. + required: false + schema: + type: string + nullable: true + - name: filter + in: query + description: >- + JSON string containing an array of filter conditions. When provided, + this takes precedence over query parameter filters (userId, name, + sessionId, tags, version, release, environment, fromTimestamp, + toTimestamp). + + + ## Filter Structure + + Each filter condition has the following structure: + + ```json + + [ + { + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "booleanObject", "boolean", "null" + "column": string, // Required. Column to filter on (see available columns below) + "operator": string, // Required. Operator based on type: + // - datetime: ">", "<", ">=", "<=" + // - string: "=", "contains", "does not contain", "starts with", "ends with" + // - stringOptions: "any of", "none of" + // - categoryOptions: "any of", "none of" + // - arrayOptions: "any of", "none of", "all of" + // - number: "=", ">", "<", ">=", "<=" + // - stringObject: "=", "contains", "does not contain", "starts with", "ends with" + // - numberObject: "=", ">", "<", ">=", "<=" + // - booleanObject: "=", "<>" + // - boolean: "=", "<>" + // - null: "is null", "is not null" + "value": any, // Required (except for null type). Value to compare against. Type depends on filter type + "key": string // Required only for stringObject, numberObject, booleanObject, and categoryOptions types when filtering on nested fields like metadata or score names + } + ] + + ``` + + + ## Available Columns + + + ### Core Trace Fields + + - `id` (string) - Trace ID + + - `name` (string) - Trace name + + - `timestamp` (datetime) - Trace timestamp + + - `userId` (string) - User ID + + - `sessionId` (string) - Session ID + + - `environment` (string) - Environment tag + + - `version` (string) - Version tag + + - `release` (string) - Release tag + + - `tags` (arrayOptions) - Array of tags + + - `bookmarked` (boolean) - Bookmark status + + + ### Structured Data + + - `metadata` (stringObject/numberObject/categoryOptions) - Metadata + key-value pairs. Use `key` parameter to filter on specific metadata + keys. + + + ### Aggregated Metrics (from observations) + + These metrics are aggregated from all observations within the trace: + + - `latency` (number) - Latency in seconds (time from first + observation start to last observation end) + + - `inputTokens` (number) - Total input tokens across all + observations + + - `outputTokens` (number) - Total output tokens across all + observations + + - `totalTokens` (number) - Total tokens (alias: `tokens`) + + - `inputCost` (number) - Total input cost in USD + + - `outputCost` (number) - Total output cost in USD + + - `totalCost` (number) - Total cost in USD + + + ### Observation Level Aggregations + + These fields aggregate observation levels within the trace: + + - `level` (string) - Highest severity level (ERROR > WARNING > + DEFAULT > DEBUG) + + - `warningCount` (number) - Count of WARNING level observations + + - `errorCount` (number) - Count of ERROR level observations + + - `defaultCount` (number) - Count of DEFAULT level observations + + - `debugCount` (number) - Count of DEBUG level observations + + + ### Scores (requires join with scores table) + + - `scores_avg` (number) - Average of numeric scores (alias: + `scores`) + + - `score_categories` (categoryOptions) - Categorical score values + + - `score_booleans` (booleanObject) - Boolean score values. Use `key` + for the score name and a boolean `value`, e.g. `{"type": + "booleanObject", "column": "score_booleans", "key": "is_correct", + "operator": "=", "value": true}`. The `<>` operator also matches + traces without a score of that name. + + + ## Filter Examples + + ```json + + [ + { + "type": "datetime", + "column": "timestamp", + "operator": ">=", + "value": "2024-01-01T00:00:00Z" + }, + { + "type": "string", + "column": "userId", + "operator": "=", + "value": "user-123" + }, + { + "type": "number", + "column": "totalCost", + "operator": ">=", + "value": 0.01 + }, + { + "type": "arrayOptions", + "column": "tags", + "operator": "all of", + "value": ["production", "critical"] + }, + { + "type": "stringObject", + "column": "metadata", + "key": "customer_tier", + "operator": "=", + "value": "enterprise" + } + ] + + ``` + + + ## Performance Notes + + - Filtering on `userId`, `sessionId`, or `metadata` may enable skip + indexes for better query performance + + - Score filters require a join with the scores table and may impact + query performance + required: false + schema: + type: string + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Traces' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + delete: + description: Delete multiple traces + operationId: trace_deleteMultiple + tags: + - Trace + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteTraceResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + traceIds: + type: array + items: + type: string + description: List of trace IDs to delete + required: + - traceIds + /api/public/unstable/dashboard-widgets: + get: + description: |- + List dashboard widgets in the project, ordered by most recently + updated first. + + Responses may include legacy `traces` widgets created before this + API existed. New widgets cannot be created with `view: traces`. + operationId: unstable_dashboardWidgets_list + tags: + - UnstableDashboardWidgets + parameters: + - name: page + in: query + description: 1-based page number. Defaults to `1`. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: Maximum number of items per page. Defaults to `50`. + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardWidgetList' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + post: + description: >- + Create a dashboard widget (a standalone chart definition you place on + + any dashboard). + + + This endpoint creates the widget only; place it on a dashboard via + + `POST /dashboards/{dashboardId}/placements`. + + + Supported views are `observations`, `scores-numeric`, `scores-boolean`, + and `scores-categorical`. + + The legacy `traces` view is not supported by this unstable API. + + Widgets are created as v2 internally. + + + `chartConfig` is optional and defaults to the plain config for + + `chartType`; when `chartConfig.type` is given it must match + + `chartType`. + + + Unstable API note: + + - This surface may evolve while dashboard/widget APIs are being + finalized. + operationId: unstable_dashboardWidgets_create + tags: + - UnstableDashboardWidgets + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardWidget' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableCreateDashboardWidgetRequest' + /api/public/unstable/dashboard-widgets/{widgetId}: + get: + description: |- + Get a dashboard widget by id. + + The response may use `view: traces` for legacy widgets. + operationId: unstable_dashboardWidgets_get + tags: + - UnstableDashboardWidgets + parameters: + - name: widgetId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardWidget' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + patch: + description: |- + Update a dashboard widget. + + All fields are optional; at least one field is required. + Changing `chartType` without sending `chartConfig` resets the config + to the new chart type's defaults. When `chartConfig.type` is given + it must match the widget's (possibly updated) `chartType`. + + `view` cannot be changed to the legacy `traces` value. Existing + `traces` widgets may be updated on other fields. + operationId: unstable_dashboardWidgets_update + tags: + - UnstableDashboardWidgets + parameters: + - name: widgetId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardWidget' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableUpdateDashboardWidgetRequest' + delete: + description: |- + Delete a dashboard widget. + + The API returns `409` while the widget is still placed on a dashboard. + Remove those placements first. + operationId: unstable_dashboardWidgets_delete + tags: + - UnstableDashboardWidgets + parameters: + - name: widgetId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDeleteDashboardWidgetResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '409': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + /api/public/unstable/dashboards: + get: + description: |- + List dashboards in the project, ordered by most recently updated + first. + operationId: unstable_dashboards_list + tags: + - UnstableDashboards + parameters: + - name: page + in: query + description: 1-based page number. Defaults to `1`. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: Maximum number of items per page. Defaults to `50`. + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardList' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + post: + description: Create a dashboard. + operationId: unstable_dashboards_create + tags: + - UnstableDashboards + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboard' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableCreateDashboardRequest' + /api/public/unstable/dashboards/{dashboardId}: + get: + description: Get a dashboard by id. + operationId: unstable_dashboards_get + tags: + - UnstableDashboards + parameters: + - name: dashboardId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboard' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + patch: + description: Update a dashboard's name, description, definition, or filters. + operationId: unstable_dashboards_update + tags: + - UnstableDashboards + parameters: + - name: dashboardId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboard' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableUpdateDashboardRequest' + delete: + description: Delete a dashboard. + operationId: unstable_dashboards_delete + tags: + - UnstableDashboards + parameters: + - name: dashboardId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDeleteDashboardResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + /api/public/unstable/dashboards/{dashboardId}/placements: + post: + description: |- + Add a placement to a dashboard grid (see `DashboardPlacement` for + grid semantics). + + `id` and the position fields are optional: when omitted, the + placement gets a server-generated id and is appended below all + existing tiles as a 6x6 tile. Returns the created placement. + + The referenced widget must exist in the same project or be a + Langfuse-managed widget. The API returns `409` if a placement with + the same `id` already exists on the dashboard. + operationId: unstable_dashboards_addPlacement + tags: + - UnstableDashboards + parameters: + - name: dashboardId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardPlacement' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '409': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableCreateDashboardPlacementRequest' + /api/public/unstable/dashboards/{dashboardId}/placements/{placementId}: + patch: + description: |- + Move or resize a placement. All fields are optional; at least one is + required. Omitted fields keep their current value. The placement's + content (widget/preset reference) and id cannot change — delete and + re-add the placement to swap content. Returns the updated placement. + operationId: unstable_dashboards_updatePlacement + tags: + - UnstableDashboards + parameters: + - name: dashboardId + in: path + required: true + schema: + type: string + - name: placementId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDashboardPlacement' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableUpdateDashboardPlacementRequest' + delete: + description: >- + Remove a placement from a dashboard grid without deleting the referenced + widget. + operationId: unstable_dashboards_deletePlacement + tags: + - UnstableDashboards + parameters: + - name: dashboardId + in: path + required: true + schema: + type: string + - name: placementId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDeleteDashboardPlacementResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + /api/public/unstable/evaluation-rules: + post: + description: >- + Create an evaluation rule. + + + An evaluation rule defines **what** incoming data should be evaluated + and **how prompt variables should be populated** from that data. + + + Use this resource after choosing an evaluator from the evaluator + endpoints. + + + Key rules: + + - `name` must be unique within the project for public evaluation rules + + - `target` must be `observation` or `experiment` + + - `evaluator.name` + `evaluator.scope` must identify an existing + evaluator family returned by the evaluator endpoints + + - Langfuse resolves that family to its latest version before saving the + evaluation rule + + - for `target=experiment`, use dataset `id` values from `GET + /api/public/v2/datasets` when filtering by `datasetId` + + - for `llm_as_judge` evaluators, every evaluator prompt variable must be + mapped exactly once + + - for `code` evaluators, Langfuse uses the fixed code runtime mapping; + omit `mapping` in create and update requests + + - for user-provided `llm_as_judge` mappings, `expected_output` and + `experiment_item_metadata` are only valid for `target=experiment` + + - if `enabled=true`, Langfuse validates that the referenced evaluator + can currently run + + - at most 50 evaluation rules can be effectively active in one project + at the same time + + + If an evaluation rule with the same `name` already exists in the + project, the API returns `409`. + + In that case, update the existing resource with `PATCH + /api/public/unstable/evaluation-rules/{evaluationRuleId}` instead of + creating a second one. + + + If enabling this resource would exceed the 50-active limit, the API also + returns `409`. + + In that case, disable or pause another active evaluation rule before + enabling a new one. + + + Current scope: + + - evaluation rules are live-ingestion rules only + + - they do not trigger historical backfills + + + Recovery guidance: + + - `400 invalid_filter_value`: fix the filter `column` or `value` using + `details.column`, `details.invalidValues`, and `details.allowedValues` + + - `400 invalid_filter_value` with `details.column=datasetId`: call `GET + /api/public/v2/datasets`, then retry with dataset `id` values from that + response + + - `400 missing_variable_mapping`: for `llm_as_judge` evaluators, fetch + the evaluator again and make sure every variable in `variables` appears + exactly once in `mapping` + + - `400 duplicate_variable_mapping`: remove repeated mappings for the + same variable + + - `400 invalid_variable_mapping`: for `llm_as_judge`, switch to a valid + `source` for the selected `target`, or fix the variable name + + - `400 invalid_json_path`: remove or correct the `jsonPath` + + - `422 evaluator_preflight_failed`: the selected evaluator cannot run + with the resolved model configuration. Fix the evaluator/default model + setup, then retry the create request. + operationId: unstable_evaluationRules_create + tags: + - UnstableEvaluationRules + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableEvaluationRule' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '409': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '422': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableCreateEvaluationRuleRequest' + get: + description: >- + List evaluation rules in the authenticated project. + + + This includes legacy `trace` and `dataset` rules so they can be + inspected and migrated to v4 rules. Legacy rules are read-only through + this API; create, update, and delete continue to support only + `observation` and `experiment` rules. + operationId: unstable_evaluationRules_list + tags: + - UnstableEvaluationRules + parameters: + - name: page + in: query + description: 1-based page number. Defaults to `1`. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: Maximum number of items per page. Defaults to `50`. + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableEvaluationRules' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + /api/public/unstable/evaluation-rules/{evaluationRuleId}: + get: + description: >- + Get one evaluation rule by its identifier. + + + Use this endpoint to inspect the current evaluator, target, mapping, + filters, execution timing, and effective runtime status. Legacy `trace` + and `dataset` rules are returned for migration and are read-only through + this API. + operationId: unstable_evaluationRules_get + tags: + - UnstableEvaluationRules + parameters: + - name: evaluationRuleId + in: path + description: >- + Evaluation rule identifier returned by the evaluation rule + endpoints. + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableReadableEvaluationRule' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + patch: + description: >- + Update an evaluation rule. + + + Typical uses: + + - enable or disable live execution + + - switch to another evaluator + + - adjust sampling + + - change filters + + - update LLM-as-judge variable mappings + + + Important behavior: + + - provide only the fields you want to change + + - if you provide `evaluator`, Langfuse resolves that evaluator family to + its latest version before saving + + - changing `target`, `filter`, or an LLM-as-judge `mapping` must still + produce a valid target-specific configuration + + - if you change `target` for an LLM-as-judge rule, also send a + compatible `filter` and `mapping` in the same request unless the + existing ones are still valid for the new target + + - for `code` evaluator rules, omit `mapping`; Langfuse stores the fixed + code runtime mapping automatically + + - if the resulting config is enabled, Langfuse re-validates that the + selected evaluator can run + + - if the update would move a non-active evaluation rule into the active + state and the project already has 50 active evaluation rules, the API + returns `409` + + + Recovery guidance: + + - if an LLM-as-judge update fails with `missing_variable_mapping` or + `invalid_variable_mapping` after changing `evaluator` or `target`, + resend the request with a complete new `mapping` + + - if the update fails with `invalid_filter_value` after changing + `target`, resend the request with a target-compatible `filter` + operationId: unstable_evaluationRules_update + tags: + - UnstableEvaluationRules + parameters: + - name: evaluationRuleId + in: path + description: Evaluation rule identifier. + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableEvaluationRule' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '422': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableUpdateEvaluationRuleRequest' + delete: + description: >- + Delete an evaluation rule. + + + This removes the live-ingestion rule only. It does not delete the + referenced evaluator. + operationId: unstable_evaluationRules_delete + tags: + - UnstableEvaluationRules + parameters: + - name: evaluationRuleId + in: path + description: Evaluation rule identifier. + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDeleteEvaluationRuleResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + /api/public/unstable/evaluators: + post: + description: >- + Create an evaluator in the authenticated project. + + + Use evaluators to define **how** Langfuse should score data. + + LLM-as-a-judge evaluators define a prompt, expected structured output, + and optional model configuration. + + Code evaluators define source code and a runtime language. + + + Naming behavior: + + - If this is a new evaluator name in your project, Langfuse creates + version `1`. + + - If the name already exists in your project, Langfuse creates the next + version and returns it. + + - When a new project version is created, existing evaluation rules in + that project automatically move to the newest version for that evaluator + name. + + + Recommended workflow: + + 1. Create the evaluator. + + 2. Read the returned `variables` array. + + 3. Read the returned `outputDefinition.dataType` so the client knows + whether future scores will be numeric, boolean, or categorical. + + 4. Create one or more evaluation rules that reference the returned + evaluator family using `name` and `scope`. + + + Code evaluator validation: + + - At creation, Langfuse only validates the request shape + + - The `sourceCode` itself is not executed here. It is first run + (preflight-tested against a sample observation) when you link the + evaluator to an evaluation rule, so runtime errors in the code surface + at evaluation-rule creation, not at evaluator creation. + + + Recovery guidance: + + - `422` with `code=evaluator_preflight_failed`: the evaluator cannot run + with the resolved model configuration. Add a valid explicit + `modelConfig`, or configure the project's default evaluation model, then + retry the same request. + + - `400` with `code=invalid_body`: the request shape is malformed. Use + the structured `details.issues` array to fix the specific fields and + retry. + + - `400` with `code=invalid_body` on `outputDefinition`: for + `type=llm_as_judge`, send `dataType`, `reasoning.description`, and + `score.description`. Do not send `version`; it is not part of the public + request shape. + + - If `type` is omitted, Langfuse treats the request as + `type=llm_as_judge` for backwards compatibility. New clients should send + `type` explicitly. + + + Unstable API note: + + - This surface may evolve while the underlying evaluation data model is + being redesigned. + operationId: unstable_evaluators_create + tags: + - UnstableEvaluators + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableEvaluator' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '409': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '422': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/unstableCreateEvaluatorRequest' + get: + description: >- + List the evaluators available to the authenticated project. + + + Important behavior: + + - This endpoint returns the latest version of each available evaluator. + + - Results can include evaluators from your project and Langfuse-managed + evaluators. + + - If the same evaluator name exists in both places, both are returned as + separate items with different `scope` values. + operationId: unstable_evaluators_list + tags: + - UnstableEvaluators + parameters: + - name: page + in: query + description: 1-based page number. Defaults to `1`. + required: false + schema: + type: integer + nullable: true + - name: limit + in: query + description: Maximum number of items per page. Defaults to `50`. + required: false + schema: + type: integer + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableEvaluators' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + /api/public/unstable/evaluators/{evaluatorId}: + get: + description: >- + Get one evaluator by `id`. + + + Use this endpoint when you want the prompt, output definition, model + configuration, and derived variables for the evaluator you plan to use + in an evaluation rule. + operationId: unstable_evaluators_get + tags: + - UnstableEvaluators + parameters: + - name: evaluatorId + in: path + description: Evaluator identifier returned by the evaluator endpoints. + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableEvaluator' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 + delete: + description: >- + Delete an evaluator. + + + Important behavior: + + - This deletes the evaluator including all of its stored versions; + `evaluatorId` may reference any version. + + - The API returns `409` while evaluation rules still reference the + evaluator. Delete those evaluation rules first. + + - Langfuse-managed evaluators (`scope=managed`) cannot be deleted; the + API returns `403`. + + - Scores already produced by the evaluator are not deleted. + operationId: unstable_evaluators_delete + tags: + - UnstableEvaluators + parameters: + - name: evaluatorId + in: path + description: Evaluator identifier returned by the evaluator endpoints. + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstableDeleteEvaluatorResponse' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + '409': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '429': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + '500': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/unstablePublicApiError' + security: *ref_0 +components: + schemas: + AnnotationQueueStatus: + title: AnnotationQueueStatus + type: string + enum: + - PENDING + - COMPLETED + AnnotationQueueObjectType: + title: AnnotationQueueObjectType + type: string + enum: + - TRACE + - OBSERVATION + - SESSION + AnnotationQueue: + title: AnnotationQueue + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + nullable: true + scoreConfigIds: + type: array + items: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - name + - description + - scoreConfigIds + - createdAt + - updatedAt + AnnotationQueueItem: + title: AnnotationQueueItem + type: object + properties: + id: + type: string + queueId: + type: string + objectId: + type: string + objectType: + $ref: '#/components/schemas/AnnotationQueueObjectType' + status: + $ref: '#/components/schemas/AnnotationQueueStatus' + completedAt: + type: string + format: date-time + nullable: true + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - queueId + - objectId + - objectType + - status + - createdAt + - updatedAt + PaginatedAnnotationQueues: + title: PaginatedAnnotationQueues + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/AnnotationQueue' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + PaginatedAnnotationQueueItems: + title: PaginatedAnnotationQueueItems + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/AnnotationQueueItem' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + CreateAnnotationQueueRequest: + title: CreateAnnotationQueueRequest + type: object + properties: + name: + type: string + description: + type: string + nullable: true + scoreConfigIds: + type: array + items: + type: string + required: + - name + - scoreConfigIds + CreateAnnotationQueueItemRequest: + title: CreateAnnotationQueueItemRequest + type: object + properties: + objectId: + type: string + objectType: + $ref: '#/components/schemas/AnnotationQueueObjectType' + status: + $ref: '#/components/schemas/AnnotationQueueStatus' + nullable: true + description: Defaults to PENDING for new queue items + required: + - objectId + - objectType + UpdateAnnotationQueueItemRequest: + title: UpdateAnnotationQueueItemRequest + type: object + properties: + status: + $ref: '#/components/schemas/AnnotationQueueStatus' + nullable: true + DeleteAnnotationQueueItemResponse: + title: DeleteAnnotationQueueItemResponse + type: object + properties: + success: + type: boolean + message: + type: string + required: + - success + - message + AnnotationQueueAssignmentRequest: + title: AnnotationQueueAssignmentRequest + type: object + properties: + userId: + type: string + required: + - userId + DeleteAnnotationQueueAssignmentResponse: + title: DeleteAnnotationQueueAssignmentResponse + type: object + properties: + success: + type: boolean + required: + - success + CreateAnnotationQueueAssignmentResponse: + title: CreateAnnotationQueueAssignmentResponse + type: object + properties: + userId: + type: string + queueId: + type: string + projectId: + type: string + required: + - userId + - queueId + - projectId + BlobStorageIntegrationType: + title: BlobStorageIntegrationType + type: string + enum: + - S3 + - S3_COMPATIBLE + - AZURE_BLOB_STORAGE + BlobStorageIntegrationFileType: + title: BlobStorageIntegrationFileType + type: string + enum: + - JSON + - CSV + - JSONL + - PARQUET + description: >- + File format for exported data. `PARQUET` is a columnar binary format + encoded and compressed by the storage engine; gzip compression does not + apply to it. Note that the model-price columns (`input_price`, + `output_price`, `total_price`) are not included in Parquet observation + exports. + BlobStorageIntegrationFileTypeResponse: + title: BlobStorageIntegrationFileTypeResponse + type: string + enum: + - JSON + - CSV + - JSONL + - PARQUET + description: File type reported for an existing integration. + BlobStorageExportMode: + title: BlobStorageExportMode + type: string + enum: + - FULL_HISTORY + - FROM_TODAY + - FROM_CUSTOM_DATE + BlobStorageExportFrequency: + title: BlobStorageExportFrequency + type: string + enum: + - every_20_minutes + - hourly + - daily + - weekly + BlobStorageExportSource: + title: BlobStorageExportSource + type: string + enum: + - LEGACY_TRACES_OBSERVATIONS + - OBSERVATIONS_V2 + - LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS + description: >- + What data the integration exports. + + - `LEGACY_TRACES_OBSERVATIONS`: traces, observations, and scores tables. + Observation columns are controlled by `exportFieldGroups`; field groups + without a counterpart in this data model (e.g. `trace_context`) are + omitted. + + - `OBSERVATIONS_V2`: same data model as the + `/api/public/v2/observations` endpoint, plus scores. Columns are + controlled by `exportFieldGroups`. + + - `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS`: both sets. Observation + columns of both portions are controlled by `exportFieldGroups`. + + + **Note:** `OBSERVATIONS_V2` and the enriched-observations portion of + `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` rely on the enriched + observations table (Langfuse Fast Preview / v4), which is currently + available on Langfuse Cloud only. See https://langfuse.com/docs/v4. + BlobStorageExportFieldGroup: + title: BlobStorageExportFieldGroup + type: string + enum: + - core + - basic + - time + - io + - metadata + - model + - usage + - prompt + - metrics + - tools + - trace_context + description: >- + Field group selecting which observation columns are included in the + export. Applies to all export sources; groups without a counterpart in + the legacy data model (e.g. `trace_context`) are omitted from the legacy + observations export. + CreateBlobStorageIntegrationRequest: + title: CreateBlobStorageIntegrationRequest + type: object + properties: + projectId: + type: string + description: ID of the project in which to configure the blob storage integration + type: + $ref: '#/components/schemas/BlobStorageIntegrationType' + bucketName: + type: string + description: >- + Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid + Azure container name (3-63 chars, lowercase letters, numbers, and + hyphens only, must start and end with a letter or number, no + consecutive hyphens). + endpoint: + type: string + nullable: true + description: Custom endpoint URL (required for S3_COMPATIBLE type) + region: + type: string + description: Storage region + accessKeyId: + type: string + nullable: true + description: Access key ID for authentication + secretAccessKey: + type: string + nullable: true + description: Secret access key for authentication (will be encrypted when stored) + prefix: + type: string + nullable: true + description: >- + Path prefix for exported files (must end with forward slash if + provided) + exportFrequency: + $ref: '#/components/schemas/BlobStorageExportFrequency' + enabled: + type: boolean + description: Whether the integration is active + forcePathStyle: + type: boolean + description: Use path-style URLs for S3 requests + fileType: + $ref: '#/components/schemas/BlobStorageIntegrationFileType' + exportMode: + $ref: '#/components/schemas/BlobStorageExportMode' + exportStartDate: + type: string + format: date-time + nullable: true + description: >- + Custom start date for exports (required when exportMode is + FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for + timezone differences). + compressed: + type: boolean + nullable: true + description: >- + Enable gzip compression for exported files (.csv.gz, .json.gz, + .jsonl.gz). Defaults to true. + exportSource: + $ref: '#/components/schemas/BlobStorageExportSource' + nullable: true + description: >- + Data to export. When omitted on update, the existing value is + preserved. When omitted on create: integrations on Langfuse Cloud + default to `OBSERVATIONS_V2`; self-hosted deployments fall back to + `LEGACY_TRACES_OBSERVATIONS`. Required when `exportFieldGroups` is + provided. + + + **Cloud-only project deprecation gate (effective 2026-05-20):** For + projects created on or after 2026-05-20 on Langfuse Cloud, + `LEGACY_TRACES_OBSERVATIONS` and + `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP + 400. Use `OBSERVATIONS_V2` for all new integrations. Self-hosted + deployments are unaffected. + + + **Cloud-only integration deprecation gate (effective 2026-06-22):** + On Langfuse Cloud, legacy export sources are only accepted for blob + storage integrations created before 2026-06-22, regardless of + project age. Requests that would create a new integration with + `LEGACY_TRACES_OBSERVATIONS` or + `LEGACY_TRACES_AND_ENRICHED_OBSERVATIONS` are rejected with HTTP + 400. Use `OBSERVATIONS_V2` instead. Self-hosted deployments are + unaffected. + exportFieldGroups: + type: array + items: + $ref: '#/components/schemas/BlobStorageExportFieldGroup' + nullable: true + description: >- + Field groups to include in each exported observation row. Applies to + all export sources; must include `core` if provided. When omitted on + create, the column default (all groups) applies. When omitted on + update, the existing value is preserved. + + + `exportFieldGroups` requires `exportSource` to be provided in the + same request. + required: + - projectId + - type + - bucketName + - region + - exportFrequency + - enabled + - forcePathStyle + - fileType + - exportMode + BlobStorageIntegrationResponse: + title: BlobStorageIntegrationResponse + type: object + properties: + id: + type: string + projectId: + type: string + type: + $ref: '#/components/schemas/BlobStorageIntegrationType' + bucketName: + type: string + endpoint: + type: string + nullable: true + region: + type: string + accessKeyId: + type: string + nullable: true + prefix: + type: string + exportFrequency: + $ref: '#/components/schemas/BlobStorageExportFrequency' + enabled: + type: boolean + forcePathStyle: + type: boolean + fileType: + $ref: '#/components/schemas/BlobStorageIntegrationFileTypeResponse' + exportMode: + $ref: '#/components/schemas/BlobStorageExportMode' + exportStartDate: + type: string + format: date-time + nullable: true + compressed: + type: boolean + exportSource: + $ref: '#/components/schemas/BlobStorageExportSource' + exportFieldGroups: + type: array + items: + $ref: '#/components/schemas/BlobStorageExportFieldGroup' + nullable: true + description: >- + Field groups included in each exported observation row. An empty + list is treated as all groups during export. + nextSyncAt: + type: string + format: date-time + nullable: true + lastSyncAt: + type: string + format: date-time + nullable: true + lastError: + type: string + nullable: true + lastErrorAt: + type: string + format: date-time + nullable: true + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - projectId + - type + - bucketName + - endpoint + - region + - accessKeyId + - prefix + - exportFrequency + - enabled + - forcePathStyle + - fileType + - exportMode + - exportStartDate + - compressed + - exportSource + - exportFieldGroups + - nextSyncAt + - lastSyncAt + - lastError + - lastErrorAt + - createdAt + - updatedAt + BlobStorageIntegrationsResponse: + title: BlobStorageIntegrationsResponse + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/BlobStorageIntegrationResponse' + required: + - data + BlobStorageSyncStatus: + title: BlobStorageSyncStatus + type: string + enum: + - idle + - running + - queued + - up_to_date + - disabled + - error + description: >- + Sync status of the blob storage integration: + + - `disabled` — integration is not enabled + + - `error` — last export failed (see `lastError` for details) + + - `running` — an export job is currently being processed + + - `queued` — next export is overdue (`nextSyncAt` is in the past) and + waiting to be picked up by the worker + + - `idle` — enabled but has never exported yet and no export is queued + + - `up_to_date` — all available data has been exported; next export is + scheduled for the future + + + **ETL usage**: poll this endpoint and check for `up_to_date` status. + Compare `lastSyncAt` against your + + ETL bookmark to determine if new data is available. Note that exports + run with a 20-minute lag buffer, + + so `lastSyncAt` will always be at least 20 minutes behind real-time. + BlobStorageIntegrationStatusResponse: + title: BlobStorageIntegrationStatusResponse + type: object + properties: + id: + type: string + projectId: + type: string + syncStatus: + $ref: '#/components/schemas/BlobStorageSyncStatus' + enabled: + type: boolean + lastSyncAt: + type: string + format: date-time + nullable: true + description: >- + End of the last successfully exported time window. Compare against + your ETL bookmark to determine if new data is available. Null if the + integration has never synced. + nextSyncAt: + type: string + format: date-time + nullable: true + description: When the next export is scheduled. Null if no sync has occurred yet. + lastError: + type: string + nullable: true + description: >- + Raw error message from the storage provider (S3/Azure/GCS) if the + last export failed. Cleared on successful export. + lastErrorAt: + type: string + format: date-time + nullable: true + description: When the last error occurred. Cleared on successful export. + required: + - id + - projectId + - syncStatus + - enabled + - lastSyncAt + - nextSyncAt + - lastError + - lastErrorAt + BlobStorageIntegrationDeletionResponse: + title: BlobStorageIntegrationDeletionResponse + type: object + properties: + message: + type: string + required: + - message + CreateCommentRequest: + title: CreateCommentRequest + type: object + properties: + projectId: + type: string + description: The id of the project to attach the comment to. + objectType: + type: string + description: >- + The type of the object to attach the comment to (trace, observation, + session, prompt). + objectId: + type: string + description: >- + The id of the object to attach the comment to. If this does not + reference a valid existing object, an error will be thrown. + content: + type: string + description: >- + The content of the comment. May include markdown. Currently limited + to 5000 characters. + authorUserId: + type: string + nullable: true + description: >- + The id of the user who created the comment. Must be a member of the + organization that owns the project, otherwise an error will be + thrown. + required: + - projectId + - objectType + - objectId + - content + CreateCommentResponse: + title: CreateCommentResponse + type: object + properties: + id: + type: string + description: The id of the created object in Langfuse + required: + - id + GetCommentsResponse: + title: GetCommentsResponse + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Comment' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + Deprecation: + title: Deprecation + type: object + description: >- + Migration signal returned by deprecated endpoints. Optional fields are + omitted when they have no value. + properties: + message: + type: string + description: >- + Human- and agent-readable summary of the deprecation and its + replacement. + replacement: + type: string + nullable: true + description: >- + The replacement endpoint, e.g. "GET /api/public/v2/observations". + Omitted when the endpoint is being removed without a direct + replacement. + docsUrl: + type: string + nullable: true + description: Link to the migration documentation (markdown), when available. + sunsetAt: + type: string + nullable: true + description: >- + ISO date after which the endpoint may stop working, when a removal + date is committed. + required: + - message + Trace: + title: Trace + type: object + properties: + id: + type: string + description: The unique identifier of a trace + timestamp: + type: string + format: date-time + description: The timestamp when the trace was created + name: + type: string + nullable: true + description: The name of the trace + input: + nullable: true + description: The input data of the trace. Can be any JSON. + output: + nullable: true + description: The output data of the trace. Can be any JSON. + sessionId: + type: string + nullable: true + description: The session identifier associated with the trace + release: + type: string + nullable: true + description: The release version of the application when the trace was created + version: + type: string + nullable: true + description: The version of the trace + userId: + type: string + nullable: true + description: The user identifier associated with the trace + metadata: + nullable: true + description: The metadata associated with the trace. Can be any JSON. + tags: + type: array + items: + type: string + description: The tags associated with the trace. + public: + type: boolean + description: Public traces are accessible via url without login + environment: + type: string + description: >- + The environment from which this trace originated. Can be any + lowercase alphanumeric string with hyphens and underscores that does + not start with 'langfuse'. + required: + - id + - timestamp + - name + - sessionId + - release + - version + - userId + - tags + - public + - environment + TraceWithDetails: + title: TraceWithDetails + type: object + properties: + htmlPath: + type: string + description: Path of trace in Langfuse UI + latency: + type: number + format: double + nullable: true + description: Latency of trace in seconds + totalCost: + type: number + format: double + nullable: true + description: Cost of trace in USD + observations: + type: array + items: + type: string + nullable: true + description: List of observation ids + scores: + type: array + items: + type: string + nullable: true + description: List of score ids + required: + - htmlPath + allOf: + - $ref: '#/components/schemas/Trace' + TraceWithFullDetails: + title: TraceWithFullDetails + type: object + properties: + htmlPath: + type: string + description: Path of trace in Langfuse UI + latency: + type: number + format: double + nullable: true + description: Latency of trace in seconds + totalCost: + type: number + format: double + nullable: true + description: Cost of trace in USD + observations: + type: array + items: + $ref: '#/components/schemas/ObservationsView' + description: List of observations + scores: + type: array + items: + $ref: '#/components/schemas/ScoreV1' + description: List of scores + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - htmlPath + - observations + - scores + allOf: + - $ref: '#/components/schemas/Trace' + Session: + title: Session + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + projectId: + type: string + environment: + type: string + description: The environment from which this session originated. + required: + - id + - createdAt + - projectId + - environment + SessionWithTraces: + title: SessionWithTraces + type: object + properties: + traces: + type: array + items: + $ref: '#/components/schemas/Trace' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - traces + allOf: + - $ref: '#/components/schemas/Session' + Observation: + title: Observation + type: object + properties: + id: + type: string + description: The unique identifier of the observation + traceId: + type: string + nullable: true + description: The trace ID associated with the observation + type: + type: string + description: The type of the observation + name: + type: string + nullable: true + description: The name of the observation + startTime: + type: string + format: date-time + description: The start time of the observation + endTime: + type: string + format: date-time + nullable: true + description: The end time of the observation. + completionStartTime: + type: string + format: date-time + nullable: true + description: The completion start time of the observation + model: + type: string + nullable: true + description: The model used for the observation + modelParameters: + description: The parameters of the model used for the observation + input: + description: The input data of the observation + version: + type: string + nullable: true + description: The version of the observation + metadata: + description: Additional metadata of the observation + output: + description: The output data of the observation + usage: + $ref: '#/components/schemas/Usage' + description: >- + (Deprecated. Use usageDetails and costDetails instead.) The usage + data of the observation + level: + $ref: '#/components/schemas/ObservationLevel' + description: The level of the observation + statusMessage: + type: string + nullable: true + description: The status message of the observation + parentObservationId: + type: string + nullable: true + description: The parent observation ID + promptId: + type: string + nullable: true + description: The prompt ID associated with the observation + usageDetails: + type: object + additionalProperties: + type: integer + description: >- + The usage details of the observation. Key is the name of the usage + metric, value is the number of units consumed. The total key is the + sum of all (non-total) usage metrics or the total value ingested. + costDetails: + type: object + additionalProperties: + type: number + format: double + description: >- + The cost details of the observation. Key is the name of the cost + metric, value is the cost in USD. The total key is the sum of all + (non-total) cost metrics or the total value ingested. + environment: + type: string + description: >- + The environment from which this observation originated. Can be any + lowercase alphanumeric string with hyphens and underscores that does + not start with 'langfuse'. + required: + - id + - traceId + - type + - name + - startTime + - endTime + - completionStartTime + - model + - modelParameters + - input + - version + - metadata + - output + - usage + - level + - statusMessage + - parentObservationId + - promptId + - usageDetails + - costDetails + - environment + ObservationsView: + title: ObservationsView + type: object + properties: + promptName: + type: string + nullable: true + description: The name of the prompt associated with the observation + promptVersion: + type: integer + nullable: true + description: The version of the prompt associated with the observation + modelId: + type: string + nullable: true + description: The unique identifier of the model + inputPrice: + type: number + format: double + nullable: true + description: The price of the input in USD + outputPrice: + type: number + format: double + nullable: true + description: The price of the output in USD. + totalPrice: + type: number + format: double + nullable: true + description: The total price in USD. + calculatedInputCost: + type: number + format: double + nullable: true + description: >- + (Deprecated. Use usageDetails and costDetails instead.) The + calculated cost of the input in USD + calculatedOutputCost: + type: number + format: double + nullable: true + description: >- + (Deprecated. Use usageDetails and costDetails instead.) The + calculated cost of the output in USD + calculatedTotalCost: + type: number + format: double + nullable: true + description: >- + (Deprecated. Use usageDetails and costDetails instead.) The + calculated total cost in USD + latency: + type: number + format: double + nullable: true + description: The latency in seconds. + timeToFirstToken: + type: number + format: double + nullable: true + description: The time to the first token in seconds + required: + - promptName + - promptVersion + - modelId + - inputPrice + - outputPrice + - totalPrice + - calculatedInputCost + - calculatedOutputCost + - calculatedTotalCost + - latency + - timeToFirstToken + allOf: + - $ref: '#/components/schemas/Observation' + ObservationsViewSingle: + title: ObservationsViewSingle + type: object + properties: + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + allOf: + - $ref: '#/components/schemas/ObservationsView' + ObservationV2: + title: ObservationV2 + type: object + description: >- + An observation from the v2 API with field-group-based selection. + + Core fields are always present. Other fields are included only when + their field group is requested. + properties: + id: + type: string + description: The unique identifier of the observation + traceId: + type: string + nullable: true + description: The trace ID associated with the observation + startTime: + type: string + format: date-time + description: The start time of the observation + endTime: + type: string + format: date-time + nullable: true + description: The end time of the observation + projectId: + type: string + description: The project ID this observation belongs to + parentObservationId: + type: string + nullable: true + description: >- + The physical parent observation ID, if present. + + Observations marked as app roots by the SDK may retain a non-null + parent ID. + type: + type: string + description: The type of the observation (e.g. GENERATION, SPAN, EVENT) + isRootObservation: + type: boolean + nullable: true + description: >- + Whether this observation is a logical root. + + This is true for observations without a physical parent and + observations marked as app roots by the SDK. + name: + type: string + nullable: true + description: The name of the observation + level: + $ref: '#/components/schemas/ObservationLevel' + nullable: true + description: The level of the observation + statusMessage: + type: string + nullable: true + description: The status message of the observation + version: + type: string + nullable: true + description: The version of the observation + environment: + type: string + nullable: true + description: The environment from which this observation originated + bookmarked: + type: boolean + nullable: true + description: Whether the observation is bookmarked + public: + type: boolean + nullable: true + description: Whether the observation is public + userId: + type: string + nullable: true + description: The user ID associated with the observation + sessionId: + type: string + nullable: true + description: The session ID associated with the observation + completionStartTime: + type: string + format: date-time + nullable: true + description: The completion start time of the observation + createdAt: + type: string + format: date-time + nullable: true + description: The creation timestamp of the observation + updatedAt: + type: string + format: date-time + nullable: true + description: The last update timestamp of the observation + input: + nullable: true + description: The input data of the observation + output: + nullable: true + description: The output data of the observation + metadata: + nullable: true + description: Additional metadata of the observation + providedModelName: + type: string + nullable: true + description: The model name as provided by the user + internalModelId: + type: string + nullable: true + description: The internal model ID matched by Langfuse + modelParameters: + nullable: true + description: The parameters of the model used for the observation + usageDetails: + type: object + additionalProperties: + type: integer + nullable: true + description: >- + The usage details of the observation. Key is the usage metric name, + value is the number of units consumed. + costDetails: + type: object + additionalProperties: + type: number + format: double + nullable: true + description: >- + The cost details of the observation. Key is the cost metric name, + value is the cost in USD. + totalCost: + type: number + format: double + nullable: true + description: The total cost of the observation in USD + usagePricingTierName: + type: string + nullable: true + description: >- + The name of the pricing tier applied to this observation's usage + costs + promptId: + type: string + nullable: true + description: The prompt ID associated with the observation + promptName: + type: string + nullable: true + description: The prompt name associated with the observation + promptVersion: + type: integer + nullable: true + description: The prompt version associated with the observation + latency: + type: number + format: double + nullable: true + description: The latency in seconds + timeToFirstToken: + type: number + format: double + nullable: true + description: The time to first token in seconds + modelId: + type: string + nullable: true + description: >- + The matched model ID. Null when the `model` field group is not + requested. + inputPrice: + type: string + nullable: true + description: >- + The input token price (USD per unit) from the matched model, + serialized as a decimal string (e.g. "0.0001"). Null when the + `model` field group is not requested. + outputPrice: + type: string + nullable: true + description: >- + The output token price (USD per unit) from the matched model, + serialized as a decimal string (e.g. "0.0001"). Null when the + `model` field group is not requested. + totalPrice: + type: string + nullable: true + description: >- + The total token price (USD per unit) from the matched model, + serialized as a decimal string (e.g. "0.0001"). Null when the + `model` field group is not requested. + traceName: + type: string + nullable: true + description: The name of the parent trace + tags: + type: array + items: + type: string + nullable: true + description: Tags from the parent trace (denormalized onto the observation) + release: + type: string + nullable: true + description: The release version of the parent trace + required: + - id + - traceId + - startTime + - endTime + - projectId + - parentObservationId + - type + - modelId + - inputPrice + - outputPrice + - totalPrice + Usage: + title: Usage + type: object + description: >- + (Deprecated. Use usageDetails and costDetails instead.) Standard + interface for usage and cost + properties: + input: + type: integer + description: Number of input units (e.g. tokens) + output: + type: integer + description: Number of output units (e.g. tokens) + total: + type: integer + description: Defaults to input+output if not set + unit: + type: string + nullable: true + description: Unit of measurement + inputCost: + type: number + format: double + nullable: true + description: USD input cost + outputCost: + type: number + format: double + nullable: true + description: USD output cost + totalCost: + type: number + format: double + nullable: true + description: USD total cost, defaults to input+output + required: + - input + - output + - total + - unit + ScoreConfig: + title: ScoreConfig + type: object + description: Configuration for a score + properties: + id: + type: string + name: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + projectId: + type: string + dataType: + $ref: '#/components/schemas/ScoreConfigDataType' + isArchived: + type: boolean + description: Whether the score config is archived. Defaults to false + minValue: + type: number + format: double + nullable: true + description: >- + Sets minimum value for numerical scores. If not set, the minimum + value defaults to -∞ + maxValue: + type: number + format: double + nullable: true + description: >- + Sets maximum value for numerical scores. If not set, the maximum + value defaults to +∞ + categories: + type: array + items: + $ref: '#/components/schemas/ConfigCategory' + nullable: true + description: Configures custom categories for categorical scores + description: + type: string + nullable: true + description: Description of the score config + required: + - id + - name + - createdAt + - updatedAt + - projectId + - dataType + - isArchived + ConfigCategory: + title: ConfigCategory + type: object + properties: + value: + type: number + format: double + label: + type: string + required: + - value + - label + BaseScoreV1: + title: BaseScoreV1 + type: object + properties: + id: + type: string + traceId: + type: string + name: + type: string + source: + $ref: '#/components/schemas/ScoreSource' + observationId: + type: string + nullable: true + description: The observation ID associated with the score + timestamp: + type: string + format: date-time + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + authorUserId: + type: string + nullable: true + description: The user ID of the author + comment: + type: string + nullable: true + description: Comment on the score + metadata: + description: Metadata associated with the score + configId: + type: string + nullable: true + description: >- + Reference a score config on a score. When set, config and score name + must be equal and value must comply to optionally defined numerical + range + queueId: + type: string + nullable: true + description: >- + The annotation queue referenced by the score. Indicates if score was + initially created while processing annotation queue. + environment: + type: string + description: >- + The environment from which this score originated. Can be any + lowercase alphanumeric string with hyphens and underscores that does + not start with 'langfuse'. + required: + - id + - traceId + - name + - source + - timestamp + - createdAt + - updatedAt + - authorUserId + - comment + - metadata + - configId + - queueId + - environment + NumericScoreV1: + title: NumericScoreV1 + type: object + properties: + value: + type: number + format: double + description: The numeric value of the score + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScoreV1' + BooleanScoreV1: + title: BooleanScoreV1 + type: object + properties: + value: + type: number + format: double + description: >- + The numeric value of the score. Equals 1 for "True" and 0 for + "False" + stringValue: + type: string + description: >- + The string representation of the score value. Is inferred from the + numeric value and equals "True" or "False" + required: + - value + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScoreV1' + CategoricalScoreV1: + title: CategoricalScoreV1 + type: object + properties: + value: + type: number + format: double + description: >- + Represents the numeric category mapping of the stringValue. If no + config is linked, defaults to 0. + stringValue: + type: string + description: >- + The string representation of the score value. If no config is + linked, can be any string. Otherwise, must map to a config category + required: + - value + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScoreV1' + TextScoreV1: + title: TextScoreV1 + type: object + properties: + stringValue: + type: string + description: The text content of the score (1-500 characters) + required: + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScoreV1' + ScoreV1: + title: ScoreV1 + oneOf: + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - NUMERIC + - $ref: '#/components/schemas/NumericScoreV1' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CATEGORICAL + - $ref: '#/components/schemas/CategoricalScoreV1' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - BOOLEAN + - $ref: '#/components/schemas/BooleanScoreV1' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - TEXT + - $ref: '#/components/schemas/TextScoreV1' + required: + - dataType + BaseScore: + title: BaseScore + type: object + properties: + id: + type: string + traceId: + type: string + nullable: true + description: The trace ID associated with the score + sessionId: + type: string + nullable: true + description: The session ID associated with the score + observationId: + type: string + nullable: true + description: The observation ID associated with the score + datasetRunId: + type: string + nullable: true + description: The dataset run ID associated with the score + name: + type: string + source: + $ref: '#/components/schemas/ScoreSource' + timestamp: + type: string + format: date-time + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + authorUserId: + type: string + nullable: true + description: The user ID of the author + comment: + type: string + nullable: true + description: Comment on the score + metadata: + description: Metadata associated with the score + configId: + type: string + nullable: true + description: >- + Reference a score config on a score. When set, config and score name + must be equal and value must comply to optionally defined numerical + range + queueId: + type: string + nullable: true + description: >- + The annotation queue referenced by the score. Indicates if score was + initially created while processing annotation queue. + environment: + type: string + description: >- + The environment from which this score originated. Can be any + lowercase alphanumeric string with hyphens and underscores that does + not start with 'langfuse'. + required: + - id + - name + - source + - timestamp + - createdAt + - updatedAt + - authorUserId + - comment + - metadata + - configId + - queueId + - environment + NumericScore: + title: NumericScore + type: object + properties: + value: + type: number + format: double + description: The numeric value of the score + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScore' + BooleanScore: + title: BooleanScore + type: object + properties: + value: + type: number + format: double + description: >- + The numeric value of the score. Equals 1 for "True" and 0 for + "False" + stringValue: + type: string + description: >- + The string representation of the score value. Is inferred from the + numeric value and equals "True" or "False" + required: + - value + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScore' + CategoricalScore: + title: CategoricalScore + type: object + properties: + value: + type: number + format: double + description: >- + Represents the numeric category mapping of the stringValue. If no + config is linked, defaults to 0. + stringValue: + type: string + description: >- + The string representation of the score value. If no config is + linked, can be any string. Otherwise, must map to a config category + required: + - value + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScore' + CorrectionScore: + title: CorrectionScore + type: object + properties: + value: + type: number + format: double + description: The numeric value of the score. Always 0 for correction scores. + stringValue: + type: string + description: The string representation of the correction content + required: + - value + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScore' + TextScore: + title: TextScore + type: object + properties: + stringValue: + type: string + description: The text content of the score (1-500 characters) + required: + - stringValue + allOf: + - $ref: '#/components/schemas/BaseScore' + Score: + title: Score + oneOf: + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - NUMERIC + - $ref: '#/components/schemas/NumericScore' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CATEGORICAL + - $ref: '#/components/schemas/CategoricalScore' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - BOOLEAN + - $ref: '#/components/schemas/BooleanScore' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CORRECTION + - $ref: '#/components/schemas/CorrectionScore' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - TEXT + - $ref: '#/components/schemas/TextScore' + required: + - dataType + properties: + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + CreateScoreValue: + title: CreateScoreValue + oneOf: + - type: number + format: double + - type: string + description: >- + The value of the score. Must be passed as string for categorical and + text scores, and numeric for boolean and numeric scores + Comment: + title: Comment + type: object + properties: + id: + type: string + projectId: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + objectType: + $ref: '#/components/schemas/CommentObjectType' + objectId: + type: string + content: + type: string + authorUserId: + type: string + nullable: true + description: The user ID of the comment author + required: + - id + - projectId + - createdAt + - updatedAt + - objectType + - objectId + - content + Dataset: + title: Dataset + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + nullable: true + description: Description of the dataset + metadata: + description: Metadata associated with the dataset + inputSchema: + nullable: true + description: JSON Schema for validating dataset item inputs + expectedOutputSchema: + nullable: true + description: JSON Schema for validating dataset item expected outputs + projectId: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - name + - description + - metadata + - inputSchema + - expectedOutputSchema + - projectId + - createdAt + - updatedAt + DatasetItem: + title: DatasetItem + type: object + properties: + id: + type: string + status: + $ref: '#/components/schemas/DatasetStatus' + input: + description: Input data for the dataset item + expectedOutput: + description: Expected output for the dataset item + metadata: + description: Metadata associated with the dataset item + sourceTraceId: + type: string + nullable: true + description: The trace ID that sourced this dataset item + sourceObservationId: + type: string + nullable: true + description: The observation ID that sourced this dataset item + datasetId: + type: string + datasetName: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + mediaReferences: + type: array + items: + $ref: '#/components/schemas/DatasetItemMediaReference' + description: >- + Resolved Langfuse media references found in input, expectedOutput, + and metadata. + required: + - id + - status + - input + - expectedOutput + - metadata + - sourceTraceId + - sourceObservationId + - datasetId + - datasetName + - createdAt + - updatedAt + - mediaReferences + DatasetItemMediaReference: + title: DatasetItemMediaReference + type: object + properties: + field: + $ref: '#/components/schemas/DatasetItemMediaReferenceField' + description: The dataset item field containing the reference + referenceString: + type: string + description: >- + The Langfuse media reference string, e.g. + `@@@langfuseMedia:type=image/png|id=...|source=bytes@@@` + jsonPath: + type: string + description: >- + JSONPath of the string holding the reference within the field, e.g. + `$['image']` + media: + $ref: '#/components/schemas/DatasetItemMediaReferenceMedia' + description: The resolved media record. + required: + - field + - referenceString + - jsonPath + - media + DatasetItemMediaReferenceField: + title: DatasetItemMediaReferenceField + type: string + enum: + - input + - expectedOutput + - metadata + DatasetItemMediaReferenceMedia: + title: DatasetItemMediaReferenceMedia + type: object + properties: + mediaId: + type: string + description: The unique langfuse identifier of the media record + contentType: + type: string + description: The MIME type of the media record + contentLength: + type: integer + description: The size of the media record in bytes + url: + type: string + description: The signed download URL of the media record + urlExpiry: + type: string + description: The expiry date and time of the download URL + required: + - mediaId + - contentType + - contentLength + - url + - urlExpiry + DatasetRunItem: + title: DatasetRunItem + type: object + properties: + id: + type: string + datasetRunId: + type: string + datasetRunName: + type: string + datasetItemId: + type: string + traceId: + type: string + observationId: + type: string + nullable: true + description: The observation ID associated with this run item + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - datasetRunId + - datasetRunName + - datasetItemId + - traceId + - observationId + - createdAt + - updatedAt + DatasetRun: + title: DatasetRun + type: object + properties: + id: + type: string + description: Unique identifier of the dataset run + name: + type: string + description: Name of the dataset run + description: + type: string + nullable: true + description: Description of the run + metadata: + description: Metadata of the dataset run + datasetId: + type: string + description: Id of the associated dataset + datasetName: + type: string + description: Name of the associated dataset + createdAt: + type: string + format: date-time + description: The date and time when the dataset run was created + updatedAt: + type: string + format: date-time + description: The date and time when the dataset run was last updated + required: + - id + - name + - description + - metadata + - datasetId + - datasetName + - createdAt + - updatedAt + DatasetRunWithItems: + title: DatasetRunWithItems + type: object + properties: + datasetRunItems: + type: array + items: + $ref: '#/components/schemas/DatasetRunItem' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - datasetRunItems + allOf: + - $ref: '#/components/schemas/DatasetRun' + Model: + title: Model + type: object + description: >- + Model definition used for transforming usage into USD cost and/or + tokenization. + + + Models can have either simple flat pricing or tiered pricing: + + - Flat pricing: Single price per usage type (legacy, but still + supported) + + - Tiered pricing: Multiple pricing tiers with conditional matching based + on usage patterns + + + The pricing tiers approach is recommended for models with usage-based + pricing variations. + + When using tiered pricing, the flat price fields (inputPrice, + outputPrice, prices) are populated + + from the default tier for backward compatibility. + properties: + id: + type: string + modelName: + type: string + description: >- + Name of the model definition. If multiple with the same name exist, + they are applied in the following order: (1) custom over built-in, + (2) newest according to startTime where + model.startTime- + Regex pattern which matches this model definition to + generation.model. Useful in case of fine-tuned models. If you want + to exact match, use `(?i)^modelname$` + startDate: + type: string + format: date-time + nullable: true + description: Apply only to generations which are newer than this ISO date. + unit: + $ref: '#/components/schemas/ModelUsageUnit' + nullable: true + description: Unit used by this model. + inputPrice: + type: number + format: double + nullable: true + description: Deprecated. See 'prices' instead. Price (USD) per input unit + outputPrice: + type: number + format: double + nullable: true + description: Deprecated. See 'prices' instead. Price (USD) per output unit + totalPrice: + type: number + format: double + nullable: true + description: >- + Deprecated. See 'prices' instead. Price (USD) per total unit. Cannot + be set if input or output price is set. + tokenizerId: + type: string + nullable: true + description: >- + Optional. Tokenizer to be applied to observations which match to + this model. See docs for more details. + tokenizerConfig: + description: >- + Optional. Configuration for the selected tokenizer. Needs to be + JSON. See docs for more details. + isLangfuseManaged: + type: boolean + createdAt: + type: string + format: date-time + description: Timestamp when the model was created + prices: + type: object + additionalProperties: + $ref: '#/components/schemas/ModelPrice' + description: >- + Deprecated. Use 'pricingTiers' instead for models with usage-based + pricing variations. + + + This field shows prices by usage type from the default pricing tier. + Maintained for backward compatibility. + + If the model uses tiered pricing, this field will be populated from + the default tier's prices. + pricingTiers: + type: array + items: + $ref: '#/components/schemas/PricingTier' + description: >- + Array of pricing tiers with conditional pricing based on usage + thresholds. + + + Pricing tiers enable accurate cost tracking for models that charge + different rates based on usage patterns + + (e.g., different rates for high-volume usage, large context windows, + or cached tokens). + + + Each model must have exactly one default tier (isDefault=true, + priority=0) that serves as a fallback. + + Additional conditional tiers can be defined with specific matching + criteria. + + + If this array is empty, the model uses legacy flat pricing from the + inputPrice/outputPrice/totalPrice fields. + required: + - id + - modelName + - matchPattern + - startDate + - inputPrice + - outputPrice + - totalPrice + - tokenizerId + - tokenizerConfig + - isLangfuseManaged + - createdAt + - prices + - pricingTiers + ModelPrice: + title: ModelPrice + type: object + properties: + price: + type: number + format: double + required: + - price + PricingTierCondition: + title: PricingTierCondition + type: object + description: >- + Condition for matching a pricing tier based on usage details. Used to + implement tiered pricing models where costs vary based on usage + thresholds. + + + How it works: + + 1. The regex pattern matches against usage detail keys (e.g., + "input_tokens", "input_cached") + + 2. Values of all matching keys are summed together + + 3. The sum is compared against the threshold value using the specified + operator + + 4. All conditions in a tier must be met (AND logic) for the tier to + match + + + Common use cases: + + - Threshold-based pricing: Match when accumulated usage exceeds a + certain amount + + - Usage-type-specific pricing: Different rates for cached vs non-cached + tokens, or input vs output + + - Volume-based pricing: Different rates based on total request or token + count + properties: + usageDetailPattern: + type: string + description: >- + Regex pattern to match against usage detail keys. All matching keys' + values are summed for threshold comparison. + + + Examples: + + - "^input" matches "input", "input_tokens", "input_cached", etc. + + - "^(input|prompt)" matches both "input_tokens" and "prompt_tokens" + + - "_cache$" matches "input_cache", "output_cache", etc. + + + The pattern is case-insensitive by default. If no keys match, the + sum is treated as zero. + operator: + $ref: '#/components/schemas/PricingTierOperator' + description: >- + Comparison operator to apply between the summed value and the + threshold. + + + - gt: greater than (sum > threshold) + + - gte: greater than or equal (sum >= threshold) + + - lt: less than (sum < threshold) + + - lte: less than or equal (sum <= threshold) + + - eq: equal (sum == threshold) + + - neq: not equal (sum != threshold) + value: + type: number + format: double + description: >- + Threshold value for comparison. For token-based pricing, this is + typically the token count threshold (e.g., 200000 for a 200K token + threshold). + caseSensitive: + type: boolean + description: >- + Whether the regex pattern matching is case-sensitive. Default is + false (case-insensitive matching). + required: + - usageDetailPattern + - operator + - value + - caseSensitive + PricingTier: + title: PricingTier + type: object + description: >- + Pricing tier definition with conditional pricing based on usage + thresholds. + + + Pricing tiers enable accurate cost tracking for LLM providers that + charge different rates based on usage patterns. + + For example, some providers charge higher rates when context size + exceeds certain thresholds. + + + How tier matching works: + + 1. Tiers are evaluated in ascending priority order (priority 1 before + priority 2, etc.) + + 2. The first tier where ALL conditions match is selected + + 3. If no conditional tiers match, the default tier is used as a fallback + + 4. The default tier has priority 0 and no conditions + + + Why priorities matter: + + - Lower priority numbers are evaluated first, allowing you to define + specific cases before general ones + + - Example: Priority 1 for "high usage" (>200K tokens), Priority 2 for + "medium usage" (>100K tokens), Priority 0 for default + + - Without proper ordering, a less specific condition might match before + a more specific one + + + Every model must have exactly one default tier to ensure cost + calculation always succeeds. + properties: + id: + type: string + description: Unique identifier for the pricing tier + name: + type: string + description: >- + Name of the pricing tier for display and identification purposes. + + + Examples: "Standard", "High Volume Tier", "Large Context", "Extended + Context Tier" + isDefault: + type: boolean + description: >- + Whether this is the default tier. Every model must have exactly one + default tier with priority 0 and no conditions. + + + The default tier serves as a fallback when no conditional tiers + match, ensuring cost calculation always succeeds. + + It typically represents the base pricing for standard usage + patterns. + priority: + type: integer + description: >- + Priority for tier matching evaluation. Lower numbers = higher + priority (evaluated first). + + + The default tier must always have priority 0. Conditional tiers + should have priority 1, 2, 3, etc. + + + Example ordering: + + - Priority 0: Default tier (no conditions, always matches as + fallback) + + - Priority 1: High usage tier (e.g., >200K tokens) + + - Priority 2: Medium usage tier (e.g., >100K tokens) + + + This ensures more specific conditions are checked before general + ones. + conditions: + type: array + items: + $ref: '#/components/schemas/PricingTierCondition' + description: >- + Array of conditions that must ALL be met for this tier to match (AND + logic). + + + The default tier must have an empty conditions array. Conditional + tiers should have one or more conditions + + that define when this tier's pricing applies. + + + Multiple conditions enable complex matching scenarios (e.g., "high + input tokens AND low output tokens"). + prices: + type: object + additionalProperties: + type: number + format: double + description: >- + Prices (USD) by usage type for this tier. + + + Common usage types: "input", "output", "total", "request", "image" + + Prices are specified in USD per unit (e.g., per token, per request, + per second). + + + Example: {"input": 0.000003, "output": 0.000015} means $3 per + million input tokens and $15 per million output tokens. + required: + - id + - name + - isDefault + - priority + - conditions + - prices + PricingTierInput: + title: PricingTierInput + type: object + description: >- + Input schema for creating a pricing tier. The tier ID will be + automatically generated server-side. + + + When creating a model with pricing tiers: + + - Exactly one tier must have isDefault=true (the fallback tier) + + - The default tier must have priority=0 and conditions=[] + + - All tier names and priorities must be unique within the model + + - Each tier must define at least one price + + + See PricingTier for detailed information about how tiers work and why + they're useful. + properties: + name: + type: string + description: >- + Name of the pricing tier for display and identification purposes. + + + Must be unique within the model. Common patterns: "Standard", "High + Volume Tier", "Extended Context" + isDefault: + type: boolean + description: >- + Whether this is the default tier. Exactly one tier per model must be + marked as default. + + + Requirements for default tier: + + - Must have isDefault=true + + - Must have priority=0 + + - Must have empty conditions array (conditions=[]) + + + The default tier acts as a fallback when no conditional tiers match. + priority: + type: integer + description: >- + Priority for tier matching evaluation. Lower numbers = higher + priority (evaluated first). + + + Must be unique within the model. The default tier must have + priority=0. + + Conditional tiers should use priority 1, 2, 3, etc. based on their + specificity. + conditions: + type: array + items: + $ref: '#/components/schemas/PricingTierCondition' + description: >- + Array of conditions that must ALL be met for this tier to match (AND + logic). + + + The default tier must have an empty array (conditions=[]). + + Conditional tiers should define one or more conditions that specify + when this tier's pricing applies. + + + Each condition specifies a regex pattern, operator, and threshold + value for matching against usage details. + prices: + type: object + additionalProperties: + type: number + format: double + description: >- + Prices (USD) by usage type for this tier. At least one price must be + defined. + + + Common usage types: "input", "output", "total", "request", "image" + + Prices are in USD per unit (e.g., per token). + + + Example: {"input": 0.000003, "output": 0.000015} represents $3 per + million input tokens and $15 per million output tokens. + required: + - name + - isDefault + - priority + - conditions + - prices + PricingTierOperator: + title: PricingTierOperator + type: string + enum: + - gt + - gte + - lt + - lte + - eq + - neq + description: Comparison operators for pricing tier conditions + ModelUsageUnit: + title: ModelUsageUnit + type: string + enum: + - CHARACTERS + - TOKENS + - MILLISECONDS + - SECONDS + - IMAGES + - REQUESTS + description: Unit of usage in Langfuse + ObservationLevel: + title: ObservationLevel + type: string + enum: + - DEBUG + - DEFAULT + - WARNING + - ERROR + MapValue: + title: MapValue + oneOf: + - type: string + nullable: true + - type: integer + nullable: true + - type: number + format: float + nullable: true + - type: boolean + nullable: true + - type: array + items: + type: string + nullable: true + CommentObjectType: + title: CommentObjectType + type: string + enum: + - TRACE + - OBSERVATION + - SESSION + - PROMPT + DatasetStatus: + title: DatasetStatus + type: string + enum: + - ACTIVE + - ARCHIVED + ScoreSource: + title: ScoreSource + type: string + enum: + - ANNOTATION + - API + - EVAL + ScoreConfigDataType: + title: ScoreConfigDataType + type: string + enum: + - NUMERIC + - BOOLEAN + - CATEGORICAL + - TEXT + ScoreDataType: + title: ScoreDataType + type: string + enum: + - NUMERIC + - BOOLEAN + - CATEGORICAL + - CORRECTION + - TEXT + DeleteDatasetItemResponse: + title: DeleteDatasetItemResponse + type: object + properties: + message: + type: string + description: Success message after deletion + required: + - message + CreateDatasetItemRequest: + title: CreateDatasetItemRequest + type: object + properties: + datasetName: + type: string + input: + nullable: true + expectedOutput: + nullable: true + metadata: + nullable: true + sourceTraceId: + type: string + nullable: true + sourceObservationId: + type: string + nullable: true + id: + type: string + nullable: true + description: >- + Dataset items are upserted on their id. Id needs to be unique + (project-level), cannot be reused across datasets, and must be at + most 255 characters. + status: + $ref: '#/components/schemas/DatasetStatus' + nullable: true + description: Defaults to ACTIVE for newly created items + required: + - datasetName + PaginatedDatasetItems: + title: PaginatedDatasetItems + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/DatasetItem' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + CreateDatasetRunItemRequest: + title: CreateDatasetRunItemRequest + type: object + properties: + runName: + type: string + runDescription: + type: string + nullable: true + description: Description of the run. If run exists, description will be updated. + metadata: + nullable: true + description: Metadata of the dataset run, updates run if run already exists + datasetItemId: + type: string + observationId: + type: string + nullable: true + traceId: + type: string + nullable: true + description: >- + traceId should always be provided. For compatibility with older SDK + versions it can also be inferred from the provided observationId. + datasetVersion: + type: string + format: date-time + nullable: true + description: >- + ISO 8601 timestamp (RFC 3339, Section 5.6) in UTC (e.g., + "2026-01-21T14:35:42Z"). + + Specifies the dataset version to use for this experiment run. + + If provided, the experiment will use dataset items as they existed + at or before this timestamp. + + If not provided, uses the latest version of dataset items. + createdAt: + type: string + format: date-time + nullable: true + description: >- + Optional timestamp to set the createdAt field of the dataset run + item. If not provided or null, defaults to current timestamp. + required: + - runName + - datasetItemId + PaginatedDatasetRunItems: + title: PaginatedDatasetRunItems + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/DatasetRunItem' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + - meta + PaginatedDatasets: + title: PaginatedDatasets + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Dataset' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + CreateDatasetRequest: + title: CreateDatasetRequest + type: object + properties: + name: + type: string + description: + type: string + nullable: true + metadata: + nullable: true + inputSchema: + nullable: true + description: >- + JSON Schema for validating dataset item inputs. When set, all new + and existing dataset items will be validated against this schema. + expectedOutputSchema: + nullable: true + description: >- + JSON Schema for validating dataset item expected outputs. When set, + all new and existing dataset items will be validated against this + schema. + required: + - name + PaginatedDatasetRuns: + title: PaginatedDatasetRuns + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/DatasetRun' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + - meta + DeleteDatasetRunResponse: + title: DeleteDatasetRunResponse + type: object + properties: + message: + type: string + required: + - message + ExperimentsResponse: + title: ExperimentsResponse + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Experiment' + meta: + $ref: '#/components/schemas/ExperimentsResponseMeta' + required: + - data + - meta + ExperimentsResponseMeta: + title: ExperimentsResponseMeta + type: object + properties: + cursor: + type: string + nullable: true + description: >- + Versioned base64url cursor for retrieving the next page. Absent when + there are no more results. + Experiment: + title: Experiment + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + nullable: true + startTime: + type: string + format: date-time + description: |- + Start of the experiment, i.e. the earliest event within the + requested time range. Clipped to `fromStartTime` when the + experiment started before the requested range. + endTime: + type: string + format: date-time + description: |- + End of the experiment, i.e. the latest event end within the + requested time range. + itemCount: + type: integer + description: Number of experiment items within the requested time range. + datasetId: + type: string + nullable: true + description: Null when the experiment is not associated with a dataset. + metadata: + type: object + additionalProperties: true + nullable: true + description: Included only when `fields=metadata` is requested. + scores: + type: array + items: + $ref: '#/components/schemas/ScoreV3' + nullable: true + description: >- + Included only when `fields=scores` is requested. Contains scores + directly attached to the experiment. + required: + - id + - name + - description + - startTime + - endTime + - itemCount + - datasetId + ExperimentItemsResponse: + title: ExperimentItemsResponse + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ExperimentItem' + meta: + $ref: '#/components/schemas/ExperimentsResponseMeta' + required: + - data + - meta + ExperimentItem: + title: ExperimentItem + type: object + properties: + id: + type: string + traceId: + type: string + startTime: + type: string + format: date-time + endTime: + type: string + format: date-time + nullable: true + level: + $ref: '#/components/schemas/ObservationLevel' + environment: + type: string + experimentId: + type: string + experimentName: + type: string + experimentItemId: + type: string + experimentDatasetId: + type: string + nullable: true + description: Included when `fields=dataset` is requested. + experimentItemVersion: + type: string + format: date-time + nullable: true + description: Included when `fields=dataset` is requested. + input: + nullable: true + description: Included when `fields=io` is requested. + output: + nullable: true + description: Included when `fields=io` is requested. + expectedOutput: + nullable: true + description: Included when `fields=io` is requested. + metadata: + type: object + additionalProperties: true + nullable: true + description: Included when `fields=metadata` is requested. + experimentItemMetadata: + type: object + additionalProperties: true + nullable: true + description: Included when `fields=itemMetadata` is requested. + experimentMetadata: + type: object + additionalProperties: true + nullable: true + description: Included when `fields=experimentMetadata` is requested. + experimentDescription: + type: string + nullable: true + description: Included when `fields=experimentMetadata` is requested. + scores: + type: array + items: + $ref: '#/components/schemas/ScoreV3' + nullable: true + description: >- + Included only when `fields=scores` is requested. Contains item and + trace scores only; experiment-level scores are returned by the + experiments endpoint. + required: + - id + - traceId + - startTime + - endTime + - level + - environment + - experimentId + - experimentName + - experimentItemId + FeedbackTargetType: + title: FeedbackTargetType + type: string + enum: + - skill + - mcp-tool + - cli + - docs + - public-api + - other + SubmitFeedbackRequest: + title: SubmitFeedbackRequest + type: object + properties: + targetType: + $ref: '#/components/schemas/FeedbackTargetType' + description: Category of the thing the feedback is about. + target: + type: string + description: >- + The specific instance within targetType: the skill name, MCP tool + name, CLI command, API endpoint path, or docs page path (e.g. + 'queryMetrics', '/docs/mcp'). An identifier, not a sentence. Must be + between 1 and 200 characters. + feedback: + type: string + description: >- + Concise feedback text approved by the user. Must be between 1 and + 3000 characters. + goal: + type: string + nullable: true + description: >- + Optional user-approved goal or use case they were trying to achieve. + Must be between 1 and 1500 characters when provided. Do not include + secrets, customer data, trace payloads, or broad unrelated context. + referenceUrl: + type: string + nullable: true + description: >- + Optional HTTP(S) reference URL. Langfuse stores it as text for + triage and does not fetch it. + required: + - targetType + - target + - feedback + SubmitFeedbackResponse: + title: SubmitFeedbackResponse + type: object + properties: + id: + type: string + description: Correlation ID for the submitted feedback. + required: + - id + HealthResponse: + title: HealthResponse + type: object + properties: + version: + type: string + description: Langfuse server version + status: + type: string + required: + - version + - status + IngestionEvent: + title: IngestionEvent + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - trace-create + - $ref: '#/components/schemas/TraceEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - score-create + - $ref: '#/components/schemas/ScoreEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - span-create + - $ref: '#/components/schemas/CreateSpanEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - span-update + - $ref: '#/components/schemas/UpdateSpanEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - generation-create + - $ref: '#/components/schemas/CreateGenerationEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - generation-update + - $ref: '#/components/schemas/UpdateGenerationEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - event-create + - $ref: '#/components/schemas/CreateEventEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - sdk-log + - $ref: '#/components/schemas/SDKLogEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - observation-create + - $ref: '#/components/schemas/CreateObservationEvent' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - observation-update + - $ref: '#/components/schemas/UpdateObservationEvent' + required: + - type + ObservationType: + title: ObservationType + type: string + enum: + - SPAN + - GENERATION + - EVENT + - AGENT + - TOOL + - CHAIN + - RETRIEVER + - EVALUATOR + - EMBEDDING + - GUARDRAIL + IngestionUsage: + title: IngestionUsage + oneOf: + - $ref: '#/components/schemas/Usage' + - $ref: '#/components/schemas/OpenAIUsage' + OpenAIUsage: + title: OpenAIUsage + type: object + description: Usage interface of OpenAI for improved compatibility. + properties: + promptTokens: + type: integer + nullable: true + completionTokens: + type: integer + nullable: true + totalTokens: + type: integer + nullable: true + OptionalObservationBody: + title: OptionalObservationBody + type: object + properties: + traceId: + type: string + nullable: true + name: + type: string + nullable: true + startTime: + type: string + format: date-time + nullable: true + metadata: + nullable: true + input: + nullable: true + output: + nullable: true + level: + $ref: '#/components/schemas/ObservationLevel' + nullable: true + statusMessage: + type: string + nullable: true + parentObservationId: + type: string + nullable: true + version: + type: string + nullable: true + environment: + type: string + nullable: true + CreateEventBody: + title: CreateEventBody + type: object + properties: + id: + type: string + nullable: true + allOf: + - $ref: '#/components/schemas/OptionalObservationBody' + UpdateEventBody: + title: UpdateEventBody + type: object + properties: + id: + type: string + required: + - id + allOf: + - $ref: '#/components/schemas/OptionalObservationBody' + CreateSpanBody: + title: CreateSpanBody + type: object + properties: + endTime: + type: string + format: date-time + nullable: true + allOf: + - $ref: '#/components/schemas/CreateEventBody' + UpdateSpanBody: + title: UpdateSpanBody + type: object + properties: + endTime: + type: string + format: date-time + nullable: true + allOf: + - $ref: '#/components/schemas/UpdateEventBody' + CreateGenerationBody: + title: CreateGenerationBody + type: object + properties: + completionStartTime: + type: string + format: date-time + nullable: true + model: + type: string + nullable: true + modelParameters: + type: object + additionalProperties: + $ref: '#/components/schemas/MapValue' + nullable: true + usage: + $ref: '#/components/schemas/IngestionUsage' + nullable: true + usageDetails: + $ref: '#/components/schemas/UsageDetails' + nullable: true + costDetails: + type: object + additionalProperties: + type: number + format: double + nullable: true + promptName: + type: string + nullable: true + promptVersion: + type: integer + nullable: true + allOf: + - $ref: '#/components/schemas/CreateSpanBody' + UpdateGenerationBody: + title: UpdateGenerationBody + type: object + properties: + completionStartTime: + type: string + format: date-time + nullable: true + model: + type: string + nullable: true + modelParameters: + type: object + additionalProperties: + $ref: '#/components/schemas/MapValue' + nullable: true + usage: + $ref: '#/components/schemas/IngestionUsage' + nullable: true + promptName: + type: string + nullable: true + usageDetails: + $ref: '#/components/schemas/UsageDetails' + nullable: true + costDetails: + type: object + additionalProperties: + type: number + format: double + nullable: true + promptVersion: + type: integer + nullable: true + allOf: + - $ref: '#/components/schemas/UpdateSpanBody' + ObservationBody: + title: ObservationBody + type: object + properties: + id: + type: string + nullable: true + traceId: + type: string + nullable: true + type: + $ref: '#/components/schemas/ObservationType' + name: + type: string + nullable: true + startTime: + type: string + format: date-time + nullable: true + endTime: + type: string + format: date-time + nullable: true + completionStartTime: + type: string + format: date-time + nullable: true + model: + type: string + nullable: true + modelParameters: + type: object + additionalProperties: + $ref: '#/components/schemas/MapValue' + nullable: true + input: + nullable: true + version: + type: string + nullable: true + metadata: + nullable: true + output: + nullable: true + usage: + $ref: '#/components/schemas/Usage' + nullable: true + level: + $ref: '#/components/schemas/ObservationLevel' + nullable: true + statusMessage: + type: string + nullable: true + parentObservationId: + type: string + nullable: true + environment: + type: string + nullable: true + required: + - type + TraceBody: + title: TraceBody + type: object + properties: + id: + type: string + nullable: true + timestamp: + type: string + format: date-time + nullable: true + name: + type: string + nullable: true + userId: + type: string + nullable: true + input: + nullable: true + output: + nullable: true + sessionId: + type: string + nullable: true + release: + type: string + nullable: true + version: + type: string + nullable: true + metadata: + nullable: true + tags: + type: array + items: + type: string + nullable: true + environment: + type: string + nullable: true + public: + type: boolean + nullable: true + description: Make trace publicly accessible via url + SDKLogBody: + title: SDKLogBody + type: object + properties: + log: {} + required: + - log + ScoreBody: + title: ScoreBody + type: object + properties: + id: + type: string + nullable: true + traceId: + type: string + nullable: true + sessionId: + type: string + nullable: true + observationId: + type: string + nullable: true + datasetRunId: + type: string + nullable: true + name: + type: string + description: >- + The name of the score. Always overrides "output" for correction + scores. + environment: + type: string + nullable: true + queueId: + type: string + nullable: true + description: >- + The annotation queue referenced by the score. Indicates if score was + initially created while processing annotation queue. + value: + $ref: '#/components/schemas/CreateScoreValue' + description: >- + The value of the score. Must be passed as string for categorical and + text scores, and numeric for boolean and numeric scores. Boolean + score values must equal either 1 or 0 (true or false). Text score + values must be between 1 and 500 characters. + comment: + type: string + nullable: true + metadata: + nullable: true + dataType: + $ref: '#/components/schemas/ScoreDataType' + nullable: true + description: >- + When set, must match the score value's type. If not set, will be + inferred from the score value or config + configId: + type: string + nullable: true + description: >- + Reference a score config on a score. When set, the score name must + equal the config name and scores must comply with the config's range + and data type. For categorical scores, the value must map to a + config category. Numeric scores might be constrained by the score + config's max and min values + required: + - name + - value + BaseEvent: + title: BaseEvent + type: object + properties: + id: + type: string + description: UUID v4 that identifies the event + timestamp: + type: string + description: >- + Datetime (ISO 8601) of event creation in client. Should be as close + to actual event creation in client as possible, this timestamp will + be used for ordering of events in future release. Resolution: + milliseconds (required), microseconds (optimal). + metadata: + nullable: true + description: Optional. Metadata field used by the Langfuse SDKs for debugging. + required: + - id + - timestamp + TraceEvent: + title: TraceEvent + type: object + properties: + body: + $ref: '#/components/schemas/TraceBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + CreateObservationEvent: + title: CreateObservationEvent + type: object + properties: + body: + $ref: '#/components/schemas/ObservationBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + UpdateObservationEvent: + title: UpdateObservationEvent + type: object + properties: + body: + $ref: '#/components/schemas/ObservationBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + ScoreEvent: + title: ScoreEvent + type: object + properties: + body: + $ref: '#/components/schemas/ScoreBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + SDKLogEvent: + title: SDKLogEvent + type: object + properties: + body: + $ref: '#/components/schemas/SDKLogBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + CreateGenerationEvent: + title: CreateGenerationEvent + type: object + properties: + body: + $ref: '#/components/schemas/CreateGenerationBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + UpdateGenerationEvent: + title: UpdateGenerationEvent + type: object + properties: + body: + $ref: '#/components/schemas/UpdateGenerationBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + CreateSpanEvent: + title: CreateSpanEvent + type: object + properties: + body: + $ref: '#/components/schemas/CreateSpanBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + UpdateSpanEvent: + title: UpdateSpanEvent + type: object + properties: + body: + $ref: '#/components/schemas/UpdateSpanBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + CreateEventEvent: + title: CreateEventEvent + type: object + properties: + body: + $ref: '#/components/schemas/CreateEventBody' + required: + - body + allOf: + - $ref: '#/components/schemas/BaseEvent' + IngestionSuccess: + title: IngestionSuccess + type: object + properties: + id: + type: string + status: + type: integer + required: + - id + - status + IngestionError: + title: IngestionError + type: object + properties: + id: + type: string + status: + type: integer + message: + type: string + nullable: true + error: + nullable: true + required: + - id + - status + IngestionResponse: + title: IngestionResponse + type: object + properties: + successes: + type: array + items: + $ref: '#/components/schemas/IngestionSuccess' + errors: + type: array + items: + $ref: '#/components/schemas/IngestionError' + required: + - successes + - errors + OpenAICompletionUsageSchema: + title: OpenAICompletionUsageSchema + type: object + description: OpenAI Usage schema from (Chat-)Completion APIs + properties: + prompt_tokens: + type: integer + completion_tokens: + type: integer + total_tokens: + type: integer + prompt_tokens_details: + type: object + additionalProperties: + type: integer + nullable: true + nullable: true + completion_tokens_details: + type: object + additionalProperties: + type: integer + nullable: true + nullable: true + required: + - prompt_tokens + - completion_tokens + - total_tokens + OpenAIResponseUsageSchema: + title: OpenAIResponseUsageSchema + type: object + description: OpenAI Usage schema from Response API + properties: + input_tokens: + type: integer + output_tokens: + type: integer + total_tokens: + type: integer + input_tokens_details: + type: object + additionalProperties: + type: integer + nullable: true + nullable: true + output_tokens_details: + type: object + additionalProperties: + type: integer + nullable: true + nullable: true + required: + - input_tokens + - output_tokens + - total_tokens + UsageDetails: + title: UsageDetails + oneOf: + - type: object + additionalProperties: + type: integer + - $ref: '#/components/schemas/OpenAICompletionUsageSchema' + - $ref: '#/components/schemas/OpenAIResponseUsageSchema' + legacyMetricsResponse: + title: legacyMetricsResponse + type: object + properties: + data: + type: array + items: + type: object + additionalProperties: true + description: >- + The metrics data. Each item in the list contains the metric values + and dimensions requested in the query. + + Format varies based on the query parameters. + + Histograms will return an array with [lower, upper, height] tuples. + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + legacyObservations: + title: legacyObservations + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Observation' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + legacyObservationsViews: + title: legacyObservationsViews + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ObservationsView' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + - meta + LlmConnection: + title: LlmConnection + type: object + description: LLM API connection configuration (secrets excluded) + properties: + id: + type: string + provider: + type: string + description: >- + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in + project, used for upserting. + adapter: + type: string + description: The adapter used to interface with the LLM + displaySecretKey: + type: string + description: Masked version of the secret key for display purposes + baseURL: + type: string + nullable: true + description: Custom base URL for the LLM API + customModels: + type: array + items: + type: string + description: List of custom model names available for this connection + withDefaultModels: + type: boolean + description: Whether to include default models for this adapter + extraHeaderKeys: + type: array + items: + type: string + description: >- + Keys of extra headers sent with requests (values excluded for + security) + config: + type: object + additionalProperties: true + nullable: true + description: >- + Adapter-specific configuration. Required for Bedrock + (`{"region":"us-east-1"}`), optional for OpenAI + (`{"useResponsesApi":true}`), optional for VertexAI + (`{"location":"us-central1"}`), not used by other adapters. + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - provider + - adapter + - displaySecretKey + - customModels + - withDefaultModels + - extraHeaderKeys + - createdAt + - updatedAt + PaginatedLlmConnections: + title: PaginatedLlmConnections + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/LlmConnection' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + UpsertLlmConnectionRequest: + title: UpsertLlmConnectionRequest + type: object + description: Request to create or update an LLM connection (upsert) + properties: + provider: + type: string + description: >- + Provider name (e.g., 'openai', 'my-gateway'). Must be unique in + project, used for upserting. + adapter: + $ref: '#/components/schemas/LlmAdapter' + description: The adapter used to interface with the LLM + secretKey: + type: string + description: Secret key for the LLM API. + baseURL: + type: string + nullable: true + description: Custom base URL for the LLM API + customModels: + type: array + items: + type: string + nullable: true + description: List of custom model names + withDefaultModels: + type: boolean + nullable: true + description: Whether to include default models. Default is true. + extraHeaders: + type: object + additionalProperties: + type: string + nullable: true + description: Extra headers to send with requests + config: + type: object + additionalProperties: true + nullable: true + description: >- + Adapter-specific configuration. Validation rules: - **Bedrock**: + Required. Must be `{"region": ""}` (e.g., + `{"region":"us-east-1"}`) - **OpenAI**: Optional. If provided, must + be `{"useResponsesApi": }` to control whether Langfuse + routes calls through OpenAI's Responses API. - **VertexAI**: + Optional. If provided, must be `{"location": ""}` + (e.g., `{"location":"us-central1"}`) - **Other adapters**: Not + supported. Omit this field or set to null. + required: + - provider + - adapter + - secretKey + DeleteLlmConnectionResponse: + title: DeleteLlmConnectionResponse + type: object + properties: + message: + type: string + required: + - message + LlmAdapter: + title: LlmAdapter + type: string + enum: + - anthropic + - openai + - azure + - bedrock + - google-vertex-ai + - google-ai-studio + GetMediaResponse: + title: GetMediaResponse + type: object + properties: + mediaId: + type: string + description: The unique langfuse identifier of a media record + contentType: + type: string + description: The MIME type of the media record + contentLength: + type: integer + description: The size of the media record in bytes + uploadedAt: + type: string + format: date-time + description: The date and time when the media record was uploaded + url: + type: string + description: The download URL of the media record + urlExpiry: + type: string + description: The expiry date and time of the media record download URL + required: + - mediaId + - contentType + - contentLength + - uploadedAt + - url + - urlExpiry + PatchMediaBody: + title: PatchMediaBody + type: object + properties: + uploadedAt: + type: string + format: date-time + description: The date and time when the media record was uploaded + uploadHttpStatus: + type: integer + description: The HTTP status code of the upload + uploadHttpError: + type: string + nullable: true + description: The HTTP error message of the upload + uploadTimeMs: + type: integer + nullable: true + description: The time in milliseconds it took to upload the media record + required: + - uploadedAt + - uploadHttpStatus + GetMediaUploadUrlRequest: + title: GetMediaUploadUrlRequest + type: object + description: >- + Request a presigned media upload URL. Provide exactly one context: a + trace (traceId, optionally observationId) or a dataset item (datasetId + + datasetItemId). field is required and must match the chosen context. + properties: + traceId: + type: string + nullable: true + description: >- + The trace the media is associated with. Null for dataset item media + uploads. + observationId: + type: string + nullable: true + description: >- + The observation ID associated with the media record. If the media + record is associated directly with a trace, this will be null. + datasetId: + type: string + nullable: true + description: >- + The dataset the media belongs to. Null for trace/observation media + uploads. + datasetItemId: + type: string + nullable: true + description: >- + The dataset item the media is associated with (need not exist yet). + Null for trace/observation media uploads. + contentType: + $ref: '#/components/schemas/MediaContentType' + contentLength: + type: integer + description: The size of the media record in bytes + sha256Hash: + type: string + description: The SHA-256 hash of the media record + field: + type: string + description: >- + The item field the media is in: `input`/`output`/`metadata` (trace) + or `input`/`expectedOutput`/`metadata` (dataset item). + required: + - contentType + - contentLength + - sha256Hash + - field + GetMediaUploadUrlResponse: + title: GetMediaUploadUrlResponse + type: object + properties: + uploadUrl: + type: string + nullable: true + description: >- + The presigned upload URL. If the asset is already uploaded, this + will be null + mediaId: + type: string + description: The unique langfuse identifier of a media record + required: + - mediaId + MediaContentType: + title: MediaContentType + type: string + enum: + - image/png + - image/jpeg + - image/jpg + - image/webp + - image/gif + - image/svg+xml + - image/tiff + - image/bmp + - image/avif + - image/heic + - audio/mpeg + - audio/mp3 + - audio/wav + - audio/ogg + - audio/oga + - audio/aac + - audio/mp4 + - audio/flac + - audio/opus + - audio/webm + - video/mp4 + - video/webm + - video/ogg + - video/mpeg + - video/quicktime + - video/x-msvideo + - video/x-matroska + - text/plain + - text/html + - text/css + - text/csv + - text/markdown + - text/x-python + - application/javascript + - text/x-typescript + - application/x-yaml + - application/pdf + - application/msword + - application/vnd.ms-excel + - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + - application/zip + - application/json + - application/xml + - application/octet-stream + - >- + application/vnd.openxmlformats-officedocument.wordprocessingml.document + - >- + application/vnd.openxmlformats-officedocument.presentationml.presentation + - application/rtf + - application/x-ndjson + - application/vnd.apache.parquet + - application/gzip + - application/x-tar + - application/x-7z-compressed + description: The MIME type of the media record + MetricsV2Response: + title: MetricsV2Response + type: object + properties: + data: + type: array + items: + type: object + additionalProperties: true + description: >- + The metrics data. Each item in the list contains the metric values + and dimensions requested in the query. + + Format varies based on the query parameters. + + Histograms will return an array with [lower, upper, height] tuples. + required: + - data + PaginatedModels: + title: PaginatedModels + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Model' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + CreateModelRequest: + title: CreateModelRequest + type: object + properties: + modelName: + type: string + description: >- + Name of the model definition. If multiple with the same name exist, + they are applied in the following order: (1) custom over built-in, + (2) newest according to startTime where + model.startTime- + Regex pattern which matches this model definition to + generation.model. Useful in case of fine-tuned models. If you want + to exact match, use `(?i)^modelname$` + startDate: + type: string + format: date-time + nullable: true + description: Apply only to generations which are newer than this ISO date. + unit: + $ref: '#/components/schemas/ModelUsageUnit' + nullable: true + description: Unit used by this model. + inputPrice: + type: number + format: double + nullable: true + description: >- + Deprecated. Use 'pricingTiers' instead. Price (USD) per input unit. + Creates a default tier if pricingTiers not provided. + outputPrice: + type: number + format: double + nullable: true + description: >- + Deprecated. Use 'pricingTiers' instead. Price (USD) per output unit. + Creates a default tier if pricingTiers not provided. + totalPrice: + type: number + format: double + nullable: true + description: >- + Deprecated. Use 'pricingTiers' instead. Price (USD) per total units. + Cannot be set if input or output price is set. Creates a default + tier if pricingTiers not provided. + pricingTiers: + type: array + items: + $ref: '#/components/schemas/PricingTierInput' + nullable: true + description: >- + Optional. Array of pricing tiers for this model. + + + Use pricing tiers for all models - both those with threshold-based + pricing variations and those with simple flat pricing: + + + - For models with standard flat pricing: Create a single default + tier with your prices + (e.g., one tier with isDefault=true, priority=0, conditions=[], and your standard prices) + + - For models with threshold-based pricing: Create a default tier + plus additional conditional tiers + (e.g., default tier for standard usage + high-volume tier for usage above certain thresholds) + + Requirements: + + - Cannot be provided with flat prices + (inputPrice/outputPrice/totalPrice) - use one approach or the other + + - Must include exactly one default tier with isDefault=true, + priority=0, and conditions=[] + + - All tier names and priorities must be unique within the model + + - Each tier must define at least one price + + + If omitted, you must provide flat prices instead + (inputPrice/outputPrice/totalPrice), + + which will automatically create a single default tier named + "Standard". + tokenizerId: + type: string + nullable: true + description: >- + Optional. Tokenizer to be applied to observations which match to + this model. See docs for more details. + tokenizerConfig: + nullable: true + description: >- + Optional. Configuration for the selected tokenizer. Needs to be + JSON. See docs for more details. + required: + - modelName + - matchPattern + ObservationsV2Response: + title: ObservationsV2Response + type: object + description: >- + Response containing observations with field-group-based filtering and + cursor-based pagination. + + + The `data` array contains observation objects with only the requested + field groups included. + + Use the `cursor` in `meta` to retrieve the next page of results. + properties: + data: + type: array + items: + $ref: '#/components/schemas/ObservationV2' + description: >- + Array of observation objects. Fields included depend on the `fields` + parameter in the request. + meta: + $ref: '#/components/schemas/ObservationsV2Meta' + required: + - data + - meta + ObservationsV2Meta: + title: ObservationsV2Meta + type: object + description: Metadata for cursor-based pagination + properties: + cursor: + type: string + nullable: true + description: >- + Base64-encoded cursor to use for retrieving the next page. If not + present, there are no more results. + OtelResourceSpan: + title: OtelResourceSpan + type: object + description: >- + Represents a collection of spans from a single resource as per OTLP + specification + properties: + resource: + $ref: '#/components/schemas/OtelResource' + nullable: true + description: Resource information + scopeSpans: + type: array + items: + $ref: '#/components/schemas/OtelScopeSpan' + nullable: true + description: Array of scope spans + OtelResource: + title: OtelResource + type: object + description: Resource attributes identifying the source of telemetry + properties: + attributes: + type: array + items: + $ref: '#/components/schemas/OtelAttribute' + nullable: true + description: Resource attributes like service.name, service.version, etc. + OtelScopeSpan: + title: OtelScopeSpan + type: object + description: Collection of spans from a single instrumentation scope + properties: + scope: + $ref: '#/components/schemas/OtelScope' + nullable: true + description: Instrumentation scope information + spans: + type: array + items: + $ref: '#/components/schemas/OtelSpan' + nullable: true + description: Array of spans + OtelScope: + title: OtelScope + type: object + description: Instrumentation scope information + properties: + name: + type: string + nullable: true + description: Instrumentation scope name + version: + type: string + nullable: true + description: Instrumentation scope version + attributes: + type: array + items: + $ref: '#/components/schemas/OtelAttribute' + nullable: true + description: Additional scope attributes + OtelSpan: + title: OtelSpan + type: object + description: Individual span representing a unit of work or operation + properties: + traceId: + nullable: true + description: Trace ID (16 bytes, hex-encoded string in JSON or Buffer in binary) + spanId: + nullable: true + description: Span ID (8 bytes, hex-encoded string in JSON or Buffer in binary) + parentSpanId: + nullable: true + description: Parent span ID if this is a child span + name: + type: string + nullable: true + description: Span name describing the operation + kind: + type: integer + nullable: true + description: Span kind (1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER) + startTimeUnixNano: + nullable: true + description: Start time in nanoseconds since Unix epoch + endTimeUnixNano: + nullable: true + description: End time in nanoseconds since Unix epoch + attributes: + type: array + items: + $ref: '#/components/schemas/OtelAttribute' + nullable: true + description: >- + Span attributes including Langfuse-specific attributes + (langfuse.observation.*) + status: + nullable: true + description: Span status object + OtelAttribute: + title: OtelAttribute + type: object + description: Key-value attribute pair for resources, scopes, or spans + properties: + key: + type: string + nullable: true + description: Attribute key (e.g., "service.name", "langfuse.observation.type") + value: + $ref: '#/components/schemas/OtelAttributeValue' + nullable: true + description: Attribute value + OtelAttributeValue: + title: OtelAttributeValue + type: object + description: Attribute value wrapper supporting different value types + properties: + stringValue: + type: string + nullable: true + description: String value + intValue: + type: integer + nullable: true + description: Integer value + doubleValue: + type: number + format: double + nullable: true + description: Double value + boolValue: + type: boolean + nullable: true + description: Boolean value + OtelTraceResponse: + title: OtelTraceResponse + type: object + description: Response from trace export request. Empty object indicates success. + properties: {} + MembershipRole: + title: MembershipRole + type: string + enum: + - OWNER + - ADMIN + - MEMBER + - VIEWER + MembershipRequest: + title: MembershipRequest + type: object + properties: + userId: + type: string + role: + $ref: '#/components/schemas/MembershipRole' + required: + - userId + - role + DeleteMembershipRequest: + title: DeleteMembershipRequest + type: object + properties: + userId: + type: string + required: + - userId + MembershipResponse: + title: MembershipResponse + type: object + properties: + userId: + type: string + role: + $ref: '#/components/schemas/MembershipRole' + email: + type: string + name: + type: string + required: + - userId + - role + - email + - name + MembershipDeletionResponse: + title: MembershipDeletionResponse + type: object + properties: + message: + type: string + userId: + type: string + required: + - message + - userId + MembershipsResponse: + title: MembershipsResponse + type: object + properties: + memberships: + type: array + items: + $ref: '#/components/schemas/MembershipResponse' + required: + - memberships + OrganizationProject: + title: OrganizationProject + type: object + properties: + id: + type: string + name: + type: string + metadata: + type: object + additionalProperties: true + nullable: true + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - name + - createdAt + - updatedAt + OrganizationProjectsResponse: + title: OrganizationProjectsResponse + type: object + properties: + projects: + type: array + items: + $ref: '#/components/schemas/OrganizationProject' + required: + - projects + OrganizationApiKey: + title: OrganizationApiKey + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + expiresAt: + type: string + format: date-time + nullable: true + lastUsedAt: + type: string + format: date-time + nullable: true + note: + type: string + nullable: true + publicKey: + type: string + displaySecretKey: + type: string + required: + - id + - createdAt + - publicKey + - displaySecretKey + OrganizationApiKeysResponse: + title: OrganizationApiKeysResponse + type: object + properties: + apiKeys: + type: array + items: + $ref: '#/components/schemas/OrganizationApiKey' + required: + - apiKeys + Projects: + title: Projects + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Project' + required: + - data + Organization: + title: Organization + type: object + properties: + id: + type: string + description: The unique identifier of the organization + name: + type: string + description: The name of the organization + required: + - id + - name + Project: + title: Project + type: object + properties: + id: + type: string + name: + type: string + organization: + $ref: '#/components/schemas/Organization' + description: The organization this project belongs to + metadata: + type: object + additionalProperties: true + description: Metadata for the project + retentionDays: + type: integer + nullable: true + description: >- + Number of days to retain data. Null or 0 means no retention. Omitted + if no retention is configured. + required: + - id + - name + - organization + - metadata + ProjectDeletionResponse: + title: ProjectDeletionResponse + type: object + properties: + success: + type: boolean + message: + type: string + required: + - success + - message + ApiKeyList: + title: ApiKeyList + type: object + description: List of API keys for a project + properties: + apiKeys: + type: array + items: + $ref: '#/components/schemas/ApiKeySummary' + required: + - apiKeys + ApiKeySummary: + title: ApiKeySummary + type: object + description: Summary of an API key + properties: + id: + type: string + createdAt: + type: string + format: date-time + expiresAt: + type: string + format: date-time + nullable: true + lastUsedAt: + type: string + format: date-time + nullable: true + note: + type: string + nullable: true + publicKey: + type: string + displaySecretKey: + type: string + required: + - id + - createdAt + - publicKey + - displaySecretKey + ApiKeyResponse: + title: ApiKeyResponse + type: object + description: Response for API key creation + properties: + id: + type: string + createdAt: + type: string + format: date-time + publicKey: + type: string + secretKey: + type: string + displaySecretKey: + type: string + note: + type: string + nullable: true + required: + - id + - createdAt + - publicKey + - secretKey + - displaySecretKey + ApiKeyDeletionResponse: + title: ApiKeyDeletionResponse + type: object + description: Response for API key deletion + properties: + success: + type: boolean + required: + - success + PromptMetaListResponse: + title: PromptMetaListResponse + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/PromptMeta' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + PromptMeta: + title: PromptMeta + type: object + properties: + name: + type: string + type: + $ref: '#/components/schemas/PromptType' + description: Indicates whether the prompt is a text or chat prompt. + versions: + type: array + items: + type: integer + labels: + type: array + items: + type: string + tags: + type: array + items: + type: string + lastUpdatedAt: + type: string + format: date-time + lastConfig: + description: >- + Config object of the most recent prompt version that matches the + filters (if any are provided) + required: + - name + - type + - versions + - labels + - tags + - lastUpdatedAt + - lastConfig + CreatePromptRequest: + title: CreatePromptRequest + oneOf: + - $ref: '#/components/schemas/CreateChatPromptRequest' + - $ref: '#/components/schemas/CreateTextPromptRequest' + CreateChatPromptRequest: + title: CreateChatPromptRequest + type: object + properties: + name: + type: string + prompt: + type: array + items: + $ref: '#/components/schemas/ChatMessageWithPlaceholders' + config: + nullable: true + type: + $ref: '#/components/schemas/CreateChatPromptType' + labels: + type: array + items: + type: string + nullable: true + description: List of deployment labels of this prompt version. + tags: + type: array + items: + type: string + nullable: true + description: List of tags to apply to all versions of this prompt. + commitMessage: + type: string + nullable: true + description: Commit message for this prompt version. + required: + - name + - prompt + - type + CreateTextPromptRequest: + title: CreateTextPromptRequest + type: object + properties: + name: + type: string + prompt: + type: string + config: + nullable: true + type: + $ref: '#/components/schemas/CreateTextPromptType' + nullable: true + labels: + type: array + items: + type: string + nullable: true + description: List of deployment labels of this prompt version. + tags: + type: array + items: + type: string + nullable: true + description: List of tags to apply to all versions of this prompt. + commitMessage: + type: string + nullable: true + description: Commit message for this prompt version. + required: + - name + - prompt + Prompt: + title: Prompt + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - chat + - $ref: '#/components/schemas/ChatPrompt' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - text + - $ref: '#/components/schemas/TextPrompt' + required: + - type + PromptType: + title: PromptType + type: string + enum: + - chat + - text + BasePrompt: + title: BasePrompt + type: object + properties: + name: + type: string + version: + type: integer + config: {} + labels: + type: array + items: + type: string + description: List of deployment labels of this prompt version. + tags: + type: array + items: + type: string + description: >- + List of tags. Used to filter via UI and API. The same across + versions of a prompt. + commitMessage: + type: string + nullable: true + description: Commit message for this prompt version. + resolutionGraph: + type: object + additionalProperties: true + nullable: true + description: >- + The dependency resolution graph for the current prompt. Null if the + prompt has no dependencies or if `resolve=false` was used. + required: + - name + - version + - config + - labels + - tags + ChatMessageWithPlaceholders: + title: ChatMessageWithPlaceholders + oneOf: + - $ref: '#/components/schemas/ChatMessage' + - $ref: '#/components/schemas/PlaceholderMessage' + ChatMessage: + title: ChatMessage + type: object + properties: + role: + type: string + content: + type: string + type: + $ref: '#/components/schemas/ChatMessageType' + nullable: true + required: + - role + - content + ChatMessageType: + title: ChatMessageType + type: string + enum: + - chatmessage + PlaceholderMessage: + title: PlaceholderMessage + type: object + properties: + name: + type: string + type: + $ref: '#/components/schemas/PlaceholderMessageType' + nullable: true + required: + - name + PlaceholderMessageType: + title: PlaceholderMessageType + type: string + enum: + - placeholder + TextPrompt: + title: TextPrompt + type: object + properties: + prompt: + type: string + required: + - prompt + allOf: + - $ref: '#/components/schemas/BasePrompt' + ChatPrompt: + title: ChatPrompt + type: object + properties: + prompt: + type: array + items: + $ref: '#/components/schemas/ChatMessageWithPlaceholders' + required: + - prompt + allOf: + - $ref: '#/components/schemas/BasePrompt' + CreateChatPromptType: + title: CreateChatPromptType + type: string + enum: + - chat + CreateTextPromptType: + title: CreateTextPromptType + type: string + enum: + - text + ServiceProviderConfig: + title: ServiceProviderConfig + type: object + properties: + schemas: + type: array + items: + type: string + documentationUri: + type: string + patch: + $ref: '#/components/schemas/ScimFeatureSupport' + bulk: + $ref: '#/components/schemas/BulkConfig' + filter: + $ref: '#/components/schemas/FilterConfig' + changePassword: + $ref: '#/components/schemas/ScimFeatureSupport' + sort: + $ref: '#/components/schemas/ScimFeatureSupport' + etag: + $ref: '#/components/schemas/ScimFeatureSupport' + authenticationSchemes: + type: array + items: + $ref: '#/components/schemas/AuthenticationScheme' + meta: + $ref: '#/components/schemas/ResourceMeta' + required: + - schemas + - documentationUri + - patch + - bulk + - filter + - changePassword + - sort + - etag + - authenticationSchemes + - meta + ScimFeatureSupport: + title: ScimFeatureSupport + type: object + properties: + supported: + type: boolean + required: + - supported + BulkConfig: + title: BulkConfig + type: object + properties: + supported: + type: boolean + maxOperations: + type: integer + maxPayloadSize: + type: integer + required: + - supported + - maxOperations + - maxPayloadSize + FilterConfig: + title: FilterConfig + type: object + properties: + supported: + type: boolean + maxResults: + type: integer + required: + - supported + - maxResults + ResourceMeta: + title: ResourceMeta + type: object + properties: + resourceType: + type: string + location: + type: string + required: + - resourceType + - location + AuthenticationScheme: + title: AuthenticationScheme + type: object + properties: + name: + type: string + description: + type: string + specUri: + type: string + type: + type: string + primary: + type: boolean + required: + - name + - description + - specUri + - type + - primary + ResourceTypesResponse: + title: ResourceTypesResponse + type: object + properties: + schemas: + type: array + items: + type: string + totalResults: + type: integer + Resources: + type: array + items: + $ref: '#/components/schemas/ResourceType' + required: + - schemas + - totalResults + - Resources + ResourceType: + title: ResourceType + type: object + properties: + schemas: + type: array + items: + type: string + nullable: true + id: + type: string + name: + type: string + endpoint: + type: string + description: + type: string + schema: + type: string + schemaExtensions: + type: array + items: + $ref: '#/components/schemas/SchemaExtension' + meta: + $ref: '#/components/schemas/ResourceMeta' + required: + - id + - name + - endpoint + - description + - schema + - schemaExtensions + - meta + SchemaExtension: + title: SchemaExtension + type: object + properties: + schema: + type: string + required: + type: boolean + required: + - schema + - required + SchemasResponse: + title: SchemasResponse + type: object + properties: + schemas: + type: array + items: + type: string + totalResults: + type: integer + Resources: + type: array + items: + $ref: '#/components/schemas/SchemaResource' + required: + - schemas + - totalResults + - Resources + SchemaResource: + title: SchemaResource + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + attributes: + type: array + items: {} + meta: + $ref: '#/components/schemas/ResourceMeta' + required: + - id + - name + - description + - attributes + - meta + ScimUsersListResponse: + title: ScimUsersListResponse + type: object + properties: + schemas: + type: array + items: + type: string + totalResults: + type: integer + startIndex: + type: integer + itemsPerPage: + type: integer + Resources: + type: array + items: + $ref: '#/components/schemas/ScimUser' + required: + - schemas + - totalResults + - startIndex + - itemsPerPage + - Resources + ScimUser: + title: ScimUser + type: object + properties: + schemas: + type: array + items: + type: string + id: + type: string + userName: + type: string + name: + $ref: '#/components/schemas/ScimName' + emails: + type: array + items: + $ref: '#/components/schemas/ScimEmail' + meta: + $ref: '#/components/schemas/UserMeta' + required: + - schemas + - id + - userName + - name + - emails + - meta + UserMeta: + title: UserMeta + type: object + properties: + resourceType: + type: string + created: + type: string + nullable: true + lastModified: + type: string + nullable: true + required: + - resourceType + ScimName: + title: ScimName + type: object + properties: + formatted: + type: string + nullable: true + ScimEmail: + title: ScimEmail + type: object + properties: + primary: + type: boolean + value: + type: string + type: + type: string + required: + - primary + - value + - type + EmptyResponse: + title: EmptyResponse + type: object + description: Empty response for 204 No Content responses + properties: {} + ScoreConfigs: + title: ScoreConfigs + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ScoreConfig' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + CreateScoreConfigRequest: + title: CreateScoreConfigRequest + type: object + properties: + name: + type: string + description: >- + Name of the score config. Max 35 characters. Only letters, numbers, + underscores, spaces, periods, parentheses, and hyphens are allowed. + dataType: + $ref: '#/components/schemas/ScoreConfigDataType' + categories: + type: array + items: + $ref: '#/components/schemas/ConfigCategory' + nullable: true + description: >- + Configure custom categories for categorical scores. Pass a list of + objects with `label` and `value` properties. Categories are + autogenerated for boolean configs and cannot be passed + minValue: + type: number + format: double + nullable: true + description: >- + Configure a minimum value for numerical scores. If not set, the + minimum value defaults to -∞ + maxValue: + type: number + format: double + nullable: true + description: >- + Configure a maximum value for numerical scores. If not set, the + maximum value defaults to +∞ + description: + type: string + nullable: true + description: >- + Description is shown across the Langfuse UI and can be used to e.g. + explain the config categories in detail, why a numeric range was + set, or provide additional context on config name or usage + required: + - name + - dataType + UpdateScoreConfigRequest: + title: UpdateScoreConfigRequest + type: object + properties: + isArchived: + type: boolean + nullable: true + description: The status of the score config showing if it is archived or not + name: + type: string + nullable: true + description: >- + Name of the score config. Max 35 characters. Only letters, numbers, + underscores, spaces, periods, parentheses, and hyphens are allowed. + categories: + type: array + items: + $ref: '#/components/schemas/ConfigCategory' + nullable: true + description: >- + Configure custom categories for categorical scores. Pass a list of + objects with `label` and `value` properties. Categories are + autogenerated for boolean configs and cannot be passed + minValue: + type: number + format: double + nullable: true + description: >- + Configure a minimum value for numerical scores. If not set, the + minimum value defaults to -∞ + maxValue: + type: number + format: double + nullable: true + description: >- + Configure a maximum value for numerical scores. If not set, the + maximum value defaults to +∞ + description: + type: string + nullable: true + description: >- + Description is shown across the Langfuse UI and can be used to e.g. + explain the config categories in detail, why a numeric range was + set, or provide additional context on config name or usage + ScoreSubjectTraceV3: + title: ScoreSubjectTraceV3 + type: object + properties: + id: + type: string + description: The trace ID. + required: + - id + ScoreSubjectObservationV3: + title: ScoreSubjectObservationV3 + type: object + properties: + id: + type: string + description: The observation ID. + traceId: + type: string + nullable: true + description: The parent trace ID, if available. + required: + - id + ScoreSubjectSessionV3: + title: ScoreSubjectSessionV3 + type: object + properties: + id: + type: string + description: The session ID. + required: + - id + ScoreSubjectExperimentV3: + title: ScoreSubjectExperimentV3 + type: object + properties: + id: + type: string + description: The dataset run ID (experiment ID). + required: + - id + ScoreSubjectV3: + title: ScoreSubjectV3 + oneOf: + - type: object + allOf: + - type: object + properties: + kind: + type: string + enum: + - trace + - $ref: '#/components/schemas/ScoreSubjectTraceV3' + required: + - kind + - type: object + allOf: + - type: object + properties: + kind: + type: string + enum: + - observation + - $ref: '#/components/schemas/ScoreSubjectObservationV3' + required: + - kind + - type: object + allOf: + - type: object + properties: + kind: + type: string + enum: + - session + - $ref: '#/components/schemas/ScoreSubjectSessionV3' + required: + - kind + - type: object + allOf: + - type: object + properties: + kind: + type: string + enum: + - experiment + - $ref: '#/components/schemas/ScoreSubjectExperimentV3' + required: + - kind + description: >- + A reference to the entity this score is attached to. Discriminated by + "kind" — one of trace, observation, session, or experiment. + BaseScoreV3: + title: BaseScoreV3 + type: object + properties: + id: + type: string + projectId: + type: string + name: + type: string + source: + $ref: '#/components/schemas/ScoreSource' + timestamp: + type: string + format: date-time + environment: + type: string + description: The environment from which this score originated. + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + comment: + type: string + nullable: true + description: >- + Optional comment attached to the score. Present when "details" is + included in the fields parameter. + configId: + type: string + nullable: true + description: >- + The score config ID, if this score was created from a config. + Present when "details" is included in the fields parameter. + metadata: + type: object + additionalProperties: true + nullable: true + description: >- + Arbitrary metadata attached to the score. Present when "details" is + included in the fields parameter. + authorUserId: + type: string + nullable: true + description: >- + The user who created this score, if available. Present when + "annotation" is included in the fields parameter. + queueId: + type: string + nullable: true + description: >- + The annotation queue this score belongs to, if any. Present when + "annotation" is included in the fields parameter. + subject: + $ref: '#/components/schemas/ScoreSubjectV3' + nullable: true + description: >- + The entity this score is attached to (trace, observation, session, + or experiment). Present when "subject" is included in the fields + parameter. + required: + - id + - projectId + - name + - source + - timestamp + - environment + - createdAt + - updatedAt + NumericScoreV3: + title: NumericScoreV3 + type: object + properties: + value: + type: number + format: double + description: The numeric value of the score. + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScoreV3' + BooleanScoreV3: + title: BooleanScoreV3 + type: object + properties: + value: + type: boolean + description: The boolean value of the score. + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScoreV3' + CategoricalScoreV3: + title: CategoricalScoreV3 + type: object + properties: + value: + type: string + description: The string category value of the score. + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScoreV3' + TextScoreV3: + title: TextScoreV3 + type: object + properties: + value: + type: string + description: The text content of the score. + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScoreV3' + CorrectionScoreV3: + title: CorrectionScoreV3 + type: object + properties: + value: + type: string + description: The correction content of the score. Empty string if not set. + required: + - value + allOf: + - $ref: '#/components/schemas/BaseScoreV3' + ScoreV3: + title: ScoreV3 + oneOf: + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - NUMERIC + - $ref: '#/components/schemas/NumericScoreV3' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - BOOLEAN + - $ref: '#/components/schemas/BooleanScoreV3' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CATEGORICAL + - $ref: '#/components/schemas/CategoricalScoreV3' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - TEXT + - $ref: '#/components/schemas/TextScoreV3' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CORRECTION + - $ref: '#/components/schemas/CorrectionScoreV3' + required: + - dataType + GetScoresV3Meta: + title: GetScoresV3Meta + type: object + properties: + limit: + type: integer + cursor: + type: string + nullable: true + description: >- + URL-safe base64 (base64url) cursor for the next page. Absent when + there are no more results. + required: + - limit + GetScoresV3Response: + title: GetScoresV3Response + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ScoreV3' + meta: + $ref: '#/components/schemas/GetScoresV3Meta' + required: + - data + - meta + CreateScoreRequest: + title: CreateScoreRequest + type: object + properties: + id: + type: string + nullable: true + traceId: + type: string + nullable: true + sessionId: + type: string + nullable: true + observationId: + type: string + nullable: true + datasetRunId: + type: string + nullable: true + name: + type: string + value: + $ref: '#/components/schemas/CreateScoreValue' + description: >- + The value of the score. Must be passed as string for categorical and + text scores, and numeric for boolean and numeric scores. Boolean + score values must equal either 1 or 0 (true or false). Text score + values must be between 1 and 500 characters. + comment: + type: string + nullable: true + metadata: + type: object + additionalProperties: true + nullable: true + environment: + type: string + nullable: true + description: >- + The environment of the score. Can be any lowercase alphanumeric + string with hyphens and underscores that does not start with + 'langfuse'. + queueId: + type: string + nullable: true + description: >- + The annotation queue referenced by the score. Indicates if score was + initially created while processing annotation queue. + dataType: + $ref: '#/components/schemas/ScoreDataType' + nullable: true + description: >- + The data type of the score. When passing a configId this field is + inferred. Otherwise, this field must be passed or will default to + numeric. + configId: + type: string + nullable: true + description: >- + Reference a score config on a score. The unique langfuse identifier + of a score config. When passing this field, the dataType and + stringValue fields are automatically populated. + source: + $ref: '#/components/schemas/CreateScoreSource' + nullable: true + description: >- + The source of the score. Defaults to API. Set to ANNOTATION to + prefill scores (e.g. from an LLM) for a human reviewer to verify in + an annotation queue. When source is ANNOTATION, a configId is + required unless dataType is CORRECTION. EVAL is reserved for + internal evaluator outputs and is not accepted on this endpoint. + required: + - name + - value + CreateScoreSource: + title: CreateScoreSource + type: string + enum: + - API + - ANNOTATION + description: |- + Source values accepted when creating a score via the public REST API. + EVAL is reserved for internal evaluator outputs and is intentionally not + exposed here — use commons.ScoreSource when reading scores. + CreateScoreResponse: + title: CreateScoreResponse + type: object + properties: + id: + type: string + description: The id of the created object in Langfuse + required: + - id + GetScoresResponseTraceData: + title: GetScoresResponseTraceData + type: object + properties: + userId: + type: string + nullable: true + description: The user ID associated with the trace referenced by score + tags: + type: array + items: + type: string + nullable: true + description: A list of tags associated with the trace referenced by score + environment: + type: string + nullable: true + description: The environment of the trace referenced by score + sessionId: + type: string + nullable: true + description: The session ID associated with the trace referenced by score + GetScoresResponseDataNumeric: + title: GetScoresResponseDataNumeric + type: object + properties: + trace: + $ref: '#/components/schemas/GetScoresResponseTraceData' + nullable: true + allOf: + - $ref: '#/components/schemas/NumericScore' + GetScoresResponseDataCategorical: + title: GetScoresResponseDataCategorical + type: object + properties: + trace: + $ref: '#/components/schemas/GetScoresResponseTraceData' + nullable: true + allOf: + - $ref: '#/components/schemas/CategoricalScore' + GetScoresResponseDataBoolean: + title: GetScoresResponseDataBoolean + type: object + properties: + trace: + $ref: '#/components/schemas/GetScoresResponseTraceData' + nullable: true + allOf: + - $ref: '#/components/schemas/BooleanScore' + GetScoresResponseDataCorrection: + title: GetScoresResponseDataCorrection + type: object + properties: + trace: + $ref: '#/components/schemas/GetScoresResponseTraceData' + nullable: true + allOf: + - $ref: '#/components/schemas/CorrectionScore' + GetScoresResponseDataText: + title: GetScoresResponseDataText + type: object + properties: + trace: + $ref: '#/components/schemas/GetScoresResponseTraceData' + nullable: true + allOf: + - $ref: '#/components/schemas/TextScore' + GetScoresResponseData: + title: GetScoresResponseData + oneOf: + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - NUMERIC + - $ref: '#/components/schemas/GetScoresResponseDataNumeric' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CATEGORICAL + - $ref: '#/components/schemas/GetScoresResponseDataCategorical' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - BOOLEAN + - $ref: '#/components/schemas/GetScoresResponseDataBoolean' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CORRECTION + - $ref: '#/components/schemas/GetScoresResponseDataCorrection' + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - TEXT + - $ref: '#/components/schemas/GetScoresResponseDataText' + required: + - dataType + GetScoresResponse: + title: GetScoresResponse + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/GetScoresResponseData' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + - meta + PaginatedSessions: + title: PaginatedSessions + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Session' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + - meta + Traces: + title: Traces + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/TraceWithDetails' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + _deprecation: + $ref: '#/components/schemas/Deprecation' + nullable: true + required: + - data + - meta + DeleteTraceResponse: + title: DeleteTraceResponse + type: object + properties: + message: + type: string + required: + - message + Sort: + title: Sort + type: object + properties: + id: + type: string + required: + - id + unstableEvaluatorType: + title: unstableEvaluatorType + type: string + enum: + - llm_as_judge + - code + description: |- + The evaluator engine type. + + The unstable public API supports LLM-as-a-judge and code evaluators. + unstableCodeEvaluatorSourceCodeLanguage: + title: unstableCodeEvaluatorSourceCodeLanguage + type: string + enum: + - PYTHON + - TYPESCRIPT + description: Code evaluator runtime language. + unstableEvaluatorScope: + title: unstableEvaluatorScope + type: string + enum: + - project + - managed + description: |- + Where an evaluator comes from. + + - `project`: created in your project + - `managed`: provided by Langfuse + unstableEvaluationRuleTarget: + title: unstableEvaluationRuleTarget + type: string + enum: + - observation + - experiment + description: >- + The ingestion object type that should trigger evaluation runs. + + + Choose the target first, because it changes both the valid filter + columns and the valid variable-mapping sources: + + - `observation` evaluates live-ingested observations such as + generations, spans, and events. + It supports mapping from `input`, `output`, `metadata`, and `tool_calls`. + - `experiment` evaluates live experiment executions and can additionally + map `expected_output` and `experiment_item_metadata`. + It currently supports filtering by `datasetId`. + Discover valid dataset IDs with `GET /api/public/v2/datasets`, then use the returned dataset `id` values in your filter. + unstableEvaluationRuleStatus: + title: unstableEvaluationRuleStatus + type: string + enum: + - active + - inactive + - paused + description: >- + Effective runtime status of the evaluation rule. + + + - `active`: enabled and currently runnable. + + - `inactive`: disabled by configuration. + + - `paused`: enabled, but Langfuse has blocked execution until the + underlying issue is resolved. + unstableEvaluationRuleMappingSource: + title: unstableEvaluationRuleMappingSource + type: string + enum: + - input + - output + - metadata + - tool_calls + - expected_output + - experiment_item_metadata + description: >- + Source field used to populate a prompt variable. + + + Use these values when mapping evaluator prompt variables to live data. + + + Target-specific rules: + + - `target=observation` supports `input`, `output`, `metadata`, and + `tool_calls` + + - `target=experiment` supports `input`, `output`, `metadata`, + `tool_calls`, `expected_output`, and `experiment_item_metadata` + + + Source semantics: + + - `input`: the observation or experiment input payload + + - `output`: the observation or experiment output payload + + - `metadata`: the metadata object for the target. Combine with + `jsonPath` when you need one nested field instead of the whole object. + + - `tool_calls`: the tool calls recorded on the observation, as an array + of `{id, name, arguments, type, index}` objects in the order the model + emitted them. Combine with `jsonPath` (for example `$[*].name`) to + select parts of each call. + + - `expected_output`: the experiment item's expected output. Only valid + for `target=experiment`. + + - `experiment_item_metadata`: the experiment item's metadata object. + Only valid for `target=experiment`. + unstableEvaluatorModelConfig: + title: unstableEvaluatorModelConfig + type: object + description: >- + Optional explicit model configuration for an evaluator. + + + If omitted, Langfuse uses the project's default evaluation model. + + If provided, the model must be available to the project when the + evaluator or evaluation rule is enabled. + + + To discover valid configured `provider` values for a project, call `GET + /api/public/llm-connections` and read the `provider` field from the + returned connections. + + Use a `provider` value that matches one of the connections already + configured in the same project. + + + Recovery guidance: + + - If evaluator creation returns `422` with + `code=evaluator_preflight_failed`, either provide a valid explicit + `modelConfig` here or configure the project's default evaluation model, + then retry the same request. + properties: + provider: + type: string + description: >- + Provider identifier to use for this evaluator, for example `openai` + or `anthropic`. + + + To discover valid values for the current project, call `GET + /api/public/llm-connections` and use one of the returned `provider` + values. + model: + type: string + description: >- + Model identifier exposed by the provider, for example + `gpt-4.1-mini`. + required: + - provider + - model + unstableEvaluatorOutputDataType: + title: unstableEvaluatorOutputDataType + type: string + enum: + - NUMERIC + - BOOLEAN + - CATEGORICAL + description: >- + Structured score type returned by an evaluator. + + + This controls the type of score value Langfuse stores for evaluation + results: + + - `NUMERIC`: a numeric score such as `0.82` + + - `BOOLEAN`: a boolean score such as `true` + + - `CATEGORICAL`: one or more category labels from a fixed list + unstableEvaluatorOutputFieldDefinition: + title: unstableEvaluatorOutputFieldDefinition + type: object + properties: + description: + type: string + description: >- + Human-readable instructions for what the evaluator should return in + this field. + required: + - description + unstableEvaluatorOutputDefinition: + title: unstableEvaluatorOutputDefinition + oneOf: + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - NUMERIC + - $ref: >- + #/components/schemas/unstablePublicNumericEvaluatorOutputDefinition + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - BOOLEAN + - $ref: >- + #/components/schemas/unstablePublicBooleanEvaluatorOutputDefinition + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CATEGORICAL + - $ref: >- + #/components/schemas/unstablePublicCategoricalEvaluatorOutputDefinition + required: + - dataType + description: >- + Structured output definition to send when creating an evaluator. + + + Agent guidance: + + - `dataType` is required. + + - Do not send `version`; that is an internal storage detail and is not + part of the public request contract. + + - For `NUMERIC` and `BOOLEAN`, provide `reasoning.description` and + `score.description`. + + - For `CATEGORICAL`, also provide `score.categories` and + `score.shouldAllowMultipleMatches`. + unstablePublicNumericEvaluatorOutputDefinition: + title: unstablePublicNumericEvaluatorOutputDefinition + type: object + properties: + dataType: + $ref: '#/components/schemas/unstableEvaluatorOutputDataType' + description: Always `NUMERIC`. + reasoning: + $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' + score: + $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' + required: + - dataType + - reasoning + - score + unstablePublicBooleanEvaluatorOutputDefinition: + title: unstablePublicBooleanEvaluatorOutputDefinition + type: object + properties: + dataType: + $ref: '#/components/schemas/unstableEvaluatorOutputDataType' + description: Always `BOOLEAN`. + reasoning: + $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' + score: + $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' + required: + - dataType + - reasoning + - score + unstablePublicCategoricalEvaluatorOutputScoreDefinition: + title: unstablePublicCategoricalEvaluatorOutputScoreDefinition + type: object + properties: + description: + type: string + categories: + type: array + items: + type: string + shouldAllowMultipleMatches: + type: boolean + required: + - description + - categories + - shouldAllowMultipleMatches + unstablePublicCategoricalEvaluatorOutputDefinition: + title: unstablePublicCategoricalEvaluatorOutputDefinition + type: object + properties: + dataType: + $ref: '#/components/schemas/unstableEvaluatorOutputDataType' + description: Always `CATEGORICAL`. + reasoning: + $ref: '#/components/schemas/unstableEvaluatorOutputFieldDefinition' + score: + $ref: >- + #/components/schemas/unstablePublicCategoricalEvaluatorOutputScoreDefinition + required: + - dataType + - reasoning + - score + unstablePublicEvaluatorOutputDefinition: + title: unstablePublicEvaluatorOutputDefinition + oneOf: + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - NUMERIC + - $ref: >- + #/components/schemas/unstablePublicNumericEvaluatorOutputDefinition + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - BOOLEAN + - $ref: >- + #/components/schemas/unstablePublicBooleanEvaluatorOutputDefinition + required: + - dataType + - type: object + allOf: + - type: object + properties: + dataType: + type: string + enum: + - CATEGORICAL + - $ref: >- + #/components/schemas/unstablePublicCategoricalEvaluatorOutputDefinition + required: + - dataType + description: >- + Evaluator output definition returned by the public API. + + + This response always includes `dataType` and never includes an internal + output-definition `version`. + + Legacy stored evaluator definitions are normalized into this shape + before they are returned. + + + Use this response shape when deciding how to interpret future evaluation + scores: + + - `NUMERIC`: expect numeric score values + + - `BOOLEAN`: expect `true` / `false` + + - `CATEGORICAL`: expect one or more values from `score.categories` + unstableEvaluationRuleStringFilterOperator: + title: unstableEvaluationRuleStringFilterOperator + type: string + enum: + - '=' + - contains + - does not contain + - starts with + - ends with + unstableEvaluationRuleNumberFilterOperator: + title: unstableEvaluationRuleNumberFilterOperator + type: string + enum: + - '=' + - '>' + - < + - '>=' + - <= + unstableEvaluationRuleOptionsFilterOperator: + title: unstableEvaluationRuleOptionsFilterOperator + type: string + enum: + - any of + - none of + unstableEvaluationRuleArrayOptionsFilterOperator: + title: unstableEvaluationRuleArrayOptionsFilterOperator + type: string + enum: + - any of + - none of + - all of + unstableEvaluationRuleBooleanFilterOperator: + title: unstableEvaluationRuleBooleanFilterOperator + type: string + enum: + - '=' + - <> + unstableEvaluationRuleNullFilterOperator: + title: unstableEvaluationRuleNullFilterOperator + type: string + enum: + - is null + - is not null + unstableDateTimeEvaluationRuleFilter: + title: unstableDateTimeEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleNumberFilterOperator' + description: Comparison operator for datetime values. + value: + type: string + format: date-time + description: Datetime value to compare against. + required: + - column + - operator + - value + unstableStringEvaluationRuleFilter: + title: unstableStringEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleStringFilterOperator' + value: + type: string + required: + - column + - operator + - value + unstableNumberEvaluationRuleFilter: + title: unstableNumberEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleNumberFilterOperator' + value: + type: number + format: double + required: + - column + - operator + - value + unstableStringOptionsEvaluationRuleFilter: + title: unstableStringOptionsEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleOptionsFilterOperator' + value: + type: array + items: + type: string + description: One or more allowed string values. + required: + - column + - operator + - value + unstableArrayOptionsEvaluationRuleFilter: + title: unstableArrayOptionsEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Column to filter on. + operator: + $ref: >- + #/components/schemas/unstableEvaluationRuleArrayOptionsFilterOperator + value: + type: array + items: + type: string + description: One or more array elements to match. + required: + - column + - operator + - value + unstableStringObjectEvaluationRuleFilter: + title: unstableStringObjectEvaluationRuleFilter + type: object + properties: + column: + type: string + description: >- + Object-valued column to filter on. In the unstable public API this + is currently `metadata`. + key: + type: string + description: Top-level key inside the object-valued column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleStringFilterOperator' + value: + type: string + required: + - column + - key + - operator + - value + unstableNumberObjectEvaluationRuleFilter: + title: unstableNumberObjectEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Object-valued column to filter on. + key: + type: string + description: Key inside the object-valued column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleNumberFilterOperator' + value: + type: number + format: double + required: + - column + - key + - operator + - value + unstableCategoryOptionsEvaluationRuleFilter: + title: unstableCategoryOptionsEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Object-valued column to filter on. + key: + type: string + description: Key inside the object-valued column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleOptionsFilterOperator' + value: + type: array + items: + type: string + required: + - column + - key + - operator + - value + unstableBooleanEvaluationRuleFilter: + title: unstableBooleanEvaluationRuleFilter + type: object + properties: + column: + type: string + description: Column to filter on. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleBooleanFilterOperator' + value: + type: boolean + required: + - column + - operator + - value + unstableNullEvaluationRuleFilter: + title: unstableNullEvaluationRuleFilter + type: object + properties: + column: + type: string + description: >- + Column to filter on. In the unstable public API this is currently + `parentObservationId`. + operator: + $ref: '#/components/schemas/unstableEvaluationRuleNullFilterOperator' + value: + type: string + nullable: true + description: >- + Ignored placeholder value. Clients may omit it or send an empty + string. + required: + - column + - operator + unstableEvaluationRuleMapping: + title: unstableEvaluationRuleMapping + type: object + description: >- + Maps one evaluator variable to one source field from the target object. + + + Manual mappings are used for `llm_as_judge` evaluators. `code` + evaluators use a fixed runtime mapping managed by Langfuse. + + + How to build a valid mapping list: + + 1. Create the evaluator or fetch it with `GET /evaluators/{id}`. + + 2. Read the evaluator `variables` array. + + 3. Add exactly one mapping object for each variable in that array. + + 4. Use the variable name exactly as returned, without braces such as + `{{` or `}}`. + + 5. Choose a `source` that is valid for the selected `target`. + + + `jsonPath` is optional. Use it only when the selected source is a JSON + object and you want to extract one nested field before inserting it into + the evaluator prompt. + + + Recovery guidance: + + - `invalid_variable_mapping`: the variable name is unknown for this + evaluator, or the selected `source` is not valid for the chosen `target` + + - `missing_variable_mapping`: one or more LLM-as-judge evaluator + variables are not mapped yet + + - `duplicate_variable_mapping`: the same evaluator variable appears more + than once + + - `invalid_json_path`: the JSONPath expression is malformed. Remove it + or correct it. + properties: + variable: + type: string + description: >- + Prompt variable name without braces. + + + Example: for the prompt `Judge {{input}} against {{output}}`, use + `input` and `output`. + source: + $ref: '#/components/schemas/unstableEvaluationRuleMappingSource' + description: >- + Source field that should populate the prompt variable. + + + Quick reference: + + - `target=observation`: `input`, `output`, `metadata`, `tool_calls` + + - `target=experiment`: `input`, `output`, `metadata`, `tool_calls`, + `expected_output`, `experiment_item_metadata` + jsonPath: + type: string + nullable: true + description: >- + Optional JSONPath selector applied to the selected source before it + is passed to the evaluator prompt. + + + Requirements: + + - Must start with `$` + + - Must be a syntactically valid JSONPath expression + + - Most useful with `source=metadata` + required: + - variable + - source + unstableEvaluationRuleFilter: + title: unstableEvaluationRuleFilter + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - datetime + - $ref: '#/components/schemas/unstableDateTimeEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - string + - $ref: '#/components/schemas/unstableStringEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - number + - $ref: '#/components/schemas/unstableNumberEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - stringOptions + - $ref: '#/components/schemas/unstableStringOptionsEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - categoryOptions + - $ref: '#/components/schemas/unstableCategoryOptionsEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - arrayOptions + - $ref: '#/components/schemas/unstableArrayOptionsEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - stringObject + - $ref: '#/components/schemas/unstableStringObjectEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - numberObject + - $ref: '#/components/schemas/unstableNumberObjectEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - boolean + - $ref: '#/components/schemas/unstableBooleanEvaluationRuleFilter' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - 'null' + - $ref: '#/components/schemas/unstableNullEvaluationRuleFilter' + required: + - type + description: >- + One filter condition used to decide whether a live-ingested target + should be evaluated. + + + An evaluation rule can include zero or more filter objects. All filters + must be satisfied for the target to run. + + + How to build a valid filter object: + + - Pick the `target` first, because it changes the supported columns. + + - Pick the filter `type`. That determines which fields are required. + + - Use `key` only for object filters such as `metadata`. + + - Use the correct `value` shape for the chosen filter `type`. + + + Operator quick reference by filter `type`: + + - `string`: `"="`, `contains`, `does not contain`, `starts with`, `ends + with` + + - `number`: `"="`, `">"`, `"<"`, `">="`, `"<="` + + - `datetime`: `"="`, `">"`, `"<"`, `">="`, `"<="` + + - `stringOptions`: `any of`, `none of` + + - `arrayOptions`: `any of`, `none of`, `all of` + + - `stringObject`: same operators as `string` + + - `boolean`: `"="`, `"<>"` + + - `null`: `is null`, `is not null` + + + Supported columns by target: + + - `target=observation` + - `type`: `stringOptions`, operators `any of` / `none of`, values `GENERATION`, `SPAN`, `EVENT` + - `name`: `stringOptions`, operators `any of` / `none of` + - `environment`: `stringOptions`, operators `any of` / `none of` + - `level`: `stringOptions`, operators `any of` / `none of`, values `DEBUG`, `DEFAULT`, `WARNING`, `ERROR` + - `version`: `string` + - `traceName`: `stringOptions`, operators `any of` / `none of` + - `userId`: `string` + - `sessionId`: `string` + - `tags`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `metadata`: `stringObject` with `key` + - `isRootObservation`: `boolean`, operators `=` / `<>`; true when the observation has no parent or is explicitly marked as an application root + - `parentObservationId`: `null`, operators `is null` / `is not null` + - `calledToolNames`: `arrayOptions`, operators `any of` / `none of` / `all of` + - `toolCalls`: `number` + - `target=experiment` + - `datasetId`: `stringOptions`, operators `any of` / `none of` + Use dataset `id` values from `GET /api/public/v2/datasets`, not dataset names. + + Recovery guidance: + + - `invalid_filter_value` with `details.column` but no `invalidValues`: + the selected `column` is not supported for the chosen `target` + + - `invalid_filter_value` with `details.invalidValues`: the selected + values are not allowed for that column. Replace them with one of + `details.allowedValues` when provided. + + - `invalid_filter_value` for `column=datasetId`: call `GET + /api/public/v2/datasets`, then retry with dataset `id` values from that + response. + unstableDashboardWidgetView: + title: unstableDashboardWidgetView + type: string + enum: + - observations + - scores-numeric + - scores-boolean + - scores-categorical + unstableDashboardWidgetViewWithLegacy: + title: unstableDashboardWidgetViewWithLegacy + type: string + enum: + - observations + - scores-numeric + - scores-boolean + - scores-categorical + - traces + description: |- + Widget data view. Responses may include the legacy `traces` value for + widgets created before this API existed. + unstableDashboardWidgetChartType: + title: unstableDashboardWidgetChartType + type: string + enum: + - LINE_TIME_SERIES + - AREA_TIME_SERIES + - BAR_TIME_SERIES + - HORIZONTAL_BAR + - VERTICAL_BAR + - PIE + - NUMBER + - HISTOGRAM + - PIVOT_TABLE + unstableDashboardWidgetMetricAggregation: + title: unstableDashboardWidgetMetricAggregation + type: string + enum: + - sum + - avg + - count + - max + - min + - p50 + - p75 + - p90 + - p95 + - p99 + - histogram + - uniq + unstableDashboardWidgetDimension: + title: unstableDashboardWidgetDimension + type: object + properties: + field: + type: string + required: + - field + unstableDashboardWidgetMetric: + title: unstableDashboardWidgetMetric + type: object + properties: + measure: + type: string + agg: + $ref: '#/components/schemas/unstableDashboardWidgetMetricAggregation' + required: + - measure + - agg + unstableDashboardWidgetFilter: + title: unstableDashboardWidgetFilter + type: object + description: >- + A filter in Langfuse filter-state shape. The `value` shape and the + + allowed operators depend on `type`: + + + | `type` | `value` | operators | + + |---|---|---| + + | `string` | string | `=`, `contains`, `does not contain`, `starts + with`, `ends with` | + + | `number` | number | `=`, `>`, `<`, `>=`, `<=` | + + | `datetime` | ISO datetime string | `>`, `<`, `>=`, `<=` | + + | `boolean` | boolean | `=`, `<>` | + + | `null` | `""` | `is null`, `is not null` | + + | `stringOptions` | list of strings | `any of`, `none of` | + + | `arrayOptions` | list of strings | `any of`, `none of`, `all of` | + + | `categoryOptions` | list of strings (requires `key`) | `any of`, `none + of` | + + | `stringObject` | string (requires `key`, e.g. a metadata key) | same + as `string` | + + | `numberObject` | number (requires `key`, e.g. a score name) | same as + `number` | + + | `booleanObject` | boolean (requires `key`) | `=`, `<>` | + properties: + column: + type: string + operator: + type: string + type: + type: string + value: + nullable: true + key: + type: string + nullable: true + required: + - column + - operator + - type + unstableDashboardWidgetChartConfig: + title: unstableDashboardWidgetChartConfig + type: object + description: |- + Chart-specific widget configuration. + + `type` must match the top-level `chartType`. + `row_limit` applies to total-value charts and pivot tables. + `bins` applies to histograms. + `defaultSort` applies to pivot tables. + properties: + type: + $ref: '#/components/schemas/unstableDashboardWidgetChartType' + row_limit: + type: integer + nullable: true + show_value_labels: + type: boolean + nullable: true + bins: + type: integer + nullable: true + defaultSort: + $ref: '#/components/schemas/unstableDashboardWidgetDefaultSort' + nullable: true + required: + - type + unstableDashboardWidgetDefaultSort: + title: unstableDashboardWidgetDefaultSort + type: object + properties: + column: + type: string + order: + $ref: '#/components/schemas/unstableDashboardWidgetSortOrder' + required: + - column + - order + unstableDashboardWidgetSortOrder: + title: unstableDashboardWidgetSortOrder + type: string + enum: + - ASC + - DESC + unstableDashboardWidgetChartConfigInput: + title: unstableDashboardWidgetChartConfigInput + type: object + description: |- + Input-side chart config. `type` is optional and defaults to the + widget's `chartType`; when given it must match. + properties: + type: + $ref: '#/components/schemas/unstableDashboardWidgetChartType' + nullable: true + row_limit: + type: integer + nullable: true + show_value_labels: + type: boolean + nullable: true + bins: + type: integer + nullable: true + defaultSort: + $ref: '#/components/schemas/unstableDashboardWidgetDefaultSort' + nullable: true + unstableCreateDashboardWidgetRequest: + title: unstableCreateDashboardWidgetRequest + type: object + properties: + name: + type: string + description: + type: string + nullable: true + description: Defaults to an empty string. + view: + $ref: '#/components/schemas/unstableDashboardWidgetView' + dimensions: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetDimension' + metrics: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetMetric' + filters: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetFilter' + chartType: + $ref: '#/components/schemas/unstableDashboardWidgetChartType' + chartConfig: + $ref: '#/components/schemas/unstableDashboardWidgetChartConfigInput' + nullable: true + description: Defaults to the plain config for `chartType`. + required: + - name + - view + - dimensions + - metrics + - filters + - chartType + unstableUpdateDashboardWidgetRequest: + title: unstableUpdateDashboardWidgetRequest + type: object + properties: + name: + type: string + nullable: true + description: + type: string + nullable: true + view: + $ref: '#/components/schemas/unstableDashboardWidgetView' + nullable: true + dimensions: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetDimension' + nullable: true + metrics: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetMetric' + nullable: true + filters: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetFilter' + nullable: true + chartType: + $ref: '#/components/schemas/unstableDashboardWidgetChartType' + nullable: true + chartConfig: + $ref: '#/components/schemas/unstableDashboardWidgetChartConfigInput' + nullable: true + unstableDashboardWidgetList: + title: unstableDashboardWidgetList + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidget' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + unstableDeleteDashboardWidgetResponse: + title: unstableDeleteDashboardWidgetResponse + type: object + properties: + message: + type: string + required: + - message + unstableDashboardWidget: + title: unstableDashboardWidget + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + name: + type: string + description: + type: string + view: + $ref: '#/components/schemas/unstableDashboardWidgetViewWithLegacy' + dimensions: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetDimension' + metrics: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetMetric' + filters: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetFilter' + chartType: + $ref: '#/components/schemas/unstableDashboardWidgetChartType' + chartConfig: + $ref: '#/components/schemas/unstableDashboardWidgetChartConfig' + required: + - id + - createdAt + - updatedAt + - name + - description + - view + - dimensions + - metrics + - filters + - chartType + - chartConfig + unstableDashboardPlacement: + title: unstableDashboardPlacement + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - widget + - $ref: '#/components/schemas/unstableWidgetPlacement' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - preset + - $ref: '#/components/schemas/unstablePresetPlacement' + required: + - type + description: |- + A tile on the dashboard's 12-column grid. `x`/`y` are the tile's + top-left cell (0-based; `y` grows downward), `width`/`height` its size + in cells. The UI default tile is 6x6 (half width). Overlapping tiles + are not rejected; prefer appending below existing tiles (or omit the + position on create to let the server do it). + unstableWidgetPlacement: + title: unstableWidgetPlacement + type: object + properties: + id: + type: string + widgetId: + type: string + x: + type: integer + 'y': + type: integer + width: + type: integer + height: + type: integer + required: + - id + - widgetId + - x + - 'y' + - width + - height + unstablePresetPlacement: + title: unstablePresetPlacement + type: object + properties: + id: + type: string + presetId: + type: string + x: + type: integer + 'y': + type: integer + width: + type: integer + height: + type: integer + required: + - id + - presetId + - x + - 'y' + - width + - height + unstableCreateDashboardPlacementRequest: + title: unstableCreateDashboardPlacementRequest + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - widget + - $ref: '#/components/schemas/unstableCreateWidgetPlacement' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - preset + - $ref: '#/components/schemas/unstableCreatePresetPlacement' + required: + - type + unstableCreateWidgetPlacement: + title: unstableCreateWidgetPlacement + type: object + properties: + id: + type: string + nullable: true + description: Server-generated when omitted. + widgetId: + type: string + x: + type: integer + nullable: true + description: Grid column (12-column grid). Defaults to `0`. + 'y': + type: integer + nullable: true + description: Grid row. Defaults to the first row below all existing tiles. + width: + type: integer + nullable: true + description: Width in grid columns. Defaults to `6`. + height: + type: integer + nullable: true + description: Height in grid rows. Defaults to `6`. + required: + - widgetId + unstableCreatePresetPlacement: + title: unstableCreatePresetPlacement + type: object + properties: + id: + type: string + nullable: true + description: Server-generated when omitted. + presetId: + type: string + x: + type: integer + nullable: true + description: Grid column (12-column grid). Defaults to `0`. + 'y': + type: integer + nullable: true + description: Grid row. Defaults to the first row below all existing tiles. + width: + type: integer + nullable: true + description: Width in grid columns. Defaults to `6`. + height: + type: integer + nullable: true + description: Height in grid rows. Defaults to `6`. + required: + - presetId + unstableUpdateDashboardPlacementRequest: + title: unstableUpdateDashboardPlacementRequest + type: object + properties: + x: + type: integer + nullable: true + description: Grid column (12-column grid). + 'y': + type: integer + nullable: true + description: Grid row. + width: + type: integer + nullable: true + description: Width in grid columns. + height: + type: integer + nullable: true + description: Height in grid rows. + unstableDeleteDashboardPlacementResponse: + title: unstableDeleteDashboardPlacementResponse + type: object + properties: + message: + type: string + required: + - message + unstableDashboardDefinition: + title: unstableDashboardDefinition + type: object + properties: + widgets: + type: array + items: + $ref: '#/components/schemas/unstableDashboardPlacement' + required: + - widgets + unstableDashboard: + title: unstableDashboard + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + name: + type: string + description: + type: string + definition: + $ref: '#/components/schemas/unstableDashboardDefinition' + filters: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetFilter' + description: Dashboard-level filters applied to all widgets on the dashboard. + required: + - id + - createdAt + - updatedAt + - name + - description + - definition + - filters + unstableDashboardList: + title: unstableDashboardList + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/unstableDashboard' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + unstableCreateDashboardRequest: + title: unstableCreateDashboardRequest + type: object + properties: + name: + type: string + description: + type: string + nullable: true + definition: + $ref: '#/components/schemas/unstableDashboardDefinition' + nullable: true + filters: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetFilter' + nullable: true + required: + - name + unstableUpdateDashboardRequest: + title: unstableUpdateDashboardRequest + type: object + properties: + name: + type: string + nullable: true + description: + type: string + nullable: true + definition: + $ref: '#/components/schemas/unstableDashboardDefinition' + nullable: true + filters: + type: array + items: + $ref: '#/components/schemas/unstableDashboardWidgetFilter' + nullable: true + unstableDeleteDashboardResponse: + title: unstableDeleteDashboardResponse + type: object + properties: + message: + type: string + required: + - message + unstablePublicApiErrorCode: + title: unstablePublicApiErrorCode + type: string + enum: + - authentication_failed + - access_denied + - invalid_request + - invalid_query + - invalid_body + - invalid_filter_value + - invalid_json_path + - invalid_variable_mapping + - missing_variable_mapping + - duplicate_variable_mapping + - resource_not_found + - name_conflict + - evaluator_preflight_failed + - conflict + - unprocessable_content + - rate_limited + - method_not_allowed + - internal_error + description: >- + Machine-readable error code returned by the unstable evaluators API. + + + SDKs, CLIs, and agents should branch on `code` rather than parsing the + human-readable `message`. + + The HTTP status still indicates the broad error class, while `code` + gives the specific failure reason. + unstablePublicApiValidationIssue: + title: unstablePublicApiValidationIssue + type: object + description: >- + One validation issue returned for malformed request bodies or query + parameters. + + + This mirrors the most important parts of a Zod issue: a machine-readable + `code`, + + a human-readable `message`, and a structured `path`. + properties: + code: + type: string + description: >- + Machine-readable validation issue code emitted by the server + validator. + message: + type: string + description: Human-readable explanation of the validation failure. + path: + type: array + items: {} + description: Path to the invalid field, for example `["mapping", 0, "jsonPath"]`. + required: + - code + - message + - path + unstablePublicApiErrorDetails: + title: unstablePublicApiErrorDetails + type: object + description: >- + Optional structured context attached to an unstable-evals error. + + + The populated fields depend on the error `code`: + + - request parsing failures populate `issues` + + - filter validation failures populate `field`, `column`, + `invalidValues`, and `allowedValues` + + - variable mapping failures populate `field`, `variable`, or `variables` + + - JSONPath validation failures populate `field`, `variable`, and `value` + + - evaluator preflight failures populate `evaluatorName`, `provider`, and + `model` + + - rate limiting populates `retryAfterSeconds`, `limit`, `remaining`, and + `resetAt` + properties: + issues: + type: array + items: + $ref: '#/components/schemas/unstablePublicApiValidationIssue' + nullable: true + description: Validation issues for malformed request bodies or query parameters. + field: + type: string + nullable: true + description: >- + Path-like reference to the failing field, for example + `mapping[1].jsonPath`. + column: + type: string + nullable: true + description: Filter column that failed validation. + invalidValues: + type: array + items: + type: string + nullable: true + description: Unsupported values supplied by the caller. + allowedValues: + type: array + items: + type: string + nullable: true + description: Allowed values for the failing filter column. + variable: + type: string + nullable: true + description: Evaluator variable involved in the failure. + variables: + type: array + items: + type: string + nullable: true + description: >- + Multiple evaluator variables involved in the failure, for example + missing mappings. + value: + type: string + nullable: true + description: Raw invalid value supplied by the caller. + evaluatorName: + type: string + nullable: true + description: Evaluator name used during preflight validation. + provider: + type: string + nullable: true + description: Provider resolved during evaluator preflight, if any. + model: + type: string + nullable: true + description: Model resolved during evaluator preflight, if any. + retryAfterSeconds: + type: integer + nullable: true + description: Suggested retry delay for rate-limited requests. + limit: + type: integer + nullable: true + description: >- + Numeric limit associated with the failure, for example the active + evaluation-rule cap or the current rate-limit window. + remaining: + type: integer + nullable: true + description: Remaining requests in the current rate-limit window. + resetAt: + type: string + nullable: true + description: ISO-8601 timestamp when the current rate-limit window resets. + unstablePublicApiError: + title: unstablePublicApiError + type: object + description: >- + Standard error envelope for the unstable evaluators API. + + + Response handling guidance: + + - Use the HTTP status code for the broad class of failure. + + - Use `code` for precise branching in SDKs, CLIs, or agents. + + - Inspect `details` for field-level validation context such as invalid + filter values, malformed JSONPath expressions, or missing variable + mappings. + + - Retry only after fixing the specific issue described by `code` and + `details`. + properties: + message: + type: string + description: Human-readable description of the failure. + code: + $ref: '#/components/schemas/unstablePublicApiErrorCode' + description: Stable machine-readable error code. + details: + $ref: '#/components/schemas/unstablePublicApiErrorDetails' + nullable: true + description: >- + Optional structured error context. Inspect the populated fields + based on `code`. + required: + - message + - code + unstableEvaluationRuleBase: + title: unstableEvaluationRuleBase + type: object + description: >- + Live evaluation rule for incoming data. + + + An evaluation rule answers: + + - which evaluator should be used + + - which target objects should trigger scoring + + - how often scoring should run + + - which target fields should populate each evaluator variable + + - whether the deployment is active, inactive, or paused + + + Important status semantics: + + - `enabled` is the desired on/off setting from the client + + - `status` is the effective runtime state after Langfuse applies + validation and blocking rules + + - `enabled=true` with `status=paused` means the rule should run, but + Langfuse has paused it until the underlying problem is fixed + properties: + id: + type: string + description: Stable evaluation rule identifier. + name: + type: string + description: >- + Human-readable deployment name. This is independent from the + evaluator name. + evaluator: + $ref: '#/components/schemas/unstableEvaluationRuleEvaluator' + description: >- + Evaluator currently used by this rule. + + + `name` and `scope` identify the evaluator family conceptually. + + `id` is the currently active evaluator version in that family. + + If you create a newer project version with the same evaluator name + later, existing evaluation rules are moved to it automatically. + enabled: + type: boolean + description: Desired enabled state configured by the client. + status: + $ref: '#/components/schemas/unstableEvaluationRuleStatus' + description: >- + Effective runtime status after Langfuse applies validation and + blocking rules. + pausedReason: + type: string + nullable: true + description: Machine-readable reason when `status=paused`, otherwise `null`. + pausedMessage: + type: string + nullable: true + description: Human-readable explanation when `status=paused`, otherwise `null`. + sampling: + type: number + format: double + description: |- + Fraction of matching target objects that should be evaluated. + + Must be greater than `0` and less than or equal to `1`. + - `1` means evaluate every matching target. + - `0.25` means evaluate approximately 25% of matching targets. + createdAt: + type: string + format: date-time + description: Timestamp when the evaluation rule was created. + updatedAt: + type: string + format: date-time + description: Timestamp when the evaluation rule was last updated. + required: + - id + - name + - evaluator + - enabled + - status + - pausedReason + - pausedMessage + - sampling + - createdAt + - updatedAt + unstableEvaluationRule: + title: unstableEvaluationRule + type: object + properties: + target: + $ref: '#/components/schemas/unstableEvaluationRuleTarget' + description: Target object type that should trigger scoring. + filter: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleFilter' + description: >- + List of filter conditions used to decide whether a target should be + evaluated. + mapping: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleMapping' + description: >- + Variable mappings used to populate evaluator runtime variables from + the live target object. + required: + - target + - filter + - mapping + allOf: + - $ref: '#/components/schemas/unstableEvaluationRuleBase' + unstableLegacyEvaluationRule: + title: unstableLegacyEvaluationRule + type: object + description: >- + Legacy trace- or dataset-level evaluation rule returned by list and get + for migration. + + + This resource is read-only through the unstable public API. Its mapping + preserves the trace, dataset item, or named observation that each + evaluator variable previously read from. Its filters use the persisted + legacy filter format so migration clients can read the configuration + without losing information. + properties: + target: + $ref: '#/components/schemas/unstableLegacyEvaluationRuleTarget' + delay: + type: integer + description: Delay in milliseconds before the legacy evaluation job runs. + timeScope: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleTimeScope' + description: >- + Whether the legacy rule evaluates newly ingested data, existing + data, or both. + filter: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleFilter' + description: Stored filters used by the legacy trace or dataset rule. + mapping: + type: array + items: + $ref: '#/components/schemas/unstableLegacyEvaluationRuleMapping' + description: >- + Stored variable mappings, including the trace, dataset item, or + named observation selected for each variable. + required: + - target + - delay + - timeScope + - filter + - mapping + allOf: + - $ref: '#/components/schemas/unstableEvaluationRuleBase' + unstableReadableEvaluationRule: + title: unstableReadableEvaluationRule + oneOf: + - $ref: '#/components/schemas/unstableEvaluationRule' + - $ref: '#/components/schemas/unstableLegacyEvaluationRule' + description: >- + Evaluation rule returned by list and get, including read-only legacy + trace and dataset rules. + unstableEvaluationRules: + title: unstableEvaluationRules + type: object + description: Paginated list of evaluation rules. + properties: + data: + type: array + items: + $ref: '#/components/schemas/unstableReadableEvaluationRule' + description: Evaluation rules in the current page. + meta: + $ref: '#/components/schemas/utilsMetaResponse' + description: Standard pagination metadata. + required: + - data + - meta + unstableCreateEvaluationRuleRequest: + title: unstableCreateEvaluationRuleRequest + oneOf: + - $ref: '#/components/schemas/unstableCreateLlmAsJudgeEvaluationRuleRequest' + - $ref: '#/components/schemas/unstableCreateCodeEvaluationRuleRequest' + description: >- + Request body for creating an evaluation rule. + + + Checklist for agents and SDK clients: + + - reference an existing evaluator family by `evaluator.name` and + `evaluator.scope` + + - choose `target=observation` or `target=experiment` + + - if `target=experiment` and you want a dataset filter, call `GET + /api/public/v2/datasets` first and use dataset `id` values in + `filter[].value` + + - for `llm_as_judge`, fetch or inspect the evaluator first and provide a + complete variable mapping for every evaluator variable + + - for `code`, do not send variables or mappings; Langfuse stores the + fixed code runtime mapping automatically + + - optionally narrow execution with `filter` + + - set `enabled=true` only when you want live execution immediately + unstableCreateLlmAsJudgeEvaluationRuleRequest: + title: unstableCreateLlmAsJudgeEvaluationRuleRequest + type: object + properties: + name: + type: string + description: Human-readable deployment name. + evaluator: + $ref: >- + #/components/schemas/unstableLlmAsJudgeEvaluationRuleEvaluatorReference + description: >- + LLM-as-judge evaluator family to use. + + + Use `name`, `scope`, and `type` from the evaluator endpoints. If + `type` is omitted, Langfuse defaults it to `llm_as_judge` for + backwards compatibility. + + Langfuse resolves that family to its latest version before saving + the rule. + target: + $ref: '#/components/schemas/unstableEvaluationRuleTarget' + description: Target object type to evaluate. + enabled: + type: boolean + description: Whether the deployment should be active immediately after creation. + sampling: + type: number + format: double + nullable: true + description: Optional sampling fraction. Defaults to `1`. + filter: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleFilter' + nullable: true + description: >- + Optional filter list. + + + Omit or pass an empty list to evaluate all matching targets for the + selected `target`. + + Each filter object must use a column that is valid for that + `target`. + + For `target=experiment`, `column=datasetId` expects dataset `id` + values from `GET /api/public/v2/datasets`, not dataset names. + mapping: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleMapping' + description: >- + LLM-as-judge variable mappings. + + + Every evaluator variable must appear exactly once. + + Build this list from the evaluator `variables` array returned by the + evaluator endpoints. + required: + - name + - evaluator + - target + - enabled + - mapping + unstableCreateCodeEvaluationRuleRequest: + title: unstableCreateCodeEvaluationRuleRequest + type: object + properties: + name: + type: string + description: Human-readable deployment name. + evaluator: + $ref: '#/components/schemas/unstableCodeEvaluationRuleEvaluatorReference' + description: >- + Code evaluator family to use. + + + Use `name`, `scope`, and `type` from the evaluator endpoints. + + Langfuse resolves that family to its latest version before saving + the rule. + target: + $ref: '#/components/schemas/unstableEvaluationRuleTarget' + description: Target object type to evaluate. + enabled: + type: boolean + description: Whether the deployment should be active immediately after creation. + sampling: + type: number + format: double + nullable: true + description: Optional sampling fraction. Defaults to `1`. + filter: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleFilter' + nullable: true + description: >- + Optional filter list. + + + Omit or pass an empty list to evaluate all matching targets for the + selected `target`. + + Each filter object must use a column that is valid for that + `target`. + + For `target=experiment`, `column=datasetId` expects dataset `id` + values from `GET /api/public/v2/datasets`, not dataset names. + required: + - name + - evaluator + - target + - enabled + unstableUpdateEvaluationRuleRequest: + title: unstableUpdateEvaluationRuleRequest + type: object + description: >- + Partial update body for an evaluation rule. + + + Provide only the fields you want to change. + + An empty body is rejected. + + + Practical guidance: + + - If you only want to rename the rule or change sampling, send just + those fields. + + - If you change to an LLM-as-judge `evaluator`, send a fresh `mapping` + unless you are certain the existing mapping still matches the evaluator + variables. + + - If you change `target` for an LLM-as-judge rule, usually send both + `filter` and `mapping` in the same request. + + - For code evaluator rules, omit `mapping`; Langfuse stores the fixed + code runtime mapping automatically. + + - If you change an experiment `datasetId` filter, call `GET + /api/public/v2/datasets` and use dataset `id` values from that response. + properties: + name: + type: string + nullable: true + description: Updated deployment name. + evaluator: + $ref: '#/components/schemas/unstableEvaluationRuleEvaluatorReference' + nullable: true + description: >- + Updated evaluator family. + + + Langfuse resolves the provided evaluator family to its latest + version before saving the rule. + + A rule's evaluator type cannot be changed: provide `name` and + `scope` for an evaluator family of the rule's current type. To use a + different evaluator type, create a new rule. + target: + $ref: '#/components/schemas/unstableEvaluationRuleTarget' + nullable: true + description: Updated target object type. + enabled: + type: boolean + nullable: true + description: Updated desired enabled state. + sampling: + type: number + format: double + nullable: true + description: Updated sampling fraction. + filter: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleFilter' + nullable: true + description: >- + Updated filter list. + + + For `target=experiment`, `column=datasetId` expects dataset `id` + values from `GET /api/public/v2/datasets`, not dataset names. + mapping: + type: array + items: + $ref: '#/components/schemas/unstableEvaluationRuleMapping' + nullable: true + description: >- + Updated LLM-as-judge variable mappings. + + + Do not send this field for code evaluator rules. Langfuse stores the + fixed code runtime mapping automatically and returns it in the + response. + unstableDeleteEvaluationRuleResponse: + title: unstableDeleteEvaluationRuleResponse + type: object + description: Confirmation response returned after successful deletion. + properties: + message: + type: string + description: Always `Evaluation rule successfully deleted`. + required: + - message + unstableEvaluationRuleEvaluatorReference: + title: unstableEvaluationRuleEvaluatorReference + type: object + description: >- + Evaluator family reference used when updating an evaluation rule. + + + `name` and `scope` identify the evaluator family in the authenticated + project context. + + A rule's evaluator type cannot be changed, so this reference does not + accept a `type`; the family must match the rule's current evaluator + type. + properties: + name: + type: string + description: Evaluator family name. + scope: + $ref: '#/components/schemas/unstableEvaluatorScope' + description: Whether the evaluator family is project-owned or Langfuse-managed. + required: + - name + - scope + unstableLlmAsJudgeEvaluationRuleEvaluatorReference: + title: unstableLlmAsJudgeEvaluationRuleEvaluatorReference + type: object + description: >- + LLM-as-judge evaluator family reference used when creating an evaluation + rule. + properties: + name: + type: string + description: Evaluator family name. + scope: + $ref: '#/components/schemas/unstableEvaluatorScope' + description: Whether the evaluator family is project-owned or Langfuse-managed. + type: + $ref: '#/components/schemas/unstableLlmAsJudgeEvaluatorType' + nullable: true + description: Evaluator engine type. Defaults to `llm_as_judge` when omitted. + required: + - name + - scope + unstableCodeEvaluationRuleEvaluatorReference: + title: unstableCodeEvaluationRuleEvaluatorReference + type: object + description: Code evaluator family reference used when creating an evaluation rule. + properties: + name: + type: string + description: Evaluator family name. + scope: + $ref: '#/components/schemas/unstableEvaluatorScope' + description: Whether the evaluator family is project-owned or Langfuse-managed. + type: + type: string + const: code + description: Must be `code`. + required: + - name + - scope + - type + unstableLlmAsJudgeEvaluatorType: + title: unstableLlmAsJudgeEvaluatorType + type: string + enum: + - llm_as_judge + unstableEvaluationRuleTimeScope: + title: unstableEvaluationRuleTimeScope + type: string + enum: + - NEW + - EXISTING + unstableLegacyEvaluationRuleTarget: + title: unstableLegacyEvaluationRuleTarget + type: string + enum: + - trace + - dataset + unstableLegacyEvaluationRuleMapping: + title: unstableLegacyEvaluationRuleMapping + type: object + description: >- + Maps one evaluator variable to a trace, dataset item, or field on a + named observation in a legacy rule. + properties: + variable: + type: string + description: Evaluator prompt variable populated by this mapping. + langfuseObject: + $ref: '#/components/schemas/unstableLegacyEvaluationObject' + description: >- + Trace, dataset item, or observation type from which the value is + read. + objectName: + type: string + nullable: true + description: >- + Observation name to match, or `null` when `langfuseObject` is + `trace` or `dataset_item`. + source: + type: string + description: Stored field selected from the trace, dataset item, or observation. + jsonPath: + type: string + nullable: true + description: Optional JSONPath selector applied to the selected field. + required: + - variable + - langfuseObject + - objectName + - source + unstableLegacyEvaluationObject: + title: unstableLegacyEvaluationObject + type: string + enum: + - trace + - span + - generation + - event + - agent + - tool + - chain + - retriever + - evaluator + - embedding + - guardrail + - dataset_item + unstableEvaluationRuleEvaluator: + title: unstableEvaluationRuleEvaluator + type: object + description: |- + Resolved evaluator currently used by the evaluation rule. + + `id` is the exact active evaluator version. + `name`, `scope`, and `type` identify the evaluator family conceptually. + properties: + id: + type: string + description: >- + Identifier of the exact evaluator version currently used by the + rule. + name: + type: string + description: Evaluator family name. + scope: + $ref: '#/components/schemas/unstableEvaluatorScope' + description: Whether the evaluator family is project-owned or Langfuse-managed. + type: + $ref: '#/components/schemas/unstableEvaluatorType' + description: Evaluator engine type. + required: + - id + - name + - scope + - type + unstableDeleteEvaluatorResponse: + title: unstableDeleteEvaluatorResponse + type: object + description: Confirmation response returned after successful deletion. + properties: + message: + type: string + description: Always `Evaluator successfully deleted`. + required: + - message + unstableEvaluator: + title: unstableEvaluator + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - llm_as_judge + - $ref: '#/components/schemas/unstableLlmAsJudgeEvaluator' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - code + - $ref: '#/components/schemas/unstableCodeEvaluator' + required: + - type + description: >- + One evaluator that can be used for scoring. + + + An evaluator describes **how** to score data. + + + It does not define **which** live objects are evaluated. That is the job + of `evaluation-rules`. + + + For agent clients, the most important fields are: + + - `type`: determines which evaluator fields are present + + - `variables`: for LLM evaluators, use these exact names when building + the evaluation-rule `mapping` array. LLM evaluators require every + variable to be mapped. Code evaluators always expose the fixed runtime + payload fields and Langfuse maps them automatically. + + + Versioning behavior: + + - `GET /evaluators` returns the latest version of each available + evaluator. + + - `GET /evaluators/{id}` can return an older version. + + - Evaluation rules always run against the latest version for the + selected evaluator name within the same source (`project` or `managed`). + unstableEvaluatorBase: + title: unstableEvaluatorBase + type: object + properties: + id: + type: string + description: Identifier of this evaluator. + name: + type: string + description: Evaluator name. + version: + type: integer + description: Version number of this evaluator. + scope: + $ref: '#/components/schemas/unstableEvaluatorScope' + description: >- + Where this evaluator comes from: your project or Langfuse-managed + defaults. + variables: + type: array + items: + type: string + description: >- + Variables that can be mapped when creating an evaluation rule. + + + LLM evaluators require every variable to be mapped exactly once. + Code evaluators always expose the fixed runtime payload fields and + Langfuse maps them automatically. + evaluationRuleCount: + type: integer + description: >- + Number of evaluation rules in the project that currently use this + evaluator version. + createdAt: + type: string + format: date-time + description: Timestamp when this evaluator was created. + updatedAt: + type: string + format: date-time + description: Timestamp when this evaluator was last updated. + required: + - id + - name + - version + - scope + - variables + - evaluationRuleCount + - createdAt + - updatedAt + unstableLlmAsJudgeEvaluator: + title: unstableLlmAsJudgeEvaluator + type: object + properties: + prompt: + type: string + description: Prompt template used during evaluation. + outputDefinition: + $ref: '#/components/schemas/unstablePublicEvaluatorOutputDefinition' + description: >- + Structured output schema returned by this evaluator. + + + Responses always include `dataType` and omit the internal + output-definition `version`. + + Use `dataType` to decide how future scores should be interpreted. + modelConfig: + $ref: '#/components/schemas/unstableEvaluatorModelConfig' + nullable: true + description: >- + Explicit model configuration, or `null` when the project default + evaluation model is used. + required: + - prompt + - outputDefinition + - modelConfig + allOf: + - $ref: '#/components/schemas/unstableEvaluatorBase' + unstableCodeEvaluator: + title: unstableCodeEvaluator + type: object + properties: + sourceCode: + type: string + description: Source code executed for each matched observation. + sourceCodeLanguage: + $ref: '#/components/schemas/unstableCodeEvaluatorSourceCodeLanguage' + description: Runtime language for `sourceCode`. + required: + - sourceCode + - sourceCodeLanguage + allOf: + - $ref: '#/components/schemas/unstableEvaluatorBase' + unstableEvaluators: + title: unstableEvaluators + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/unstableEvaluator' + meta: + $ref: '#/components/schemas/utilsMetaResponse' + required: + - data + - meta + unstableCreateEvaluatorRequest: + title: unstableCreateEvaluatorRequest + oneOf: + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - llm_as_judge + - $ref: '#/components/schemas/unstableCreateLlmAsJudgeEvaluatorRequest' + required: + - type + - type: object + allOf: + - type: object + properties: + type: + type: string + enum: + - code + - $ref: '#/components/schemas/unstableCreateCodeEvaluatorRequest' + required: + - type + description: >- + Request body for creating an evaluator. + + + If the same `name` already exists in your project, Langfuse creates the + next version and returns it. + + Existing evaluation rules in the same project are then moved to that new + latest version automatically. + + If `type` is omitted, Langfuse defaults it to `llm_as_judge` for + backwards compatibility. + unstableCreateLlmAsJudgeEvaluatorRequest: + title: unstableCreateLlmAsJudgeEvaluatorRequest + type: object + properties: + name: + type: string + description: Evaluator name within the authenticated project. + prompt: + type: string + description: Prompt template used by the evaluator. + outputDefinition: + $ref: '#/components/schemas/unstableEvaluatorOutputDefinition' + description: >- + Structured output schema the evaluator must return. + + + Always send `dataType`. + + Do not send `version`; it is an internal storage detail and not part + of the public request contract. + modelConfig: + $ref: '#/components/schemas/unstableEvaluatorModelConfig' + nullable: true + description: >- + Optional explicit model configuration. Omit or set to `null` to use + the project default evaluation model. + required: + - name + - prompt + - outputDefinition + unstableCreateCodeEvaluatorRequest: + title: unstableCreateCodeEvaluatorRequest + type: object + properties: + name: + type: string + description: Evaluator name within the authenticated project. + sourceCode: + type: string + description: Code executed for each matched observation. + sourceCodeLanguage: + $ref: '#/components/schemas/unstableCodeEvaluatorSourceCodeLanguage' + description: Runtime language for `sourceCode`. + required: + - name + - sourceCode + - sourceCodeLanguage + utilsMetaResponse: + title: utilsMetaResponse + type: object + properties: + page: + type: integer + description: current page number + limit: + type: integer + description: number of items per page + totalItems: + type: integer + description: number of total items given the current filters/selection (if any) + totalPages: + type: integer + description: number of total pages given the current limit + required: + - page + - limit + - totalItems + - totalPages + securitySchemes: + BasicAuth: + type: http + scheme: basic diff --git a/conformance/src/add-version.ts b/conformance/src/add-version.ts new file mode 100644 index 0000000..d424cc7 --- /dev/null +++ b/conformance/src/add-version.ts @@ -0,0 +1,328 @@ +import SwaggerParser from "@apidevtools/swagger-parser"; +import { mkdir, rm } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { parse } from "yaml"; + +import { compileApiContract } from "../../src/contracts/compiler"; +import { + CATALOG_PATH, + CONFORMANCE_ROOT, + REPOSITORY_ROOT, + readVerifiedSpec, + sha256, +} from "./catalog"; +import { compileOpenApi } from "./openapi"; +import type { Catalog, CatalogEntry } from "./types"; + +const README_PATH = resolve(CONFORMANCE_ROOT, "README.md"); +const USER_AGENT = "langfuse-cli-conformance-suite"; + +export interface SpecSummary { + version: string; + paths: number; + operations: number; +} + +interface AddVersionOptions { + dryRun: boolean; + runChecks: boolean; +} + +function usage(): never { + process.stderr.write(`Usage: bun run conformance:add-version -- vX.Y.Z [--dry-run]\n`); + process.exit(2); +} + +export function normalizeReleaseTag(input: string): { + tag: string; + version: string; +} { + const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(input); + if (!match) throw new Error(`Expected a stable release tag like v4.10.0, got ${input}`); + const version = `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`; + return { tag: `v${version}`, version }; +} + +function versionParts(version: string): [number, number, number] { + const normalized = normalizeReleaseTag(version).version; + return normalized.split(".").map(Number) as [number, number, number]; +} + +export function compareVersions(left: string, right: string): number { + const a = versionParts(left); + const b = versionParts(right); + for (let index = 0; index < 3; index++) { + if (a[index] !== b[index]) return a[index] - b[index]; + } + return 0; +} + +export function withCatalogEntry( + catalog: Catalog, + entry: CatalogEntry, +): Catalog { + if ( + catalog.versions.some( + (candidate) => candidate.version === entry.version || candidate.ref === entry.ref, + ) + ) { + throw new Error(`${entry.ref} is already present in the catalog`); + } + return { + ...catalog, + versions: [...catalog.versions, entry].sort((left, right) => + compareVersions(left.version, right.version), + ), + }; +} + +export function formatCatalog(catalog: Catalog): string { + return `${JSON.stringify(catalog, null, 2).replace( + /"knownIssues": \[\n\s+"([^"]+)"\n\s+\]/g, + '"knownIssues": ["$1"]', + )}\n`; +} + +export function updateConformanceReadme( + content: string, + summaries: SpecSummary[], +): string { + const total = summaries.reduce((sum, item) => sum + item.operations, 0); + let updated = content.replace( + /currently attempts all \d+ operations across .*? snapshots using/, + `currently attempts all ${total} operations across ${summaries.length} pinned snapshots using`, + ); + updated = updated.replace( + /passes all \d+ operations through/, + `passes all ${total} operations through`, + ); + const heading = "| Langfuse | Paths | Operations |"; + const tableStart = updated.indexOf(heading); + if (tableStart === -1) throw new Error("Could not find pinned-spec table in conformance README"); + const tableEnd = updated.indexOf("\n\n", tableStart); + if (tableEnd === -1) throw new Error("Could not find end of pinned-spec table"); + const table = [ + heading, + "|---|---:|---:|", + ...summaries.map( + (summary) => + `| ${summary.version} | ${summary.paths} | ${summary.operations} |`, + ), + ].join("\n"); + return `${updated.slice(0, tableStart)}${table}${updated.slice(tableEnd)}`; +} + +function githubHeaders(): HeadersInit { + const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN; + return { + accept: "application/vnd.github+json", + "user-agent": USER_AGENT, + ...(token ? { authorization: `Bearer ${token}` } : {}), + }; +} + +async function githubJson(path: string): Promise { + const response = await fetch(`https://api.github.com${path}`, { + headers: githubHeaders(), + }); + if (!response.ok) { + throw new Error(`GitHub ${path}: ${response.status} ${response.statusText}`); + } + return response.json(); +} + +async function resolveStableRelease(tag: string): Promise { + const release = await githubJson( + `/repos/langfuse/langfuse/releases/tags/${encodeURIComponent(tag)}`, + ); + if (release.draft || release.prerelease) { + throw new Error(`${tag} is not a stable published release`); + } + const ref = await githubJson( + `/repos/langfuse/langfuse/git/ref/tags/${encodeURIComponent(tag)}`, + ); + let object = ref.object; + for (let depth = 0; object?.type === "tag" && depth < 5; depth++) { + object = (await githubJson(`/repos/langfuse/langfuse/git/tags/${object.sha}`)) + .object; + } + if (object?.type !== "commit" || !/^[0-9a-f]{40}$/.test(object.sha)) { + throw new Error(`Could not resolve ${tag} to an immutable commit`); + } + return object.sha; +} + +async function downloadSpec(catalog: Catalog, commit: string): Promise { + const repository = catalog.repository.replace("https://github.com/", ""); + const url = `https://raw.githubusercontent.com/${repository}/${commit}/${catalog.specPath}`; + const response = await fetch(url, { headers: { "user-agent": USER_AGENT } }); + if (!response.ok) { + throw new Error(`Spec download failed: ${response.status} ${response.statusText}`); + } + return response.text(); +} + +function replaceConstWithEnum(value: any, seen = new WeakSet()): boolean { + if (!value || typeof value !== "object" || seen.has(value)) return false; + seen.add(value); + let changed = false; + if (!Array.isArray(value) && Object.hasOwn(value, "const")) { + value.enum = [value.const]; + delete value.const; + changed = true; + } + for (const child of Object.values(value)) { + changed = replaceConstWithEnum(child, seen) || changed; + } + return changed; +} + +async function knownIssues(raw: string): Promise { + const document = parse(raw, { + maxAliasCount: 100_000, + uniqueKeys: true, + }) as Record; + try { + await SwaggerParser.validate(structuredClone(document) as any); + return undefined; + } catch (originalError) { + const patched = structuredClone(document); + if (!replaceConstWithEnum(patched)) throw originalError; + await SwaggerParser.validate(patched as any); + return ["oas3.0-const-keyword"]; + } +} + +async function specSummaries( + catalog: Catalog, + newEntry: CatalogEntry, + newRaw: string, +): Promise { + return Promise.all( + catalog.versions.map(async (entry) => { + const raw = entry.version === newEntry.version + ? newRaw + : await readVerifiedSpec(entry); + const compiled = compileOpenApi(entry, raw); + return { + version: entry.version, + paths: Object.keys(compiled.document.paths ?? {}).length, + operations: compiled.manifest.operations.length, + }; + }), + ); +} + +async function run(command: string[], label: string): Promise { + process.stdout.write(`\n${label}\n`); + const child = Bun.spawn(command, { + cwd: REPOSITORY_ROOT, + stdout: "inherit", + stderr: "inherit", + }); + const code = await child.exited; + if (code !== 0) throw new Error(`${label} failed with exit code ${code}`); +} + +async function addVersion( + input: string, + options: AddVersionOptions, +): Promise { + const { tag, version } = normalizeReleaseTag(input); + const originalCatalogText = await Bun.file(CATALOG_PATH).text(); + const originalReadme = await Bun.file(README_PATH).text(); + const catalog = JSON.parse(originalCatalogText) as Catalog; + if (catalog.versions.some((entry) => entry.version === version)) { + throw new Error(`${tag} is already bundled`); + } + + process.stdout.write(`Resolving ${tag}\n`); + const commit = await resolveStableRelease(tag); + const raw = await downloadSpec(catalog, commit); + const issues = await knownIssues(raw); + const entry: CatalogEntry = { + version, + ref: tag, + commit, + sha256: await sha256(raw), + ...(issues ? { knownIssues: issues } : {}), + }; + const compiled = compileOpenApi(entry, raw); + const contract = compileApiContract(entry, raw); + if (compiled.unsupported.length > 0) { + throw new Error(`Unsupported OpenAPI features: ${compiled.unsupported.join(", ")}`); + } + if (compiled.manifest.operations.length !== contract.operations.length) { + throw new Error("Conformance and runtime compilers disagree on operation count"); + } + const updatedCatalog = withCatalogEntry(catalog, entry); + const summaries = await specSummaries(updatedCatalog, entry, raw); + const updatedReadme = updateConformanceReadme(originalReadme, summaries); + process.stdout.write( + `${tag} -> ${commit}\nSHA-256 ${entry.sha256}\n${compiled.manifest.operations.length} operations\n`, + ); + if (options.dryRun) { + process.stdout.write("Dry run complete; no files changed\n"); + return; + } + + const path = resolve(CONFORMANCE_ROOT, "specs", version, "openapi.yml"); + if (await Bun.file(path).exists()) { + throw new Error(`${path} already exists but ${tag} is not cataloged`); + } + await mkdir(dirname(path), { recursive: true }); + try { + await Bun.write(path, raw); + await Bun.write(CATALOG_PATH, formatCatalog(updatedCatalog)); + await Bun.write(README_PATH, updatedReadme); + if (options.runChecks) { + await run(["bun", "run", "typecheck"], "Typecheck"); + await run(["bun", "test"], "Test suite"); + await run(["bun", "run", "build"], "Build contracts"); + await run( + [ + "bun", + "run", + "conformance:run", + "--", + "--version", + version, + "--adapter", + "contract-v1", + "--", + "bun", + "bin/langfuse.mjs", + "--api-version", + version, + ], + `Conformance ${version}`, + ); + } + } catch (error) { + await Bun.write(CATALOG_PATH, originalCatalogText); + await Bun.write(README_PATH, originalReadme); + await rm(resolve(CONFORMANCE_ROOT, "specs", version), { + recursive: true, + force: true, + }); + throw error; + } + process.stdout.write(`\nAdded ${tag}. Review and live-test changed endpoints before commit.\n`); +} + +async function main(): Promise { + const args = process.argv.slice(2); + const input = args.find((arg) => !arg.startsWith("--")); + if (!input || args.some((arg) => ![input, "--dry-run"].includes(arg))) usage(); + await addVersion(input, { + dryRun: args.includes("--dry-run"), + runChecks: true, + }); +} + +if (import.meta.main) { + main().catch((error) => { + process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`); + process.exitCode = 1; + }); +} diff --git a/conformance/tests/add-version.test.ts b/conformance/tests/add-version.test.ts new file mode 100644 index 0000000..b2bcd1d --- /dev/null +++ b/conformance/tests/add-version.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, test } from "bun:test"; + +import { + compareVersions, + formatCatalog, + normalizeReleaseTag, + updateConformanceReadme, + withCatalogEntry, +} from "../src/add-version"; +import type { Catalog, CatalogEntry } from "../src/types"; + +const entry = (version: string): CatalogEntry => ({ + version, + ref: `v${version}`, + commit: version.replaceAll(".", "").padEnd(40, "0"), + sha256: version.replaceAll(".", "").padEnd(64, "0"), +}); + +describe("add-version workflow", () => { + test("normalizes only stable semantic release tags", () => { + expect(normalizeReleaseTag("v4.10.0")).toEqual({ + tag: "v4.10.0", + version: "4.10.0", + }); + expect(normalizeReleaseTag("4.011.0")).toEqual({ + tag: "v4.11.0", + version: "4.11.0", + }); + expect(() => normalizeReleaseTag("v4.11.0-rc.1")).toThrow( + "Expected a stable release tag", + ); + }); + + test("inserts catalog entries in semantic order and rejects duplicates", () => { + const catalog: Catalog = { + schemaVersion: 1, + repository: "https://github.com/langfuse/langfuse", + specPath: "openapi.yml", + versions: [entry("3.216.0"), entry("4.10.0")], + }; + const updated = withCatalogEntry(catalog, entry("4.9.0")); + expect(updated.versions.map((item) => item.version)).toEqual([ + "3.216.0", + "4.9.0", + "4.10.0", + ]); + expect(compareVersions("4.10.0", "4.9.0")).toBeGreaterThan(0); + expect(() => withCatalogEntry(updated, entry("4.10.0"))).toThrow( + "already present", + ); + expect(formatCatalog({ + ...updated, + versions: [{ ...entry("4.10.0"), knownIssues: ["known-issue"] }], + })).toContain('"knownIssues": ["known-issue"]'); + }); + + test("regenerates operation totals and the pinned-spec table", () => { + const input = `The suite currently attempts all 10 operations across 2 snapshots using X. +The native adapter passes all 10 operations through JSON. + +| Langfuse | Paths | Operations | +|---|---:|---:| +| 1.0.0 | 1 | 4 | +| 2.0.0 | 2 | 6 | + +After table. +`; + expect( + updateConformanceReadme(input, [ + { version: "1.0.0", paths: 1, operations: 4 }, + { version: "2.0.0", paths: 2, operations: 6 }, + { version: "3.0.0", paths: 3, operations: 8 }, + ]), + ).toContain("all 18 operations across 3 pinned snapshots"); + expect(updateConformanceReadme(input, [ + { version: "3.0.0", paths: 3, operations: 8 }, + ])).toContain("| 3.0.0 | 3 | 8 |"); + }); +}); From a950ce766442fddb56f392490b018928c4d9bfee Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 21:55:02 +0200 Subject: [PATCH 05/11] more simp --- src/cli.test.ts | 59 +++++++++++++++++++++++++++++++++++++++ src/cli.ts | 41 +++++++++++---------------- src/contracts/compiler.ts | 13 +-------- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/src/cli.test.ts b/src/cli.test.ts index 1a954b8..db2e581 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -176,6 +176,65 @@ paths: await parseOperationInput(operation, ["--customModels", "123"]), ).toMatchObject({ body: { customModels: ["123"] } }); }); + + test("rejects nested body flags without consuming a positional", async () => { + const operation: ApiOperation = { + ...promptGet, + key: "PATCH /api/public/widgets/{widgetId}", + operationId: "widgets_update", + method: "PATCH", + path: "/api/public/widgets/{widgetId}", + command: { + resource: "widgets", + action: "update", + canonicalAction: "update", + }, + pathParameterOrder: ["widgetId"], + parameters: [ + { + location: "path", + name: "widgetId", + cliName: "widget-id", + required: true, + style: "simple", + explode: false, + kind: "string", + }, + ], + requestBody: { + required: true, + contentType: "application/json", + legacyFieldFlags: true, + fields: [ + { + name: "chartConfig", + required: false, + kind: "object", + }, + ], + }, + }; + + await expect( + parseOperationInput(operation, [ + "--chartConfig.show_value_labels", + "widget-123", + ]), + ).rejects.toThrow( + "Nested body option --chartConfig.show_value_labels is unsupported; pass --chartConfig with a JSON object or use --body-json", + ); + + expect( + await parseOperationInput(operation, [ + "widget-123", + "--chartConfig", + '{"show_value_labels":true}', + ]), + ).toMatchObject({ + path: { widgetId: "widget-123" }, + body: { chartConfig: { show_value_labels: true } }, + }); + }); }); describe("result output", () => { diff --git a/src/cli.ts b/src/cli.ts index 0f4da49..efd8c78 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -376,35 +376,22 @@ function addParameterValue( function setBodyValue( body: Record, - path: string[], raw: string | undefined, - field?: ApiBodyField, + field: ApiBodyField, ): void { - let target = body; - for (const segment of path.slice(0, -1)) { - const existing = target[segment]; - if (!existing || typeof existing !== "object" || Array.isArray(existing)) { - target[segment] = {}; - } - target = target[segment] as Record; - } - const name = path.at(-1)!; - const kind = - path.length > 1 - ? undefined - : field?.kind === "array" - ? field.itemKind - : field?.kind; + const kind = field.kind === "array" ? field.itemKind : field.kind; const parsed = parseJsonValue(raw ?? "true", kind); - const existing = target[name]; - if (field?.kind === "array") { - if (Array.isArray(parsed)) target[name] = parsed; + const existing = body[field.name]; + if (field.kind === "array") { + if (Array.isArray(parsed)) body[field.name] = parsed; else if (Array.isArray(existing)) existing.push(parsed); - else target[name] = [parsed]; + else body[field.name] = [parsed]; } else if (existing !== undefined) { - target[name] = Array.isArray(existing) ? [...existing, parsed] : [existing, parsed]; + body[field.name] = Array.isArray(existing) + ? [...existing, parsed] + : [existing, parsed]; } else { - target[name] = parsed; + body[field.name] = parsed; } } @@ -467,6 +454,11 @@ export async function parseOperationInput( (candidate) => candidate.name === option.name.split(".")[0], ) : undefined; + if (bodyField && option.name.includes(".")) { + throw new CliError( + `Nested body option --${option.name} is unsupported; pass --${bodyField.name} with a JSON object or use --body-json`, + ); + } const isBoolean = parameter?.kind === "boolean" || bodyField?.kind === "boolean"; let raw = option.inline; @@ -509,7 +501,6 @@ export async function parseOperationInput( `${operation.operationId} requires --body-json or --body-file for request bodies`, ); } - const path = option.name.split(".").filter(Boolean); const field = bodyField; if (!field) throw new CliError(`Unknown option --${option.name}`); if (option.negated && field.kind !== "boolean") { @@ -519,7 +510,7 @@ export async function parseOperationInput( throw new CliError(`--${option.name} requires a value`); } fieldBody ??= {}; - setBodyValue(fieldBody, path, option.negated ? "false" : raw, field); + setBodyValue(fieldBody, option.negated ? "false" : raw, field); } if (completeBody !== undefined && fieldBody !== undefined) { throw new CliError("Do not mix --body-json/--body-file with body field flags"); diff --git a/src/contracts/compiler.ts b/src/contracts/compiler.ts index 543d2f2..7e44b6d 100644 --- a/src/contracts/compiler.ts +++ b/src/contracts/compiler.ts @@ -1,6 +1,6 @@ import { parse } from "yaml"; -import { planCommandNames } from "../../conformance/src/naming"; +import { kebabCase, planCommandNames } from "../../conformance/src/naming"; import type { ApiBodyField, ApiContract, @@ -49,17 +49,6 @@ const LEGACY_FIELD_FLAGS_UNSUPPORTED = new Set([ "unstable_evaluators_create", ]); -function kebabCase(input: string): string { - return input - .trim() - .replace(/([a-z0-9])([A-Z])/g, "$1-$2") - .replace(/[\s_.:/]+/g, "-") - .replace(/[^a-zA-Z0-9-]/g, "-") - .replace(/-+/g, "-") - .replace(/^-|-$/g, "") - .toLowerCase(); -} - function resolveLocalRef( document: Record, value: Record, From 33dae50f33446f6e262279677897c025f3147e35 Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 22:08:32 +0200 Subject: [PATCH 06/11] block deprecated APIs --- README.md | 6 +-- conformance/README.md | 14 +++--- conformance/catalog.json | 4 +- conformance/specs/4.10.0/openapi.yml | 15 ++++++ conformance/src/catalog.ts | 12 ++++- conformance/src/openapi.ts | 2 + conformance/src/runner.ts | 35 ++++++++++--- conformance/src/types.ts | 3 ++ conformance/tests/catalog.test.ts | 28 ++++++++++- conformance/tests/multi-version.test.ts | 10 +++- conformance/tests/runner.test.ts | 33 +++++++++++++ conformance/tests/schema-validation.test.ts | 2 +- src/cli.test.ts | 33 ++++++++++++- src/cli.ts | 54 ++++++++++++++++++--- src/contracts/compiler.ts | 2 + src/contracts/types.ts | 1 + 16 files changed, 224 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c4f7713..29a681e 100644 --- a/README.md +++ b/README.md @@ -106,20 +106,20 @@ See the full [Langfuse API Reference](https://api.reference.langfuse.com/). ## OpenAPI conformance suite -The version-pinned black-box suite lives in [`conformance/`](conformance/README.md). It makes one minimally valid mocked API call per operation through the real CLI across historical Langfuse specs. +The version-pinned black-box suite lives in [`conformance/`](conformance/README.md). It invokes every operation through the real CLI across historical Langfuse specs. Active operations make one minimally valid mocked API call; operations marked `deprecated: true` must fail before any network request. ```sh bun test bun run conformance:all ``` -`bun test` verifies the generator, schemas, serialization, capture oracle, and legacy CLI compatibility. `bun run conformance:all` builds the package and fake-calls every operation through the native CLI using its lossless JSON input path. CI runs both. +`bun test` verifies the generator, schemas, serialization, capture oracle, deprecation policy, and legacy CLI compatibility. `bun run conformance:all` builds the package and checks every operation through the native CLI using its lossless JSON input path. CI runs both. ## Native OpenAPI contracts The CLI is implemented in TypeScript and runs natively on Bun. It has zero external runtime dependencies and never parses OpenAPI during invocation. -Builds compile the committed, byte-for-byte upstream OpenAPI snapshots into compact versioned contracts under ignored `dist/contracts/`. Generated contracts are packaged on npm but are not committed. +Builds compile committed OpenAPI snapshots into compact versioned contracts under ignored `dist/contracts/`. Catalog entries record the exact committed hash; snapshots with explicit local annotations also record the upstream hash and modification name. Generated contracts are packaged on npm but are not committed. ```sh # Build the Bun CLI and all versioned contracts diff --git a/conformance/README.md b/conformance/README.md index 5fdb09c..19b0e0d 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -4,15 +4,17 @@ Language-neutral, version-pinned acceptance tests for the native TypeScript CLI. ## What is tested -The primary test fake-calls every operation in every cataloged spec through the real CLI. For each operation it: +The primary test invokes every operation in every cataloged spec through the real CLI. For active operations it: -- generates one minimally valid invocation from the untouched OpenAPI source +- generates one minimally valid invocation from the committed OpenAPI source - starts a local mock HTTP server with a response generated from that operation - runs the CLI as a subprocess against the mock host - compares the received method, path, query, headers, authentication, and JSON body - compares the CLI's response status, body, and exit status -The black-box oracle does not share request-building code with the CLI. `bun test` currently attempts all 792 operations across 9 pinned snapshots using the historical field-flag adapter. Operations that require lossless JSON bodies remain an explicit compatibility baseline; any additional failure fails the test. The native `contract-v1` adapter passes all 792 operations through `--body-json`. +For operations marked `deprecated: true`, it instead verifies exit code 2, a helpful error on stderr, and zero network requests. + +The black-box oracle does not share request-building code with the CLI. `bun test` currently attempts all 792 operations across 9 pinned snapshots using the historical field-flag adapter. Operations that require lossless JSON bodies remain an explicit compatibility baseline; any additional failure fails the test. The native `contract-v1` adapter checks all 792 operations through `--body-json`, including pre-network rejection for deprecated operations. Supporting unit tests verify immutable spec hashes, valid sampling, serialization, naming, adapters, and the capture runner itself. @@ -41,7 +43,7 @@ Some pinned specs use the JSON Schema `const` keyword while declaring OpenAPI 3. ```text catalog.json immutable Git refs, commits, hashes, known source issues policy.json implementation adapters; not API truth -specs//openapi.yml untouched upstream snapshots +specs//openapi.yml committed source snapshots src/ compiler, serializers, adapters, capture runner tests/ compiler, validator, and runner tests ``` @@ -52,7 +54,7 @@ tests/ compiler, validator, and runner tests # Generator, schema, serializer, capture, and compatibility tests bun test -# Build and fake-call every endpoint through the lossless native CLI +# Build and check every endpoint through the lossless native CLI bun run conformance:all # Re-download pinned bytes and verify their hashes @@ -79,7 +81,7 @@ bun run conformance:run -- \ --current-cli ``` -The adapter name is retained because it describes the old field-flag grammar. The runner builds the native current source and compiles the selected untouched spec into a temporary runtime contract. +The adapter name is retained because it describes the old field-flag grammar. The runner builds the native current source and compiles the selected committed spec into a temporary runtime contract. Useful filters: diff --git a/conformance/catalog.json b/conformance/catalog.json index 839ff59..d78c1c0 100644 --- a/conformance/catalog.json +++ b/conformance/catalog.json @@ -58,7 +58,9 @@ "version": "4.10.0", "ref": "v4.10.0", "commit": "ddc198b49f1439f95ecf674c68863bfb0c2bd07f", - "sha256": "fa4d21a83c49bd0bc3d3f2f751e12fbfce96f72b422267726606ff67bfacf01f", + "sha256": "bcf95d11960571222d9ee47e4a0e5d2eba667d14802cf27539e4c0ff3d9352a4", + "upstreamSha256": "fa4d21a83c49bd0bc3d3f2f751e12fbfce96f72b422267726606ff67bfacf01f", + "modifications": ["deprecated-operation-markers"], "knownIssues": ["oas3.0-const-keyword"] } ] diff --git a/conformance/specs/4.10.0/openapi.yml b/conformance/specs/4.10.0/openapi.yml index 347afcb..dd5f151 100644 --- a/conformance/specs/4.10.0/openapi.yml +++ b/conformance/specs/4.10.0/openapi.yml @@ -1179,6 +1179,7 @@ paths: application/json: schema: $ref: '#/components/schemas/CreateDatasetRunItemRequest' + deprecated: true get: description: List dataset run items operationId: datasetRunItems_list @@ -1242,6 +1243,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/v2/datasets: get: description: Get all datasets @@ -1436,6 +1438,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true delete: description: Delete a dataset run and all its run items. This action is irreversible. operationId: datasets_deleteRun @@ -1485,6 +1488,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/datasets/{datasetName}/runs: get: description: Get dataset runs @@ -1544,6 +1548,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/experiments: get: description: |- @@ -1985,6 +1990,7 @@ paths: debugging. required: - batch + deprecated: true /api/public/metrics: get: description: >- @@ -2086,6 +2092,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/observations/{observationId}: get: description: Get a observation @@ -2134,6 +2141,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/observations: get: description: >- @@ -2422,6 +2430,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/scores/{scoreId}: delete: description: Delete a score (supports both trace and session scores) @@ -5973,6 +5982,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/v2/scores/{scoreId}: get: description: |- @@ -6023,6 +6033,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/sessions: get: description: >- @@ -6122,6 +6133,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/sessions/{sessionId}: get: description: >- @@ -6180,6 +6192,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true /api/public/traces/{traceId}: get: description: Get a specific trace @@ -6239,6 +6252,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true delete: description: Delete a specific trace operationId: trace_delete @@ -6611,6 +6625,7 @@ paths: application/json: schema: {} security: *ref_0 + deprecated: true delete: description: Delete multiple traces operationId: trace_deleteMultiple diff --git a/conformance/src/catalog.ts b/conformance/src/catalog.ts index 40b5e7d..c1c36ef 100644 --- a/conformance/src/catalog.ts +++ b/conformance/src/catalog.ts @@ -60,12 +60,20 @@ export async function syncSpecs(entries?: CatalogEntry[]): Promise { } const text = await response.text(); const actual = await sha256(text); - if (actual !== entry.sha256) { + const expectedUpstream = entry.upstreamSha256 ?? entry.sha256; + if (actual !== expectedUpstream) { throw new Error( - `${entry.ref}: upstream bytes changed; expected ${entry.sha256}, got ${actual}`, + `${entry.ref}: upstream bytes changed; expected ${expectedUpstream}, got ${actual}`, ); } const path = specPath(entry); + if (entry.modifications?.length) { + await readVerifiedSpec(entry); + process.stdout.write( + `verified ${entry.ref} (${entry.modifications.join(", ")})\n`, + ); + continue; + } await mkdir(dirname(path), { recursive: true }); if (!(await Bun.file(path).exists()) || (await Bun.file(path).text()) !== text) { await Bun.write(path, text); diff --git a/conformance/src/openapi.ts b/conformance/src/openapi.ts index fa1cfcc..c4fa90b 100644 --- a/conformance/src/openapi.ts +++ b/conformance/src/openapi.ts @@ -29,6 +29,7 @@ interface RawOperation { operationId: string; method: HttpMethod; path: string; + deprecated?: true; tags: string[]; auth: { required: boolean; @@ -213,6 +214,7 @@ export function compileOpenApi( operationId, method: method.toUpperCase() as HttpMethod, path, + ...(operation.deprecated === true ? { deprecated: true as const } : {}), tags: (operation.tags ?? []).map(String), auth: normalizeAuth(document, operation, unsupported), pathParameterOrder: pathParameterOrder(path), diff --git a/conformance/src/runner.ts b/conformance/src/runner.ts index b228f49..a22454e 100644 --- a/conformance/src/runner.ts +++ b/conformance/src/runner.ts @@ -178,16 +178,37 @@ export async function runConformance(options: RunOptions): Promise }); const execution = await spawn(command, args, options.timeoutMs ?? 10_000); const failures: string[] = []; - if (execution.exitCode !== 0) { - failures.push(`exit: expected zero, got ${execution.exitCode}`); - } const captured = capture.requests.slice(before); - if (captured.length !== 1) { - failures.push(`server: expected one request, got ${captured.length}`); + const operation = options.manifest.operations.find( + (candidate) => candidate.key === vector.operationKey, + ); + if (!operation) { + failures.push("manifest: operation not found"); + } else if (operation.deprecated) { + if (execution.exitCode !== 2) { + failures.push( + `exit: expected deprecated-operation exit 2, got ${execution.exitCode}`, + ); + } + if (captured.length !== 0) { + failures.push( + `server: expected no request for deprecated operation, got ${captured.length}`, + ); + } + if (!execution.stderr.includes("Cannot call deprecated API operation")) { + failures.push("stderr: expected a helpful deprecated-operation error"); + } } else { - failures.push(...requestDiff(vector.expectedRequest, captured[0])); + if (execution.exitCode !== 0) { + failures.push(`exit: expected zero, got ${execution.exitCode}`); + } + if (captured.length !== 1) { + failures.push(`server: expected one request, got ${captured.length}`); + } else { + failures.push(...requestDiff(vector.expectedRequest, captured[0])); + } } - if (execution.exitCode === 0) { + if (!operation?.deprecated && execution.exitCode === 0) { const output = parseJson(execution.stdout); if (output?.status !== vector.response.status) { failures.push( diff --git a/conformance/src/types.ts b/conformance/src/types.ts index 17cafac..5240ea0 100644 --- a/conformance/src/types.ts +++ b/conformance/src/types.ts @@ -19,6 +19,8 @@ export interface CatalogEntry { ref: string; commit: string; sha256: string; + upstreamSha256?: string; + modifications?: string[]; knownIssues?: string[]; } @@ -64,6 +66,7 @@ export interface OperationContract { operationId: string; method: HttpMethod; path: string; + deprecated?: true; auth: { required: boolean; schemes: string[]; diff --git a/conformance/tests/catalog.test.ts b/conformance/tests/catalog.test.ts index c2bd79e..6c7b478 100644 --- a/conformance/tests/catalog.test.ts +++ b/conformance/tests/catalog.test.ts @@ -28,7 +28,7 @@ describe("immutable OpenAPI catalog", () => { } }); - test("each operation produces one supported endpoint call", async () => { + test("each operation produces one supported invocation", async () => { const catalog = await loadCatalog(); for (const entry of catalog.versions) { const corpus = await generateCorpus(entry); @@ -37,6 +37,32 @@ describe("immutable OpenAPI catalog", () => { corpus.compiled.manifest.operations.length, ); expect(corpus.vectors.length).toBeGreaterThan(0); + if (entry.version === "4.10.0") { + expect( + corpus.compiled.manifest.operations + .filter((operation) => operation.deprecated) + .map((operation) => operation.operationId) + .sort(), + ).toEqual( + [ + "datasetRunItems_create", + "datasetRunItems_list", + "datasets_deleteRun", + "datasets_getRun", + "datasets_getRuns", + "ingestion_batch", + "legacy_metricsV1_metrics", + "legacy_observationsV1_get", + "legacy_observationsV1_getMany", + "scores_get-by-id", + "scores_get-many", + "sessions_get", + "sessions_list", + "trace_get", + "trace_list", + ].sort(), + ); + } } }); }); diff --git a/conformance/tests/multi-version.test.ts b/conformance/tests/multi-version.test.ts index 9a93544..9257034 100644 --- a/conformance/tests/multi-version.test.ts +++ b/conformance/tests/multi-version.test.ts @@ -27,7 +27,7 @@ const CURRENT_CLI_UNSUPPORTED_OPERATIONS = new Set([ ]); describe("multi-version black-box matrix", () => { - test("fake-calls every endpoint through the real CLI", async () => { + test("checks every endpoint through the real CLI", async () => { const catalog = await loadCatalog(); await Promise.all(catalog.versions.map(async (entry) => { const corpus = await generateCorpus(entry); @@ -43,9 +43,15 @@ describe("multi-version black-box matrix", () => { }); expect(results).toHaveLength(vectors.length); + const deprecatedOperationIds = new Set( + corpus.compiled.manifest.operations + .filter((operation) => operation.deprecated) + .map((operation) => operation.operationId), + ); const expectedFailures = vectors .filter((vector) => - CURRENT_CLI_UNSUPPORTED_OPERATIONS.has(vector.operationId ?? ""), + CURRENT_CLI_UNSUPPORTED_OPERATIONS.has(vector.operationId ?? "") && + !deprecatedOperationIds.has(vector.operationId ?? ""), ) .map((vector) => vector.id); const actualFailures = results diff --git a/conformance/tests/runner.test.ts b/conformance/tests/runner.test.ts index 04fab44..11c1ebd 100644 --- a/conformance/tests/runner.test.ts +++ b/conformance/tests/runner.test.ts @@ -76,4 +76,37 @@ process.exit(response.ok ? 0 : 1); expect(results[0].failures).toEqual([]); expect(results[0].passed).toBe(true); }); + + test("expects deprecated operations to fail before making a request", async () => { + directory = await mkdtemp(join(tmpdir(), "langfuse-cli-deprecated-")); + const script = resolve(directory, "fake-cli.ts"); + await Bun.write( + script, + `console.error("Cannot call deprecated API operation"); +process.exit(2); +`, + ); + const entry = { + version: "fixture", + ref: "fixture", + commit: "0".repeat(40), + sha256: "0".repeat(64), + }; + const compiled = compileOpenApi( + entry, + raw.replace("tags: [Widgets]", "tags: [Widgets]\n deprecated: true"), + ); + const vector = generateVectors(compiled)[0]; + const results = await runConformance({ + entry, + manifest: compiled.manifest, + vectors: [vector], + adapter: "contract-v1", + command: ["bun", script], + }); + + expect(results).toHaveLength(1); + expect(results[0].failures).toEqual([]); + expect(results[0].passed).toBe(true); + }); }); diff --git a/conformance/tests/schema-validation.test.ts b/conformance/tests/schema-validation.test.ts index 7d89bc6..63a8d9e 100644 --- a/conformance/tests/schema-validation.test.ts +++ b/conformance/tests/schema-validation.test.ts @@ -58,7 +58,7 @@ function parameterValue( } describe("generated samples against original OpenAPI schemas", () => { - test("every generated endpoint call is valid against its untouched spec", async () => { + test("every generated endpoint call is valid against its committed spec", async () => { const catalog = await loadCatalog(); for (const entry of catalog.versions) { const { compiled, vectors } = await generateCorpus(entry); diff --git a/src/cli.test.ts b/src/cli.test.ts index db2e581..d5832bf 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -3,7 +3,13 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { parseOperationInput, run, writeResult } from "./cli"; +import { + assertOperationCallable, + parseOperationInput, + run, + schemaOutput, + writeResult, +} from "./cli"; import { compileApiContract } from "./contracts/compiler"; import type { ApiOperation } from "./contracts/types"; @@ -149,6 +155,7 @@ paths: put: operationId: models_put tags: [Models] + deprecated: true security: [] requestBody: required: true @@ -171,12 +178,36 @@ paths: (candidate) => candidate.name === "customModels", ); + expect(operation.deprecated).toBe(true); expect(field?.itemKind).toBe("string"); expect( await parseOperationInput(operation, ["--customModels", "123"]), ).toMatchObject({ body: { customModels: ["123"] } }); }); + test("rejects deprecated operations with replacement guidance", () => { + const operation: ApiOperation = { + ...promptGet, + deprecated: true, + description: "**Deprecated.** Use `GET /api/public/v3/prompts` instead.", + }; + + expect(() => assertOperationCallable(operation, "4.10.0")).toThrow( + 'Cannot call deprecated API operation "prompts get"', + ); + expect(() => assertOperationCallable(operation, "4.10.0")).toThrow( + "Use `GET /api/public/v3/prompts` instead.", + ); + + const schema = schemaOutput({ + schemaVersion: 1, + apiVersion: "4.10.0", + sourceSha256: "test", + operations: [operation], + }); + expect(schema.resources[0].actions[0].deprecated).toBe(true); + }); + test("rejects nested body flags without consuming a positional", async () => { const operation: ApiOperation = { ...promptGet, diff --git a/src/cli.ts b/src/cli.ts index efd8c78..8a1266a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -214,18 +214,25 @@ function resourceMap(contract: ApiContract): Map { } function printApiHelp(contract: ApiContract): void { - const resources = [...resourceMap(contract).keys()].sort(); + const resources = [...resourceMap(contract)].sort(([left], [right]) => + left.localeCompare(right), + ); process.stdout.write(`Usage: langfuse api [options] API snapshot: ${contract.apiVersion} Resources: -${resources.map((resource) => ` ${resource}`).join("\n")} +${resources + .map( + ([resource, operations]) => + ` ${resource}${operations.some((operation) => operation.deprecated) ? " [contains deprecated actions]" : ""}`, + ) + .join("\n")} Discovery: api help [resource] [action] api schema --json Machine-readable command schema - api __schema --json Backward-compatible alias + api __schema --json Legacy command alias api versions list Bundled historical snapshots Action options: @@ -244,13 +251,41 @@ function printResourceHelp(contract: ApiContract, resource: string): void { Actions: ${operations .map( - (operation) => - ` ${operation.command.action.padEnd(30)} ${operation.summary ?? operation.operationId}`, + (operation) => { + const label = `${operation.command.action}${operation.deprecated ? " [deprecated]" : ""}`; + return ` ${label.padEnd(43)} ${operation.summary ?? operation.operationId}`; + }, ) .join("\n")} `); } +function explicitDeprecationNote(operation: ApiOperation): string | undefined { + const description = operation.description?.trim(); + if (!description || !/^(?:\*\*)?deprecated\b/i.test(description)) { + return undefined; + } + return description + .split(/\n\s*\n/, 1)[0] + .replace(/^\*\*Deprecated\.\*\*\s*/i, "") + .replace(/^Deprecated\.?\s*/i, "") + .replace(/\s*\n\s*/g, " ") + .trim(); +} + +export function assertOperationCallable( + operation: ApiOperation, + apiVersion: string, +): void { + if (!operation.deprecated) return; + const note = explicitDeprecationNote(operation); + throw new CliError( + `Cannot call deprecated API operation "${operation.command.resource} ${operation.command.action}" (${operation.method} ${operation.path}) in API ${apiVersion}.` + + (note ? ` ${note}` : " No replacement is declared in its OpenAPI description.") + + ` Use "langfuse api help ${operation.command.resource}" or "langfuse api schema --json" to find supported operations.`, + ); +} + function kindLabel(kind: ValueKind): string { return kind === "array" ? "value (repeatable)" : kind; } @@ -287,6 +322,11 @@ function printOperationHelp(operation: ApiOperation): void { ${operation.summary ?? operation.operationId} ${operation.description ? `\n${operation.description}\n` : ""} +${ + operation.deprecated + ? `\nDEPRECATED\nThis operation is discoverable but cannot be called by this CLI.${explicitDeprecationNote(operation) ? ` ${explicitDeprecationNote(operation)}` : ""}\n` + : "" +} Options: ${lines.length ? lines.join("\n") : " (no operation-specific options)"} --json JSON response envelope @@ -558,7 +598,7 @@ export async function parseOperationInput( return input; } -function schemaOutput(contract: ApiContract) { +export function schemaOutput(contract: ApiContract) { return { schemaVersion: 1, apiVersion: contract.apiVersion, @@ -574,6 +614,7 @@ function schemaOutput(contract: ApiContract) { operationId: operation.operationId, method: operation.method, path: operation.path, + deprecated: Boolean(operation.deprecated), auth: operation.auth, pathParameterOrder: operation.pathParameterOrder, parameters: operation.parameters, @@ -679,6 +720,7 @@ async function runApi(config: RuntimeConfig, args: string[]): Promise { printOperationHelp(operation); return; } + assertOperationCallable(operation, contract.apiVersion); const input = await parseOperationInput(operation, args.slice(2)); const client = createApiClient({ host: config.host, diff --git a/src/contracts/compiler.ts b/src/contracts/compiler.ts index 7e44b6d..e301738 100644 --- a/src/contracts/compiler.ts +++ b/src/contracts/compiler.ts @@ -263,6 +263,7 @@ export function compileApiContract( operationId: string; method: HttpMethod; path: string; + deprecated?: true; tags: string[]; auth: ApiOperation["auth"]; pathParameterOrder: string[]; @@ -289,6 +290,7 @@ export function compileApiContract( operationId, method: method.toUpperCase() as HttpMethod, path, + ...(operation.deprecated === true ? { deprecated: true as const } : {}), tags: (operation.tags ?? []).map(String), auth: normalizeAuth(document, operation), pathParameterOrder: [...path.matchAll(/\{([^}]+)\}/g)].map( diff --git a/src/contracts/types.ts b/src/contracts/types.ts index e41fba9..222dab9 100644 --- a/src/contracts/types.ts +++ b/src/contracts/types.ts @@ -60,6 +60,7 @@ export interface ApiOperation { operationId: string; method: HttpMethod; path: string; + deprecated?: true; auth: { required: boolean; schemes: string[]; From f4da72a92a208e7bd84bb1234442d6cbe0e2272f Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 22:34:34 +0200 Subject: [PATCH 07/11] user agent --- conformance/src/generator.ts | 3 +++ conformance/tests/runner.test.ts | 2 ++ src/client.ts | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/conformance/src/generator.ts b/conformance/src/generator.ts index 6745fdf..551afea 100644 --- a/conformance/src/generator.ts +++ b/conformance/src/generator.ts @@ -1,3 +1,5 @@ +import packageJson from "../../package.json"; + import { readVerifiedSpec } from "./catalog"; import { compileOpenApi, type CompiledSpec } from "./openapi"; import { expectedRequest } from "./serialize"; @@ -45,6 +47,7 @@ function minimalInput(operation: OperationContract): SemanticInput { function requestWithAuth(operation: OperationContract, input: SemanticInput) { const request = expectedRequest(operation, input); + request.headers["user-agent"] = `langfuse-cli/${packageJson.version}`; if (operation.auth.required && operation.auth.schemes.includes("BasicAuth")) { request.headers.authorization = "Basic Y29uZm9ybWFuY2UtcHVibGljLWtleTpjb25mb3JtYW5jZS1zZWNyZXQta2V5"; diff --git a/conformance/tests/runner.test.ts b/conformance/tests/runner.test.ts index 11c1ebd..0c227f0 100644 --- a/conformance/tests/runner.test.ts +++ b/conformance/tests/runner.test.ts @@ -3,6 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; +import packageJson from "../../package.json"; import { generateVectors } from "../src/generator"; import { compileOpenApi } from "../src/openapi"; import { runConformance } from "../src/runner"; @@ -52,6 +53,7 @@ const api = args.indexOf("api"); const id = args[api + 3]; const response = await fetch( \`\${value("--host")}/widgets/\${encodeURIComponent(id)}?limit=\${value("--limit")}\`, + { headers: { "user-agent": ${JSON.stringify(`langfuse-cli/${packageJson.version}`)} } }, ); console.log(JSON.stringify({ status: response.status, body: await response.json() })); process.exit(response.ok ? 0 : 1); diff --git a/src/client.ts b/src/client.ts index 43afc74..3daa5e1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,3 +1,5 @@ +import packageJson from "../package.json"; + import type { ApiCallInput, ApiClientConfig, @@ -13,6 +15,8 @@ export interface PreparedRequest { body?: string; } +const USER_AGENT = `langfuse-cli/${packageJson.version}`; + function primitive(value: JsonValue): string { if (value === null) return ""; if (typeof value === "object") return JSON.stringify(value); @@ -117,6 +121,7 @@ export function prepareRequest( body = JSON.stringify(input.body); } headers.set("accept", "application/json"); + headers.set("user-agent", USER_AGENT); const host = config.host.endsWith("/") ? config.host : `${config.host}/`; const url = new URL(pathname.replace(/^\//, ""), host); for (const [name, value] of query) url.searchParams.append(name, value); From 67a5a8d327d08e09181be26eebccce77ccc0f86f Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 22:54:06 +0200 Subject: [PATCH 08/11] fixes --- README.md | 22 +- conformance/src/naming.ts | 304 ++++++++++++++++++---------- conformance/src/types.ts | 9 +- conformance/tests/adapters.test.ts | 2 +- conformance/tests/naming.test.ts | 85 ++++++-- conformance/tests/serialize.test.ts | 2 +- src/cli.test.ts | 56 ++++- src/cli.ts | 72 +++++-- src/contracts/types.ts | 9 +- 9 files changed, 405 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 29a681e..aab85b0 100644 --- a/README.md +++ b/README.md @@ -59,17 +59,17 @@ langfuse api help prompts create langfuse api schema --json langfuse api __schema --json -# List traces -langfuse api traces list --limit 10 +# List observations +langfuse api observations list --limit 10 -# Get a specific trace -langfuse api traces get +# List observations for a specific trace +langfuse api observations list --trace-id # JSON output (for piping/scripting) -langfuse api traces list --limit 5 --json +langfuse api observations list --limit 5 --json # Preview curl command -langfuse api traces list --limit 5 --curl +langfuse api observations list --limit 5 --curl # Prompts langfuse api prompts list @@ -81,15 +81,21 @@ langfuse api datasets list langfuse api dataset-items list --dataset-name my-dataset # Scores -langfuse api score-v2s get-scores --limit 20 +langfuse api scores list --limit 20 # Use an API snapshot compatible with an older self-hosted deployment langfuse --api-version 3.150.0 api traces list # Detect the server version through /api/public/health -langfuse --api-version auto api traces list +langfuse --api-version auto api prompts list ``` +Canonical command resources come from API paths and use concise REST actions. +OpenAPI tags and explicit route versions remain accepted aliases, for example +`scores-v3 list` for the canonical `scores list`. Verbose OpenAPI `operationId` +values remain available in `api schema --json` but are never required as CLI +commands. + ## Agent Usage The latest Langfuse skill lives in [`langfuse/skills`](https://github.com/langfuse/skills). Print the current version with: diff --git a/conformance/src/naming.ts b/conformance/src/naming.ts index 5bddf61..ebeb157 100644 --- a/conformance/src/naming.ts +++ b/conformance/src/naming.ts @@ -1,13 +1,28 @@ -import type { CommandName, HttpMethod } from "./types"; +import type { CommandAlias, CommandName, HttpMethod } from "./types"; interface NamingInput { operationId?: string; method: HttpMethod; path: string; tags: string[]; + deprecated?: true; } -interface PlannedNaming extends NamingInput, CommandName {} +interface RouteName { + resource: string; + version?: string; + tail: string[]; +} + +interface PlannedName { + input: NamingInput; + route: RouteName; + baseAction: string; + resource: string; + action: string; + aliases: CommandAlias[]; + index: number; +} const IRREGULAR: Record = { person: "people", @@ -27,6 +42,7 @@ const UNCOUNTABLE = new Set([ "series", "species", ]); +const API_VERSION = /^v\d+$/i; export function kebabCase(input: string): string { return input @@ -51,132 +67,200 @@ export function pluralize(input: string): string { return `${word}s`; } -function pathArgs(path: string): string[] { - return [...path.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]); +function singularize(input: string): string { + if (input.endsWith("ies")) return `${input.slice(0, -3)}y`; + if (/(ches|shes|xes|zes)$/.test(input)) return input.slice(0, -2); + if (input.endsWith("s") && !input.endsWith("ss")) return input.slice(0, -1); + return input; } -function operationIdParts(operationId = ""): { - prefix?: string; - suffix?: string; -} { - const separator = operationId.includes(".") - ? "." - : operationId.includes("__") - ? "__" - : operationId.includes("_") - ? "_" - : undefined; - if (!separator) return operationId ? { suffix: operationId } : {}; - const [prefix, ...rest] = operationId.split(separator); - return { prefix, suffix: rest.join(separator) }; +function isParameter(segment: string | undefined): boolean { + return Boolean(segment?.startsWith("{") && segment.endsWith("}")); } -function canonicalAction(input: string): string { - const action = kebabCase(input); - if (["retrieve", "read"].includes(action)) return "get"; - if (["search"].includes(action)) return "list"; - if (action === "patch") return "update"; - if (action === "remove") return "delete"; - return action; -} +function routeName(path: string): RouteName { + const all = path.split("/").filter(Boolean); + const publicIndex = all.lastIndexOf("public"); + const segments = all.slice(publicIndex === -1 ? 0 : publicIndex + 1); + let version: string | undefined; + if (API_VERSION.test(segments[0] ?? "")) version = segments.shift()!.toLowerCase(); + while (isParameter(segments[0])) segments.shift(); + if (segments.length === 0) return { resource: "api", ...(version ? { version } : {}), tail: [] }; -function inferResource(input: NamingInput): string { - const tag = input.tags[0]?.trim(); - if (tag && !["default", "defaults", "api"].includes(tag.toLowerCase())) { - return pluralize(kebabCase(tag)); + let resource = kebabCase(segments.shift()!); + if (resource === "unstable" && segments[0] && !isParameter(segments[0])) { + resource = `${resource}-${kebabCase(segments.shift()!)}`; } - const prefix = operationIdParts(input.operationId).prefix; - if (prefix) return pluralize(kebabCase(prefix)); - const segment = input.path.split("/").filter(Boolean)[0] ?? "api"; - return pluralize(kebabCase(segment.replace(/^\{.+\}$/, "") || "api")); -} - -function inferAction(input: NamingInput): string { - const suffix = operationIdParts(input.operationId).suffix; - if (suffix) { - const action = canonicalAction(suffix); - if (["get", "list", "create", "update", "delete"].includes(action)) { - return action; - } + if (resource === "otel" && API_VERSION.test(segments[0] ?? "")) { + version ??= segments.shift()!.toLowerCase(); } - const hasPathArg = pathArgs(input.path).length > 0; - if (input.method === "GET") return hasPathArg ? "get" : "list"; - if (input.method === "POST" && !hasPathArg) return "create"; - if (["PUT", "PATCH"].includes(input.method) && hasPathArg) return "update"; - if (input.method === "DELETE" && hasPathArg) return "delete"; - return kebabCase(input.method); -} - -function disambiguator(operation: PlannedNaming, index: number): string { - let name = kebabCase(operation.operationId ?? ""); - const synonyms: Record = { - get: ["get", "retrieve", "read", "list", "search"], - list: ["list", "search", "get"], - create: ["create", "post"], - update: ["update", "patch", "put"], - delete: ["delete", "remove"], - }; - for (const synonym of synonyms[operation.action] ?? [operation.action]) { - if (name.startsWith(`${synonym}-`)) { - name = name.slice(synonym.length + 1); - break; + return { resource, ...(version ? { version } : {}), tail: segments }; +} + +function operationSuffix(operationId = ""): string { + const splitAt = Math.max( + operationId.lastIndexOf("__"), + operationId.lastIndexOf("_"), + operationId.lastIndexOf("."), + ); + const suffix = splitAt === -1 ? operationId : operationId.slice(splitAt + 1); + return kebabCase(suffix).replace(/-v\d+$/i, ""); +} + +function restAction(input: NamingInput, route: RouteName, suffix: string): string { + const tail = route.tail.filter((segment) => !API_VERSION.test(segment)); + const staticTail = tail.filter((segment) => !isParameter(segment)); + const subject = kebabCase(staticTail.at(-1) ?? ""); + const singularSubject = singularize(subject); + const item = isParameter(tail.at(-1)); + + if (staticTail.length === 0) { + if (input.method === "GET") { + if (item) return "get"; + if (["health", "metrics"].includes(suffix)) return "get"; + return "list"; } + if (input.method === "POST") return "create"; + if (["PUT", "PATCH"].includes(input.method)) return "update"; + if (input.method === "DELETE") return item ? "delete" : "delete-many"; + return kebabCase(input.method); } - const singular = operation.resource.replace(/s$/, ""); - for (const resource of [operation.resource, singular]) { - if (name.startsWith(`${resource}-`)) name = name.slice(resource.length + 1); - else if (name.includes(`-${resource}-`)) { - name = name.replace(`-${resource}-`, "-"); - } else if (name.endsWith(`-${resource}`)) { - name = name.slice(0, -(resource.length + 1)); - } + + if (input.method === "GET") { + if (item) return `get-${singularSubject}`; + const suffixSubject = suffix.split("-").at(-1) ?? ""; + const explicitSingleton = suffix.startsWith("get-") && !suffixSubject.endsWith("s"); + return `${explicitSingleton ? "get" : "list"}-${subject}`; } - if (name && name !== operation.action && name !== operation.resource) { - return `${operation.action}-${name}`; + if (input.method === "POST") { + return `${suffix.startsWith("add-") ? "add" : "create"}-${singularSubject}`; } - const segments = operation.path.split("/").filter(Boolean).reverse(); - for (const segment of segments) { - if (segment.startsWith("{")) continue; - const candidate = kebabCase(segment); - if (![operation.resource, singular].includes(candidate)) { - return `${operation.action}-${candidate}`; - } + if (["PUT", "PATCH"].includes(input.method)) { + return `${suffix.startsWith("upsert-") ? "upsert" : "update"}-${singularSubject}`; + } + if (input.method === "DELETE") return `delete-${singularSubject}`; + return `${kebabCase(input.method)}-${singularSubject}`; +} + +function inferAction(input: NamingInput, route: RouteName): string { + const suffix = operationSuffix(input.operationId); + if (["batch", "submit", "upsert"].includes(suffix)) return suffix; + if (suffix === "delete-multiple") return "delete-many"; + if (suffix.startsWith("add-") || suffix.startsWith("export-")) return suffix; + if (suffix.startsWith("get-") && input.method !== "GET") return suffix; + if (suffix.endsWith("-status")) return suffix; + return restAction(input, route, suffix); +} + +function versionRank(version?: string): number { + return version ? Number(version.slice(1)) : 0; +} + +function commandKey(resource: string, action: string): string { + return `${resource}\u0000${action}`; +} + +function tagResources(input: NamingInput): string[] { + return input.tags + .map((tag) => kebabCase(tag)) + .filter((tag) => tag && !["default", "defaults", "api"].includes(tag)); +} + +function uniqueFallbackAction(plan: PlannedName, used: Set): string { + const suffix = operationSuffix(plan.input.operationId) || kebabCase(plan.input.method); + let candidate = suffix === plan.action ? `${plan.action}-${plan.index + 1}` : suffix; + let index = 2; + while (used.has(commandKey(plan.resource, candidate))) { + candidate = `${suffix}-${index++}`; } - return `${operation.action}-${index}`; + return candidate; } export function planCommandNames(inputs: NamingInput[]): CommandName[] { - const planned: PlannedNaming[] = inputs.map((input) => { - const action = inferAction(input); + const planned: PlannedName[] = inputs.map((input, index) => { + const route = routeName(input.path); + const baseAction = inferAction(input, route); + const resource = input.deprecated + ? route.version + ? `${route.resource}-${route.version}` + : `legacy-${route.resource}` + : route.resource; return { - ...input, - resource: inferResource(input), - action, - canonicalAction: action, + input, + route, + baseAction, + resource, + action: baseAction, + aliases: [], + index, }; }); - const totals = new Map(); - for (const operation of planned) { - const key = `${operation.resource}:${operation.action}`; - totals.set(key, (totals.get(key) ?? 0) + 1); + + const activeGroups = new Map(); + for (const plan of planned.filter((candidate) => !candidate.input.deprecated)) { + const key = commandKey(plan.route.resource, plan.baseAction); + const group = activeGroups.get(key) ?? []; + group.push(plan); + activeGroups.set(key, group); } - const seen = new Map(); - return planned.map((operation) => { - const key = `${operation.resource}:${operation.action}`; - if ((totals.get(key) ?? 0) === 1) { - return { - resource: operation.resource, - action: operation.action, - canonicalAction: operation.canonicalAction, - }; + for (const group of activeGroups.values()) { + group.sort((left, right) => { + const versionDifference = versionRank(right.route.version) - versionRank(left.route.version); + return versionDifference || left.index - right.index; + }); + for (const loser of group.slice(1)) { + if (loser.route.version) loser.resource = `${loser.route.resource}-${loser.route.version}`; } - const index = (seen.get(key) ?? 0) + 1; - seen.set(key, index); - return { - resource: operation.resource, - action: disambiguator(operation, index), - canonicalAction: operation.canonicalAction, - aliasOf: `${operation.resource} ${operation.canonicalAction}`, - }; - }); + } + + const usedCanonical = new Set(); + for (const plan of planned) { + let key = commandKey(plan.resource, plan.action); + if (usedCanonical.has(key)) { + plan.action = uniqueFallbackAction(plan, usedCanonical); + key = commandKey(plan.resource, plan.action); + } + usedCanonical.add(key); + } + + const claimedAliases = new Set(); + for (const plan of planned) { + const candidates: CommandAlias[] = []; + if (plan.resource !== plan.route.resource) { + candidates.push({ + resource: plan.route.resource, + action: plan.baseAction, + source: "path", + }); + } + if (plan.route.version) { + candidates.push({ + resource: `${plan.route.resource}-${plan.route.version}`, + action: plan.baseAction, + source: "version", + }); + } + for (const resource of tagResources(plan.input)) { + candidates.push({ resource, action: plan.baseAction, source: "tag" }); + } + + for (const alias of candidates) { + const key = commandKey(alias.resource, alias.action); + if ( + key === commandKey(plan.resource, plan.action) || + usedCanonical.has(key) || + claimedAliases.has(key) + ) { + continue; + } + claimedAliases.add(key); + plan.aliases.push(alias); + } + } + + return planned.map((plan) => ({ + resource: plan.resource, + action: plan.action, + ...(plan.aliases.length ? { aliases: plan.aliases } : {}), + })); } diff --git a/conformance/src/types.ts b/conformance/src/types.ts index 5240ea0..1c6f4ae 100644 --- a/conformance/src/types.ts +++ b/conformance/src/types.ts @@ -34,8 +34,13 @@ export interface Catalog { export interface CommandName { resource: string; action: string; - canonicalAction: string; - aliasOf?: string; + aliases?: CommandAlias[]; +} + +export interface CommandAlias { + resource: string; + action: string; + source: "path" | "tag" | "version"; } export interface ParameterContract { diff --git a/conformance/tests/adapters.test.ts b/conformance/tests/adapters.test.ts index 5a942a1..0466d7d 100644 --- a/conformance/tests/adapters.test.ts +++ b/conformance/tests/adapters.test.ts @@ -17,7 +17,7 @@ const operation: OperationContract = { method: "POST", path: "/widgets/{id}", auth: { required: true, schemes: ["BasicAuth"] }, - command: { resource: "widgets", action: "create", canonicalAction: "create" }, + command: { resource: "widgets", action: "create" }, pathParameterOrder: ["id"], parameters: [ { diff --git a/conformance/tests/naming.test.ts b/conformance/tests/naming.test.ts index 9b84087..0ddb1ea 100644 --- a/conformance/tests/naming.test.ts +++ b/conformance/tests/naming.test.ts @@ -9,7 +9,7 @@ describe("stable CLI naming policy", () => { expect(pluralize("datasets")).toBe("datasets"); }); - test("uses tag resources and operationId actions", () => { + test("uses path resources and keeps tags as aliases", () => { expect( planCommandNames([ { @@ -29,32 +29,93 @@ describe("stable CLI naming policy", () => { { resource: "annotation-queues", action: "list", - canonicalAction: "list", }, { resource: "traces", action: "delete", - canonicalAction: "delete", + aliases: [{ resource: "trace", action: "delete", source: "tag" }], }, ]); }); - test("disambiguates collisions deterministically", () => { + test("uses REST semantics instead of stuttered dashboard actions", () => { const names = planCommandNames([ { - operationId: "comments_get", + operationId: "unstable_dashboards_create", + method: "POST", + path: "/api/public/unstable/dashboards", + tags: ["UnstableDashboards"], + }, + { + operationId: "unstable_dashboards_update", + method: "PATCH", + path: "/api/public/unstable/dashboards/{dashboardId}", + tags: ["UnstableDashboards"], + }, + { + operationId: "unstable_dashboards_delete", + method: "DELETE", + path: "/api/public/unstable/dashboards/{dashboardId}", + tags: ["UnstableDashboards"], + }, + { + operationId: "unstable_dashboards_addPlacement", + method: "POST", + path: "/api/public/unstable/dashboards/{dashboardId}/placements", + tags: ["UnstableDashboards"], + }, + { + operationId: "unstable_dashboards_updatePlacement", + method: "PATCH", + path: "/api/public/unstable/dashboards/{dashboardId}/placements/{placementId}", + tags: ["UnstableDashboards"], + }, + { + operationId: "unstable_dashboards_deletePlacement", + method: "DELETE", + path: "/api/public/unstable/dashboards/{dashboardId}/placements/{placementId}", + tags: ["UnstableDashboards"], + }, + ]); + expect(names.map(({ resource, action }) => `${resource} ${action}`)).toEqual([ + "unstable-dashboards create", + "unstable-dashboards update", + "unstable-dashboards delete", + "unstable-dashboards add-placement", + "unstable-dashboards update-placement", + "unstable-dashboards delete-placement", + ]); + }); + + test("prefers the latest active route and exposes path and tag aliases", () => { + const names = planCommandNames([ + { + operationId: "scores_get-many", method: "GET", - path: "/api/public/comments", - tags: ["Comments"], + path: "/api/public/v2/scores", + tags: ["Scores"], + deprecated: true, }, { - operationId: "comments_get-by-id", + operationId: "scoresV3_getManyV3", method: "GET", - path: "/api/public/comments/{commentId}", - tags: ["Comments"], + path: "/api/public/v3/scores", + tags: ["ScoresV3"], + }, + ]); + + expect(names).toEqual([ + { + resource: "scores-v2", + action: "list", + }, + { + resource: "scores", + action: "list", + aliases: [ + { resource: "scores-v3", action: "list", source: "version" }, + ], }, ]); - expect(new Set(names.map((name) => name.action)).size).toBe(2); - expect(names.every((name) => name.aliasOf === "comments get")).toBe(true); }); }); diff --git a/conformance/tests/serialize.test.ts b/conformance/tests/serialize.test.ts index 6b2cae7..bd26c04 100644 --- a/conformance/tests/serialize.test.ts +++ b/conformance/tests/serialize.test.ts @@ -9,7 +9,7 @@ const operation: OperationContract = { method: "GET", path: "/items/{itemId}", auth: { required: false, schemes: [] }, - command: { resource: "items", action: "get", canonicalAction: "get" }, + command: { resource: "items", action: "get" }, pathParameterOrder: ["itemId"], parameters: [ { diff --git a/src/cli.test.ts b/src/cli.test.ts index d5832bf..b6e4458 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -5,6 +5,7 @@ import { join } from "node:path"; import { assertOperationCallable, + operationByCommand, parseOperationInput, run, schemaOutput, @@ -88,7 +89,6 @@ describe("operation input parsing", () => { command: { resource: "prompts", action: "get", - canonicalAction: "get", }, pathParameterOrder: ["promptName"], parameters: [ @@ -218,7 +218,6 @@ paths: command: { resource: "widgets", action: "update", - canonicalAction: "update", }, pathParameterOrder: ["widgetId"], parameters: [ @@ -266,6 +265,59 @@ paths: body: { chartConfig: { show_value_labels: true } }, }); }); + + test("preserves an explicit null complete body", async () => { + const operation: ApiOperation = { + ...promptGet, + key: "POST /api/public/widgets", + operationId: "widgets_create", + method: "POST", + path: "/api/public/widgets", + command: { + resource: "widgets", + action: "create", + }, + pathParameterOrder: [], + parameters: [], + requestBody: { + required: true, + contentType: "application/json", + legacyFieldFlags: false, + fields: [], + }, + }; + + expect(await parseOperationInput(operation, ["--body-json", "null"])).toEqual({ + path: {}, + query: {}, + headers: {}, + cookies: {}, + body: null, + }); + }); + + test("resolves tag and version command aliases", () => { + const operation: ApiOperation = { + ...promptGet, + operationId: "scoresV3_getManyV3", + path: "/api/public/v3/scores", + command: { + resource: "scores", + action: "list", + aliases: [ + { resource: "scores-v3", action: "list", source: "tag" }, + ], + }, + }; + const contract = { + schemaVersion: 1 as const, + apiVersion: "4.10.0", + sourceSha256: "test", + operations: [operation], + }; + + expect(operationByCommand(contract, "scores-v3", "list")).toBe(operation); + }); }); describe("result output", () => { diff --git a/src/cli.ts b/src/cli.ts index 8a1266a..69a0704 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -194,11 +194,17 @@ Examples: langfuse api help langfuse api prompts list langfuse api prompts create --body-json '{"name":"my-prompt","type":"text","prompt":"Hello"}' - langfuse --api-version 3.150.0 api traces list + langfuse api observations list --limit 20 `); } -function resourceMap(contract: ApiContract): Map { +interface CommandBinding { + operation: ApiOperation; + action: string; + alias: boolean; +} + +function canonicalResourceMap(contract: ApiContract): Map { const resources = new Map(); for (const operation of contract.operations) { const existing = resources.get(operation.command.resource) ?? []; @@ -213,8 +219,31 @@ function resourceMap(contract: ApiContract): Map { return resources; } +function resourceMap(contract: ApiContract): Map { + const resources = new Map(); + const add = (resource: string, binding: CommandBinding) => { + const existing = resources.get(resource) ?? []; + existing.push(binding); + resources.set(resource, existing); + }; + for (const operation of contract.operations) { + add(operation.command.resource, { + operation, + action: operation.command.action, + alias: false, + }); + for (const alias of operation.command.aliases ?? []) { + add(alias.resource, { operation, action: alias.action, alias: true }); + } + } + for (const bindings of resources.values()) { + bindings.sort((left, right) => left.action.localeCompare(right.action)); + } + return resources; +} + function printApiHelp(contract: ApiContract): void { - const resources = [...resourceMap(contract)].sort(([left], [right]) => + const resources = [...canonicalResourceMap(contract)].sort(([left], [right]) => left.localeCompare(right), ); process.stdout.write(`Usage: langfuse api [options] @@ -234,6 +263,7 @@ Discovery: api schema --json Machine-readable command schema api __schema --json Legacy command alias api versions list Bundled historical snapshots + Path commands are canonical; OpenAPI tag and route-version aliases also work Action options: --body-json Lossless JSON request body @@ -244,16 +274,16 @@ Action options: } function printResourceHelp(contract: ApiContract, resource: string): void { - const operations = resourceMap(contract).get(resource); - if (!operations) throw new CliError(`Unknown API resource: ${resource}`); + const bindings = resourceMap(contract).get(resource); + if (!bindings) throw new CliError(`Unknown API resource: ${resource}`); process.stdout.write(`Usage: langfuse api ${resource} [options] Actions: -${operations +${bindings .map( - (operation) => { - const label = `${operation.command.action}${operation.deprecated ? " [deprecated]" : ""}`; - return ` ${label.padEnd(43)} ${operation.summary ?? operation.operationId}`; + (binding) => { + const label = `${binding.action}${binding.alias ? " [alias]" : ""}${binding.operation.deprecated ? " [deprecated]" : ""}`; + return ` ${label.padEnd(43)} ${binding.operation.method} ${binding.operation.path}`; }, ) .join("\n")} @@ -334,14 +364,23 @@ ${lines.length ? lines.join("\n") : " (no operation-specific options)"} `); } -function operationByCommand( +export function operationByCommand( contract: ApiContract, resource: string, action: string, ): ApiOperation { const operation = contract.operations.find( - (candidate) => - candidate.command.resource === resource && candidate.command.action === action, + (candidate) => { + if ( + candidate.command.resource === resource && + candidate.command.action === action + ) { + return true; + } + return candidate.command.aliases?.some( + (alias) => alias.resource === resource && alias.action === action, + ); + }, ); if (!operation) { if (!resourceMap(contract).has(resource)) { @@ -581,7 +620,7 @@ export async function parseOperationInput( throw new CliError(`Missing required option --${parameter.cliName}`); } } - let body = completeBody ?? fieldBody; + let body = completeBody !== undefined ? completeBody : fieldBody; if (completeBody === undefined && operation.requestBody?.legacyFieldFlags) { const missing = operation.requestBody.fields .filter((field) => field.required && fieldBody?.[field.name] === undefined) @@ -603,14 +642,11 @@ export function schemaOutput(contract: ApiContract) { schemaVersion: 1, apiVersion: contract.apiVersion, sourceSha256: contract.sourceSha256, - resources: [...resourceMap(contract)].map(([name, operations]) => ({ + resources: [...canonicalResourceMap(contract)].map(([name, operations]) => ({ name, actions: operations.map((operation) => ({ name: operation.command.action, - canonicalName: operation.command.canonicalAction, - ...(operation.command.aliasOf - ? { aliasOf: operation.command.aliasOf } - : {}), + aliases: operation.command.aliases ?? [], operationId: operation.operationId, method: operation.method, path: operation.path, diff --git a/src/contracts/types.ts b/src/contracts/types.ts index 222dab9..3d79344 100644 --- a/src/contracts/types.ts +++ b/src/contracts/types.ts @@ -25,8 +25,13 @@ export type ValueKind = export interface CommandName { resource: string; action: string; - canonicalAction: string; - aliasOf?: string; + aliases?: CommandAlias[]; +} + +export interface CommandAlias { + resource: string; + action: string; + source: "path" | "tag" | "version"; } export interface ApiParameter { From 5fe1a175a7d8a51d62bf484df6a85474c9a9acc2 Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 23:28:09 +0200 Subject: [PATCH 09/11] more simp --- src/cli.ts | 10 ++++++++-- src/contracts/loader.ts | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 69a0704..e264e0d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -185,7 +185,7 @@ Options: --secret-key Langfuse secret key (or LANGFUSE_SECRET_KEY) --host Langfuse host (default: ${DEFAULT_HOST}) --env Load env vars from a file - --api-version Select a bundled historical API contract + --api-version Exact/major version, latest, or auto --timeout Request timeout (default: ${DEFAULT_TIMEOUT_MS}) -h, --help Show help --version Show CLI version @@ -701,7 +701,13 @@ async function runApi(config: RuntimeConfig, args: string[]): Promise { return; } if (action === "current") { - process.stdout.write(`${config.apiVersion ?? catalog.latest}\n`); + const resolved = await resolveContractVersion({ + requested: config.apiVersion, + host: config.host, + timeoutMs: config.timeoutMs, + catalog, + }); + process.stdout.write(`${resolved.version}\n`); return; } if (action === "detect") { diff --git a/src/contracts/loader.ts b/src/contracts/loader.ts index c08ba7c..7855970 100644 --- a/src/contracts/loader.ts +++ b/src/contracts/loader.ts @@ -22,6 +22,20 @@ function compareVersion(left: string, right: string): number { return 0; } +function requestedMajor(version: string): number | undefined { + const match = /^v?(\d+)(?:\.x)?$/i.exec(version); + return match ? Number(match[1]) : undefined; +} + +function latestMajorEntry( + entries: ApiContractCatalogEntry[], + major: number, +): ApiContractCatalogEntry | undefined { + return [...entries] + .filter((entry) => parseVersion(entry.version)?.[0] === major) + .sort((left, right) => compareVersion(right.version, left.version))[0]; +} + export async function loadContractCatalog(): Promise { const catalog = (await Bun.file(CATALOG_URL).json()) as ApiContractCatalog; if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.versions)) { @@ -81,14 +95,27 @@ export async function resolveContractVersion(params: { return { catalog, version: compatible.version, detected }; } const exact = catalog.versions.find((entry) => entry.version === requested); - if (!exact) { + if (exact) return { catalog, version: exact.version }; + const major = requestedMajor(requested); + if (major !== undefined) { + const latestInMajor = latestMajorEntry(catalog.versions, major); + if (latestInMajor) return { catalog, version: latestInMajor.version }; + const availableMajors = [ + ...new Set( + catalog.versions + .map((entry) => parseVersion(entry.version)?.[0]) + .filter((value): value is number => value !== undefined), + ), + ].sort((left, right) => left - right); throw new Error( - `Unknown API version ${requested}. Available: ${catalog.versions - .map((entry) => entry.version) - .join(", ")}`, + `No bundled API contract for major version ${major}. Available majors: ${availableMajors.join(", ")}`, ); } - return { catalog, version: exact.version }; + throw new Error( + `Unknown API version ${requested}. Available: ${catalog.versions + .map((entry) => entry.version) + .join(", ")}`, + ); } export async function loadApiContract(version: string): Promise { From ca1041a237b01f278d13e5d64ee1eb8ab4b7b570 Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 23:28:25 +0200 Subject: [PATCH 10/11] more simp --- src/client.test.ts | 33 +++++++++++++++++++++++++ src/contracts/loader.test.ts | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/client.test.ts create mode 100644 src/contracts/loader.test.ts diff --git a/src/client.test.ts b/src/client.test.ts new file mode 100644 index 0000000..10bea09 --- /dev/null +++ b/src/client.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "bun:test"; + +import packageJson from "../package.json"; +import { prepareRequest } from "./client"; +import type { ApiOperation } from "./contracts/types"; + +const operation: ApiOperation = { + key: "GET /api/public/health", + operationId: "health_get", + method: "GET", + path: "/api/public/health", + auth: { required: false, schemes: [] }, + command: { + resource: "health", + action: "get", + }, + pathParameterOrder: [], + parameters: [], +}; + +describe("API client", () => { + test("identifies requests with the CLI package version", () => { + const request = prepareRequest( + { host: "https://cloud.langfuse.com", timeoutMs: 1_000 }, + operation, + { path: {}, query: {}, headers: {}, cookies: {} }, + ); + + expect(request.headers.get("user-agent")).toBe( + `langfuse-cli/${packageJson.version}`, + ); + }); +}); diff --git a/src/contracts/loader.test.ts b/src/contracts/loader.test.ts new file mode 100644 index 0000000..724ae47 --- /dev/null +++ b/src/contracts/loader.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "bun:test"; + +import { resolveContractVersion } from "./loader"; +import type { ApiContractCatalog } from "./types"; + +const catalog: ApiContractCatalog = { + schemaVersion: 1, + latest: "4.10.0", + versions: [ + { version: "3.216.0", sourceSha256: "3-latest" }, + { version: "4.10.0", sourceSha256: "4-latest" }, + { version: "3.0.0", sourceSha256: "3-oldest" }, + { version: "3.150.0", sourceSha256: "3-middle" }, + ], +}; + +function resolve(requested: string) { + return resolveContractVersion({ + requested, + host: "http://localhost:3000", + timeoutMs: 1_000, + catalog, + }); +} + +describe("API contract version resolution", () => { + test.each(["3", "v3", "3.x", "v3.x"])( + "resolves major selector %s to the latest bundled v3 contract", + async (requested) => { + expect((await resolve(requested)).version).toBe("3.216.0"); + }, + ); + + test("resolves another major independently", async () => { + expect((await resolve("4")).version).toBe("4.10.0"); + }); + + test("keeps exact selection exact", async () => { + expect((await resolve("3.150.0")).version).toBe("3.150.0"); + }); + + test("reports unavailable major selectors", async () => { + await expect(resolve("5")).rejects.toThrow( + "No bundled API contract for major version 5. Available majors: 3, 4", + ); + }); +}); From 65abd6ff879307c1f884aae7224b953fe9aca7b2 Mon Sep 17 00:00:00 2001 From: Nimar Date: Thu, 13 Aug 2026 23:39:10 +0200 Subject: [PATCH 11/11] fix --- conformance/src/add-version.ts | 24 +++++-- conformance/tests/add-version.test.ts | 29 ++++++-- src/cli.test.ts | 99 +++++++++++++++++++++++++++ src/cli.ts | 13 ++-- 4 files changed, 149 insertions(+), 16 deletions(-) diff --git a/conformance/src/add-version.ts b/conformance/src/add-version.ts index d424cc7..74d4f7c 100644 --- a/conformance/src/add-version.ts +++ b/conformance/src/add-version.ts @@ -88,13 +88,17 @@ export function updateConformanceReadme( summaries: SpecSummary[], ): string { const total = summaries.reduce((sum, item) => sum + item.operations, 0); - let updated = content.replace( + let updated = replaceRequired( + content, /currently attempts all \d+ operations across .*? snapshots using/, `currently attempts all ${total} operations across ${summaries.length} pinned snapshots using`, + "historical adapter operation count", ); - updated = updated.replace( - /passes all \d+ operations through/, - `passes all ${total} operations through`, + updated = replaceRequired( + updated, + /checks all \d+ operations through/, + `checks all ${total} operations through`, + "native adapter operation count", ); const heading = "| Langfuse | Paths | Operations |"; const tableStart = updated.indexOf(heading); @@ -112,6 +116,18 @@ export function updateConformanceReadme( return `${updated.slice(0, tableStart)}${table}${updated.slice(tableEnd)}`; } +function replaceRequired( + content: string, + pattern: RegExp, + replacement: string, + label: string, +): string { + if (!pattern.test(content)) { + throw new Error(`Could not find ${label} in conformance README`); + } + return content.replace(pattern, replacement); +} + function githubHeaders(): HeadersInit { const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN; return { diff --git a/conformance/tests/add-version.test.ts b/conformance/tests/add-version.test.ts index b2bcd1d..600e398 100644 --- a/conformance/tests/add-version.test.ts +++ b/conformance/tests/add-version.test.ts @@ -54,9 +54,9 @@ describe("add-version workflow", () => { })).toContain('"knownIssues": ["known-issue"]'); }); - test("regenerates operation totals and the pinned-spec table", () => { + test("regenerates both operation totals and the pinned-spec table", () => { const input = `The suite currently attempts all 10 operations across 2 snapshots using X. -The native adapter passes all 10 operations through JSON. +The native adapter checks all 10 operations through JSON. | Langfuse | Paths | Operations | |---|---:|---:| @@ -65,15 +65,32 @@ The native adapter passes all 10 operations through JSON. After table. `; - expect( - updateConformanceReadme(input, [ + const updated = updateConformanceReadme(input, [ { version: "1.0.0", paths: 1, operations: 4 }, { version: "2.0.0", paths: 2, operations: 6 }, { version: "3.0.0", paths: 3, operations: 8 }, - ]), - ).toContain("all 18 operations across 3 pinned snapshots"); + ]); + expect(updated).toContain("all 18 operations across 3 pinned snapshots"); + expect(updated).toContain("checks all 18 operations through"); expect(updateConformanceReadme(input, [ { version: "3.0.0", paths: 3, operations: 8 }, ])).toContain("| 3.0.0 | 3 | 8 |"); }); + + test("fails loudly when tracked README wording drifts", () => { + const input = `The suite currently attempts all 10 operations across 1 snapshots using X. +The native adapter runs every operation through JSON. + +| Langfuse | Paths | Operations | +|---|---:|---:| +| 1.0.0 | 1 | 10 | + +After table. +`; + expect(() => + updateConformanceReadme(input, [ + { version: "1.0.0", paths: 1, operations: 10 }, + ]), + ).toThrow("Could not find native adapter operation count"); + }); }); diff --git a/src/cli.test.ts b/src/cli.test.ts index b6e4458..28661f0 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -8,6 +8,7 @@ import { operationByCommand, parseOperationInput, run, + runApi, schemaOutput, writeResult, } from "./cli"; @@ -296,6 +297,48 @@ paths: }); }); + test("uses last-wins for repeated scalar body flags and appends arrays", async () => { + const operation: ApiOperation = { + ...promptGet, + key: "POST /api/public/widgets", + operationId: "widgets_create", + method: "POST", + path: "/api/public/widgets", + command: { resource: "widgets", action: "create" }, + pathParameterOrder: [], + parameters: [], + requestBody: { + required: true, + contentType: "application/json", + legacyFieldFlags: true, + fields: [ + { name: "content", required: true, kind: "string" }, + { + name: "tags", + required: false, + kind: "array", + itemKind: "string", + }, + ], + }, + }; + + expect( + await parseOperationInput(operation, [ + "--content", + "first", + "--content", + "second", + "--tags", + "one", + "--tags", + "two", + ]), + ).toMatchObject({ + body: { content: "second", tags: ["one", "two"] }, + }); + }); + test("resolves tag and version command aliases", () => { const operation: ApiOperation = { ...promptGet, @@ -320,6 +363,62 @@ paths: }); }); +describe("API version reporting", () => { + const catalog = { + schemaVersion: 1 as const, + latest: "4.10.0", + versions: [ + { version: "3.150.0", sourceSha256: "old" }, + { version: "3.216.0", sourceSha256: "new" }, + { version: "4.10.0", sourceSha256: "latest" }, + ], + }; + const config = { + host: "http://localhost:3000", + timeoutMs: 1_000, + json: false, + curl: false, + showSecrets: false, + }; + + test("versions current prints the resolved major selection", async () => { + const output = await captureOutput(() => + runApi({ ...config, apiVersion: "3" }, ["versions", "current"], catalog), + ); + + expect(output.stdout).toBe("3.216.0\n"); + expect(output.stderr).toBe(""); + }); + + test("versions current resolves auto instead of echoing it", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (): Promise => + Response.json({ version: "3.216.1" })) as typeof fetch; + try { + const output = await captureOutput(() => + runApi( + { ...config, apiVersion: "auto" }, + ["versions", "current"], + catalog, + ), + ); + expect(output.stdout).toBe("3.216.0\n"); + } finally { + globalThis.fetch = originalFetch; + } + }); + + test("versions current rejects invalid selectors", async () => { + await expect( + runApi( + { ...config, apiVersion: "bogus" }, + ["versions", "current"], + catalog, + ), + ).rejects.toThrow("Unknown API version bogus"); + }); +}); + describe("result output", () => { test("writes an empty file for an empty response body", async () => { const directory = await mkdtemp(join(tmpdir(), "langfuse-cli-test-")); diff --git a/src/cli.ts b/src/cli.ts index e264e0d..456fda4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,6 +10,7 @@ import type { ApiBodyField, ApiCallInput, ApiContract, + ApiContractCatalog, ApiOperation, ApiParameter, ApiResult, @@ -465,10 +466,6 @@ function setBodyValue( if (Array.isArray(parsed)) body[field.name] = parsed; else if (Array.isArray(existing)) existing.push(parsed); else body[field.name] = [parsed]; - } else if (existing !== undefined) { - body[field.name] = Array.isArray(existing) - ? [...existing, parsed] - : [existing, parsed]; } else { body[field.name] = parsed; } @@ -690,8 +687,12 @@ export async function writeResult( if (!result.ok) process.exitCode = 1; } -async function runApi(config: RuntimeConfig, args: string[]): Promise { - const catalog = await loadContractCatalog(); +export async function runApi( + config: RuntimeConfig, + args: string[], + providedCatalog?: ApiContractCatalog, +): Promise { + const catalog = providedCatalog ?? (await loadContractCatalog()); if (args[0] === "versions") { const action = args[1] ?? "list"; if (action === "list") {