Skip to main content
The Query API runs read-only LangWatchQL SELECT statements over your project’s analytics views. POST /api/v1/query runs one statement. GET /api/v1/query/schema lists the views, columns, and function names your key can query. The request body is the query itself, and a 200 is the result itself: no envelope, no wrapper object around it. A failure answers with the standard LangWatch error envelope and a real HTTP status, so one parser reads every response.

Endpoints

Authentication

Send your LangWatch API key in any one of these forms: Any LangWatch API key works here: project, organization, or personal. Rows come from every project the key holds the analytics:view permission on: a project key reaches its one project, an organization or personal key reaches every project it can read. No project header is required. To run POST /api/v1/query over one project only, send its id as projectId in the request body: the run then reads that project’s rows alone, under that project’s own content protections. Without projectId the run covers every project the key can read. You can also filter inside the statement itself with WHERE TenantId = '<project id>', but the result is then redacted by the strictest protection across all the projects the key can read. A result that mixes rows from more than one project carries the MULTI_PROJECT_RESULT diagnostic, so a caller who expected a single project can tell.

Errors

Both endpoints answer with the standard LangWatch error envelope and a real HTTP status. No wrapper sits around it. See Error Envelope. Where a failure has a fix, the envelope’s meta carries the corrective fields for it. For example, FUNCTION_NOT_ALLOWED lists allowedFunctions.

Queryable views

Every view is a read-only projection over one ClickHouse or PostgreSQL table. GET /api/v1/query/schema is the live source of truth: the table below is a human-readable index of the same 47 views, grouped by what they’re for.

How the catalog is built

Every ClickHouse table is a view by default: the catalog is derived from a generated columns manifest, so a new table shows up here automatically once it’s catalogued, rather than needing a hand-written entry. Two column families are gated rather than freely selectable: free-text carriers (captured input, captured output, tool output, log bodies) sit behind the output gate (input for a few views that separate the two), and billed/derived cost columns sit behind the costs gate. A gate’s state comes from the project’s privacy settings, never from the query. Selecting a gated column your key’s project has not opened is refused outright with GATED_COLUMN, never silently dropped from the result.

Observability: traces, spans, logs

Coding agents

coding_tool_results.OutputText is what the tool printed back to the agent. Read it to answer “what did this failed tool call print”:

Experiments & evaluations

Note: “dataset” above refers to an evaluation dataset (rows a batch evaluation runs over). Everything else queryable through LangWatchQL is a view.

Metrics & rollups

Rollup views (_by_minute, _rollups) return finalised values only: figures for a bucket that was still open when the row was written are absent, not partial. A metric a caller wants without touching per-row data belongs here.

Gateway & billing

Governance & audit

Governance views are keyed by the organization’s hidden internal_governance project. A key scoped to an ordinary project sees zero rows here, not an error and not a gate refusal, until the caller’s project set includes the org-level governance project. Automation firing history is not an analytics view. Read it per automation with GET /api/triggers/{id}/fires, langwatch trigger fires <id>, or the MCP tool platform_list_trigger_fires, each paged by cursor.

Legacy

Superseded by the canonical views above; kept for rolling-deployment reads and draining under their own TTL.

Entities & reference data

Application tables (PostgreSQL)

Every tenant-scoped table of the application database, derived from the schema. Organization- and team-scoped tables appear once per project the caller can reach. Secret material and person identifiers are never exposed; free-text and JSON bodies are gated on the content permissions.

Tables intentionally left out

A table off the catalog above is always a recorded decision, never an oversight: a coverage guard fails the build until every ClickHouse table is either catalogued or given a reason here:

Supported functions

A query may call only the functions on the allowlist below. GET /api/v1/query/schema serves the same list under its functions field, so an agent can read it back at run time, and a FUNCTION_NOT_ALLOWED refusal carries it under allowedFunctions so a rejected query can be fixed without another round trip. Combinator forms of the listed aggregates, such as countIf, are also accepted.
  • abs, addDays, addHours, addMinutes, addMonths, addQuarters, addSeconds, addWeeks, addYears, and
  • any, anyLast, argMax, argMin, arrayAll, arrayAvg, arrayConcat, arrayCount, arrayDistinct, arrayElement
  • arrayEnumerate, arrayExists, arrayFilter, arrayFirst, arrayFirstIndex, arrayJoin, arrayLast, arrayMap, arrayMax, arrayMin
  • arrayReverseSort, arraySlice, arraySort, arrayStringConcat, arraySum, arrayUniq, assumeNotNull, avg, caseWithExpression, caseWithoutExpression
  • CAST, ceil, ceiling, coalesce, concat, concatWithSeparator, count, date_trunc, dateDiff, dateTrunc
  • dense_rank, divide, empty, endsWith, equals, exists, exp, extract, extractAll, first_value
  • floor, formatDateTime, fromUnixTimestamp, globalIn, globalNotIn, greater, greaterOrEquals, greatest, groupArray, groupUniqArray
  • has, hasAll, hasAny, if, ifNull, ilike, in, indexOf, intDiv, intDivOrZero
  • isDistinctFrom, isNotDistinctFrom, isNotNull, isNull, isValidJSON, JSONExtract, JSONExtractArrayRaw, JSONExtractBool, JSONExtractFloat, JSONExtractInt
  • JSONExtractKeys, JSONExtractKeysAndValues, JSONExtractRaw, JSONExtractString, JSONExtractUInt, JSONHas, JSONLength, JSONType, lagInFrame, lambda
  • last_value, leadInFrame, least, left, length, lengthUTF8, less, lessOrEquals, like, ln
  • log, log10, log2, lower, lowerUTF8, map, mapContains, mapFilter, mapKeys, mapValues
  • match, max, median, medianExact, min, minus, modulo, moduloOrZero, multiIf, multiply
  • negate, not, notEmpty, notEquals, notILike, notIn, notLike, now, now64, nth_value
  • ntile, nullIf, or, plus, position, positionCaseInsensitive, pow, power, quantile, quantileExact
  • quantiles, quantilesExact, quantilesTDigest, quantilesTiming, quantileTDigest, quantileTiming, rank, replaceAll, replaceOne, replaceRegexpAll
  • replaceRegexpOne, right, round, row_number, sign, simpleJSONExtractBool, simpleJSONExtractFloat, simpleJSONExtractInt, simpleJSONExtractString, simpleJSONExtractUInt
  • simpleJSONHas, splitByChar, splitByString, sqrt, startsWith, stddevPop, stddevSamp, substring, substringUTF8, subtractDays
  • subtractHours, subtractMinutes, subtractMonths, subtractQuarters, subtractSeconds, subtractWeeks, subtractYears, sum, toBool, toDate
  • toDate32, toDate32OrDefault, toDate32OrNull, toDate32OrZero, toDateOrDefault, toDateOrNull, toDateOrZero, toDateTime, toDateTime64, toDateTime64OrDefault
  • toDateTime64OrNull, toDateTime64OrZero, toDateTimeOrDefault, toDateTimeOrNull, toDateTimeOrZero, today, toDayOfMonth, toDayOfWeek, toDayOfYear, toDecimal128
  • toDecimal128OrDefault, toDecimal128OrNull, toDecimal128OrZero, toDecimal256, toDecimal256OrDefault, toDecimal256OrNull, toDecimal256OrZero, toDecimal32, toDecimal32OrDefault, toDecimal32OrNull
  • toDecimal32OrZero, toDecimal64, toDecimal64OrDefault, toDecimal64OrNull, toDecimal64OrZero, toFloat32, toFloat32OrDefault, toFloat32OrNull, toFloat32OrZero, toFloat64
  • toFloat64OrDefault, toFloat64OrNull, toFloat64OrZero, toHour, toInt128, toInt128OrDefault, toInt128OrNull, toInt128OrZero, toInt16, toInt16OrDefault
  • toInt16OrNull, toInt16OrZero, toInt256, toInt256OrDefault, toInt256OrNull, toInt256OrZero, toInt32, toInt32OrDefault, toInt32OrNull, toInt32OrZero
  • toInt64, toInt64OrDefault, toInt64OrNull, toInt64OrZero, toInt8, toInt8OrDefault, toInt8OrNull, toInt8OrZero, toIntervalDay, toIntervalHour
  • toIntervalMinute, toIntervalMonth, toIntervalQuarter, toIntervalSecond, toIntervalWeek, toIntervalYear, toISOWeek, toLastDayOfMonth, toMinute, toMonday
  • toMonth, topK, toQuarter, toSecond, toStartOfDay, toStartOfFifteenMinutes, toStartOfFiveMinutes, toStartOfHour, toStartOfInterval, toStartOfISOWeek
  • toStartOfMinute, toStartOfMonth, toStartOfQuarter, toStartOfSecond, toStartOfTenMinutes, toStartOfWeek, toStartOfYear, toString, toTimeZone, toUInt128
  • toUInt128OrDefault, toUInt128OrNull, toUInt128OrZero, toUInt16, toUInt16OrDefault, toUInt16OrNull, toUInt16OrZero, toUInt256, toUInt256OrDefault, toUInt256OrNull
  • toUInt256OrZero, toUInt32, toUInt32OrDefault, toUInt32OrNull, toUInt32OrZero, toUInt64, toUInt64OrDefault, toUInt64OrNull, toUInt64OrZero, toUInt8
  • toUInt8OrDefault, toUInt8OrNull, toUInt8OrZero, toUnixTimestamp, toUUID, toUUIDOrDefault, toUUIDOrNull, toUUIDOrZero, toWeek, toYear
  • trim, trimBoth, trimLeft, trimRight, trunc, truncate, tuple, tupleElement, uniq, uniqCombined
  • uniqExact, upper, upperUTF8, varPop, varSamp, yesterday

Response ceilings

A statement that names no LIMIT is capped at 10,000 rows: that LIMIT is appended before the query runs, so a large answer comes back as one page rather than a stream. To read the rest, page with LIMIT/OFFSET and an ORDER BY. A statement whose own LIMIT asks for more than 10,000 rows is refused with LIMIT_TOO_HIGH before it runs. When using UNION, every top-level branch must carry its own LIMIT clause of 10,000 rows or fewer, or the query is refused with LIMIT_REQUIRED_PER_BRANCH. A result whose body exceeds about 8,000,000 bytes is refused outright with lwql_result_too_large. Select fewer columns or a smaller LIMIT to bring it under the cap.
Last modified on October 13, 2026