Agents
LeanData
LeanData serves queryable datasets published by data providers: tables (filter, order, page) and document collections (list, fetch). A dataset is addressed by {provider}.{dataset} and priced per dataset: one call plus the rows returned, and for subscription datasets a monthly charge.
Endpoints#
?format=ndjson) or CSV (?format=csv)downloadUrldownloadUrl (no bearer)The key must be scoped to the Data service; catalogue, describe and subscription calls are free. Over MCP the same operations are data.list, data.describe, data.query, data.documents.list, data.documents.get, data.subscribe and data.unsubscribe, with the dataset as an argument.
Catalogue and describe#
GET /api/v1/data lists the enabled datasets your workspace may use (a dataset licensed per country is hidden when your workspace's country is not allowed). Each item carries pricing at your rates and access, which says whether a query would be admitted right now:
{ "items": [ {
"key": "leantools-usage.service-usage", "provider": "leantools-usage", "name": "Service usage", "kind": "table",
"domains": ["Usage"], "coverage": ["GLOBAL"], "columns": ["day", "serviceKey", "meterKey", "requests", "credits"],
"pricing": { "callCredits": 0, "rowCreditsPer1000": 0, "monthlyCredits": null, "subscriptionRequired": false, "subscribed": false, "summary": "0 credits/call, 0 credits/1,000 rows" },
"access": { "allowed": true, "reason": null, "requiresSubscription": false, "subscribed": false, "subscriptionStatus": null, "currentPeriodEndUtc": null }
} ], "count": 1 }GET /api/v1/data/{dataset} adds columnDefinitions[{ name, type, description, isKey, isFilterable, isSortable, isNullable }], documentTypes, examples (ready-to-send query bodies), operators and capabilities (supportsFilters, supportsOrderBy, supportsFieldSelection, supportsNativeQuery, supportsTotalCount, maxPageSize, defaultPageSize).
Query (JSON page)#
curl -s https://YOUR-HOST/api/v1/data/leantools-usage.service-usage/query \
-H "Authorization: Bearer lt-..." -H "Content-Type: application/json" \
-d '{ "filters": [ { "field": "serviceKey", "op": "eq", "value": "Search" } ], "fields": ["day", "requests", "credits"],
"orderBy": "day", "descending": true, "take": 100, "includeTotalCount": true }'{
"dataset": "leantools-usage.service-usage",
"columns": [ { "name": "day", "type": "DateTime" }, { "name": "requests", "type": "Integer" }, { "name": "credits", "type": "Decimal" } ],
"rows": [ [ "2026-09-27T00:00:00+00:00", 41, 4.1 ], [ "2026-09-26T00:00:00+00:00", 12, 1.2 ] ],
"nextPageToken": "MjAw", "totalCount": 233, "truncated": false,
"usage": { "rows": 100, "credits": 0.11, "balance": 1874.25 }
}filters[].opis one ofeq,ne,gt,ge,lt,le,in(array value),contains,startsWith,between(two-element array),isNull(no value); filters are combined with AND and onlyisFilterablecolumns are accepted (400 invalid_filter).takeis clamped to the dataset'smaxPageSizeand the host'sLeanTools.Data.MaxRowsPerCall; passnextPageTokenback aspageTokento continue. An empty body returns the first page at the default page size.nativeQueryis accepted only wherecapabilities.supportsNativeQueryis true.- The body is limited to
LeanTools.Data.MaxQueryBodyBytes(1 MiB); a page overLeanTools.Data.MaxResponseBytes(64 MiB) is refused with413 response_too_large(stream it instead). usage.balanceis the balance after this call;usage.credits= the call price plusrows / 1000 × row price.
Streaming (NDJSON) and CSV#
Ask for ?format=ndjson (or ?stream=true, or Accept: application/x-ndjson) to receive rows as they are produced, one JSON document per line:
curl -N https://YOUR-HOST/api/v1/data/leantools-usage.service-usage/query?format=ndjson \
-H "Authorization: Bearer lt-..." -H "Content-Type: application/json" -d '{ "take": 5000 }'{"dataset":"leantools-usage.service-usage","columns":[{"name":"day","type":"DateTime"},{"name":"requests","type":"Integer"}]}
{"day":"2026-09-27T00:00:00+00:00","requests":41}
{"day":"2026-09-26T00:00:00+00:00","requests":12}
{"nextPageToken":null,"totalCount":null,"truncated":false,"usage":{"rows":2,"credits":0.1002,"balance":1874.15}}A header line with the columns, one object per row keyed by column name, then a trailer with the page token and the usage block. If the provider fails after the header the stream ends with {"error":{"code":"provider_error","message":"…","status":502},"usage":{…}} and the rows already delivered are charged; if you disconnect mid-stream, the rows already sent are charged as well. ?format=csv answers text/csv with a header row (RFC 4180 quoting; cells starting with =, +, -, @ are prefixed with '); the usage block travels in the X-LeanTools-Usage HTTP trailer.
Documents#
GET /api/v1/data/{dataset}/documents lists a document dataset's items ({ items[{ id, type, title, contentType, size, modifiedTime, metadata }], nextPageToken, totalCount, usage }; billed one call plus one row per summary). GET …/documents/{id} returns one document (one call plus one row):
{ "dataset": "leantools-usage.monthly-statement", "id": "2026-08", "type": "statement", "title": "August 2026", "contentType": "text/markdown", "size": 2140,
"content": "# Statement for August 2026\n...", "downloadUrl": null, "expiresUtc": null, "usage": { "rows": 1, "credits": 0, "balance": 1874.15 } }Textual documents up to LeanTools.Data.InlineDocumentBytes (256 KiB) come back inline. Larger or binary documents carry content: null and a signed downloadUrl valid for LeanTools.Data.DownloadTtlSeconds (no bearer needed, range requests honoured); ?redirect=true answers 302. A tampered or expired link is 403 invalid_ticket; the download itself is not billed.
Subscriptions#
A dataset with access.requiresSubscription: true refuses queries with 402 subscription_required until the workspace subscribes, from the console (Tools & Connectors → Datasets) or the API:
curl -s -X POST https://YOUR-HOST/api/v1/data/market.prices/subscribe -H "Authorization: Bearer lt-..."{ "subscription": { "datasetKey": "market.prices", "status": "active", "monthlyCredits": 5, "currentPeriodEndUtc": "2026-10-27T09:12:00+00:00" },
"created": true, "notRequired": false, "usage": { "credits": 5, "balance": 1869.15 } }The first month is charged immediately (402 insufficient_credits when the balance does not cover it) and access opens at once. Repeating the call on an active subscription answers 200 with created: false. The month renews automatically; a paid renewal that finds no credits suspends the subscription (402 subscription_suspended) until the workspace is topped up. DELETE …/subscribe answers { datasetKey, status: "cancelled", accessUntilUtc }: access continues until the end of the paid period.
Billing notes#
Every query, listing or fetch writes one usage row under the call meter with the dataset key as qualifier and the rows delivered as outputUnits. A call refused before its first row (bad filter, no credits, no subscription, provider down) costs nothing; rows already delivered are always charged. A dataset whose call and row rates are both 0 (your own usage data, for instance) can be queried on an empty balance.
Status codes#
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request, invalid_query, invalid_filter, invalid_sort, invalid_fields, invalid_page_token, native_query_not_supported, documents_not_supported, not_subscribable | Malformed body or format, a column, operator, page token or capability the dataset does not have |
| 401 | authentication_error | Missing or unknown bearer |
| 402 | insufficient_credits, subscription_required, subscription_suspended | No credits for a priced dataset; no (or a suspended) subscription |
| 403 | service_not_allowed, dataset_disabled, dataset_unavailable, country_blocked, country_not_allowed, country_required, invalid_ticket | Key scope, a dataset switched off, geography, a forged or expired download link |
| 404 | dataset_not_found, document_not_found, not_subscribed | |
| 413 | payload_too_large, response_too_large | The query body, or a page / document over the host limit |
| 502 / 503 / 504 | provider_error, provider_unavailable, provider_timeout | The data provider failed, is down, or exceeded LeanTools.Data.QueryTimeoutSeconds |