thoughtleaders-cli 0.6.46__tar.gz → 0.6.51__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/AGENTS.md +7 -1
- thoughtleaders_cli-0.6.51/API.md +426 -0
- thoughtleaders_cli-0.6.51/PKG-INFO +305 -0
- thoughtleaders_cli-0.6.51/README.md +277 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl/SKILL.md +66 -33
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl/references/elasticsearch-schema.md +4 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-import/SKILL.md +1 -1
- thoughtleaders_cli-0.6.51/skills/tl-keyword-research/SKILL.md +165 -0
- thoughtleaders_cli-0.6.51/skills/tl-keyword-research/scripts/probe.py +156 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/SKILL.md +13 -26
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/examples/e2e_findings.md +13 -8
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/examples/golden_queries.md +6 -6
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/column_builder.md +1 -1
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/__init__.py +1 -1
- thoughtleaders_cli-0.6.51/src/tl_cli/commands/describe.py +314 -0
- thoughtleaders_cli-0.6.51/src/tl_cli/commands/uploads.py +41 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/config.py +0 -1
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/main.py +7 -6
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/self_update.py +14 -3
- thoughtleaders_cli-0.6.46/PKG-INFO +0 -245
- thoughtleaders_cli-0.6.46/README.md +0 -217
- thoughtleaders_cli-0.6.46/docs/architecture.md +0 -574
- thoughtleaders_cli-0.6.46/skills/tl-report-builder/tools/keyword_research.md +0 -217
- thoughtleaders_cli-0.6.46/src/tl_cli/commands/ask.py +0 -54
- thoughtleaders_cli-0.6.46/src/tl_cli/commands/describe.py +0 -177
- thoughtleaders_cli-0.6.46/src/tl_cli/commands/uploads.py +0 -86
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/hooks/scripts/load-tl-skill.mjs +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/database_query.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/name_resolver.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/sample_judge.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/similar_channels.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/skills/tl-report-builder/tools/widget_builder.md +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/auth/finalize.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/bulk_import.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/channels.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/credits.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/test_http_auth.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/test_reports.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.51}/uv.lock +0 -0
|
@@ -56,6 +56,13 @@ The CLI integrates with AI coding agents via skills, commands, agents, and hooks
|
|
|
56
56
|
|
|
57
57
|
This repo is also a Claude Code plugin, and can directly be installed as one.
|
|
58
58
|
|
|
59
|
+
### Bundled skills — when to invoke
|
|
60
|
+
|
|
61
|
+
- **`tl`** — the main skill for querying ThoughtLeaders data. Default for any sponsorship / channel / brand / upload / report question.
|
|
62
|
+
- **`tl-keyword-research`** — invoke whenever the user wants to find videos or channels by **content keywords** (topics, concepts, niches) that aren't covered by a curated recommender tag, OR to validate that a candidate channel's content actually touches a given topic. Returns `{operator, keywords:[{keyword,count}]}` from a ranked ES probe over `title` / `summary` / `transcript`; the caller then runs the actual content search with the surviving high-count terms. **Do not compose keyword sets by hand for `tl db es` content searches — delegate to this skill first.** See `skills/tl/SKILL.md` → *Channel & video discovery* for the four-path decision tree and when to use this vs the recommender / raw SQL.
|
|
63
|
+
- **`tl-report-builder`** — invoke when the user wants to build, refine, or save a platform report (campaign config, FilterSet, columns, widgets). Multi-phase flow: routing → schema + validation → columns → widgets.
|
|
64
|
+
- **`tl-import`**, **`tl-save-report`**, **`adapt-tl-data`** — narrower workflows; the skill files document their own triggers.
|
|
65
|
+
|
|
59
66
|
### Skill content boundaries
|
|
60
67
|
|
|
61
68
|
Skills under `skills/` are split into a `SKILL.md` and one or more `references/*.md` files. To prevent drift, each fact has exactly one home:
|
|
@@ -90,7 +97,6 @@ All list endpoints return: `{ results, total, limit, offset, usage: { credits_ch
|
|
|
90
97
|
- `TL_API_URL` — API base (default: `https://app.thoughtleaders.io`)
|
|
91
98
|
- `TL_API_KEY` — Bearer token override for CI/scripts
|
|
92
99
|
- `TL_AUTH0_DOMAIN`, `TL_AUTH0_CLIENT_ID`, `TL_AUTH0_AUDIENCE` — Auth0 config
|
|
93
|
-
- `TL_LLM_KEY` — User's own LLM key for `tl ask` (avoids surcharge)
|
|
94
100
|
|
|
95
101
|
## Credit System
|
|
96
102
|
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# ThoughtLeaders CLI — HTTP API
|
|
2
|
+
|
|
3
|
+
The same endpoints `tl` calls under the hood are reachable directly with `curl` or any HTTP client. This page documents the subset most useful for scripting and BI integrations:
|
|
4
|
+
|
|
5
|
+
- [`GET /whoami`](#whoami) — current user, profile, org, brands
|
|
6
|
+
- [`GET /balance`](#balance) — credit balance + recent usage
|
|
7
|
+
- [`POST /raw/pg`](#db-pg) — read-only PostgreSQL `SELECT`
|
|
8
|
+
- [`POST /raw/es`](#db-es) — Elasticsearch search body
|
|
9
|
+
- [`POST /raw/fb`](#db-fb) — read-only Firebolt `SELECT`
|
|
10
|
+
- [`GET /raw/pg/schema`](#schema-pg) — PostgreSQL schema reference
|
|
11
|
+
- [`GET /raw/es/schema`](#schema-es) — Elasticsearch document shape
|
|
12
|
+
- [`GET /raw/fb/schema`](#schema-fb) — Firebolt schema reference
|
|
13
|
+
|
|
14
|
+
The full surface is larger (sponsorships, channels, brands, recommender, reports, …). Run `tl describe` from a logged-in CLI for the complete list.
|
|
15
|
+
|
|
16
|
+
## Base URL & auth
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Base URL: https://app.thoughtleaders.io/api/cli/v1
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
All requests must carry both of:
|
|
23
|
+
|
|
24
|
+
| Header | Value | Why |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `Authorization` | `Bearer <api_key>` | The credential. |
|
|
27
|
+
| `X-TL-Auth` | `API-KEY` | Opts into API-key auth. Without it the server interprets the Bearer as an Auth0 JWT and rejects the API-key string. |
|
|
28
|
+
|
|
29
|
+
Inactive or expired keys return `401`; if the owning user is deactivated the request fails with `403`.
|
|
30
|
+
|
|
31
|
+
A quick set of shell variables used throughout this page:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export TL_API_BASE='https://app.thoughtleaders.io/api/cli/v1'
|
|
35
|
+
export TL_API_KEY='<your 64-char hex key>'
|
|
36
|
+
|
|
37
|
+
auth() {
|
|
38
|
+
printf 'Authorization: Bearer %s\nX-TL-Auth: API-KEY\n' "$TL_API_KEY"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
And the Python equivalent (uses `requests`; works the same with `httpx`):
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import os
|
|
46
|
+
import requests
|
|
47
|
+
|
|
48
|
+
BASE = 'https://app.thoughtleaders.io/api/cli/v1'
|
|
49
|
+
KEY = os.environ['TL_API_KEY']
|
|
50
|
+
HEADERS = {
|
|
51
|
+
'Authorization': f'Bearer {KEY}',
|
|
52
|
+
'X-TL-Auth': 'API-KEY',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def get(path, params=None):
|
|
56
|
+
r = requests.get(f'{BASE}{path}', headers=HEADERS, params=params, timeout=30)
|
|
57
|
+
r.raise_for_status()
|
|
58
|
+
return r.json()
|
|
59
|
+
|
|
60
|
+
def post(path, body):
|
|
61
|
+
r = requests.post(f'{BASE}{path}', headers=HEADERS, json=body, timeout=60)
|
|
62
|
+
r.raise_for_status()
|
|
63
|
+
return r.json()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Response envelope
|
|
67
|
+
|
|
68
|
+
Multi-row responses share one shape:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"results": [ ... rows ... ],
|
|
73
|
+
"total": 1234,
|
|
74
|
+
"limit": 500,
|
|
75
|
+
"offset": 0,
|
|
76
|
+
"usage": {
|
|
77
|
+
"credits_charged": 4.12,
|
|
78
|
+
"credit_rate": 1.4,
|
|
79
|
+
"balance_remaining": 9995.88
|
|
80
|
+
},
|
|
81
|
+
"_breadcrumbs": [
|
|
82
|
+
{ "hint": "next page", "command": "..." }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- **`results`** is always a list (even for single-row responses).
|
|
88
|
+
- **`usage`** is on every metered response. Free endpoints (`whoami`, `balance`, schema) report `credits_charged: 0`.
|
|
89
|
+
- **`_breadcrumbs`** is advisory next-step hints; ignore in production scripts.
|
|
90
|
+
|
|
91
|
+
Error responses are JSON: `{"detail": "<reason>"}`, sometimes with extra structural fields (`reason` for `db/pg` sanitizer rejections, `candidates` for ambiguous-match endpoints, `queued_*` for the channels-find scrape-queue path).
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## whoami
|
|
96
|
+
|
|
97
|
+
`GET /whoami` — current user, profile flags, organization, associated profiles, and (for buyers) brands. Free.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
curl -sS "$TL_API_BASE/whoami" \
|
|
101
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
102
|
+
-H 'X-TL-Auth: API-KEY' | jq
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
print(get('/whoami'))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"user": {
|
|
112
|
+
"id": 4221,
|
|
113
|
+
"email": "alice@thoughtleaders.io",
|
|
114
|
+
"first_name": "Alice",
|
|
115
|
+
"last_name": "Roe",
|
|
116
|
+
"date_joined": "2024-08-11T12:18:43+00:00"
|
|
117
|
+
},
|
|
118
|
+
"profile": {
|
|
119
|
+
"id": 9117,
|
|
120
|
+
"flags": ["advertiser"],
|
|
121
|
+
"is_paid": true,
|
|
122
|
+
"persona": "Brand",
|
|
123
|
+
"created_at": "2024-08-11T12:18:43+00:00"
|
|
124
|
+
},
|
|
125
|
+
"organization": {
|
|
126
|
+
"id": 311,
|
|
127
|
+
"name": "Acme Marketing",
|
|
128
|
+
"plan": "Intelligence",
|
|
129
|
+
"is_managed_services": false,
|
|
130
|
+
"credits_balance": 9995.88
|
|
131
|
+
},
|
|
132
|
+
"associated_profiles": [ ... ],
|
|
133
|
+
"brands": [ ... ]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Useful for verifying the API key resolves to the user you expect before kicking off a longer script.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## balance
|
|
142
|
+
|
|
143
|
+
`GET /balance` — credit balance plus the last 10 metered calls for the org. Free.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
curl -sS "$TL_API_BASE/balance" \
|
|
147
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
148
|
+
-H 'X-TL-Auth: API-KEY' | jq
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
print(get('/balance'))
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"balance": 9995.88,
|
|
158
|
+
"allow_overage": false,
|
|
159
|
+
"recent_usage": [
|
|
160
|
+
{
|
|
161
|
+
"date": "2026-05-19T14:02:11+00:00",
|
|
162
|
+
"resource": "db_pg",
|
|
163
|
+
"results_count": 500,
|
|
164
|
+
"credits_charged": 33.41
|
|
165
|
+
},
|
|
166
|
+
...
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## db pg
|
|
174
|
+
|
|
175
|
+
`POST /raw/pg` — execute a read-only PostgreSQL `SELECT`. Sanitised: SELECT only, no DDL/DML/transactions, `LIMIT ≤ 500`, function allowlist (aggregates, window, string, JSON, math, date/time, array). `OFFSET ≥ 10 000` is rejected with `OFFSET_TOO_DEEP` — paginate with the response's `next_offset` instead.
|
|
176
|
+
|
|
177
|
+
Body: `{"query": "<sql>"}`.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
curl -sS "$TL_API_BASE/raw/pg" \
|
|
181
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
182
|
+
-H 'X-TL-Auth: API-KEY' \
|
|
183
|
+
-H 'Content-Type: application/json' \
|
|
184
|
+
-d '{
|
|
185
|
+
"query": "SELECT id, channel_name, reach FROM thoughtleaders_channel WHERE is_tl_channel = TRUE ORDER BY reach DESC LIMIT 5 OFFSET 0"
|
|
186
|
+
}' | jq
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
sql = """
|
|
191
|
+
SELECT id, channel_name, reach
|
|
192
|
+
FROM thoughtleaders_channel
|
|
193
|
+
WHERE is_tl_channel = TRUE
|
|
194
|
+
ORDER BY reach DESC
|
|
195
|
+
LIMIT 5 OFFSET 0
|
|
196
|
+
"""
|
|
197
|
+
print(post('/raw/pg', {'query': sql}))
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"results": [
|
|
203
|
+
{"id": 12345, "channel_name": "MrBeast", "reach": 320000000},
|
|
204
|
+
...
|
|
205
|
+
],
|
|
206
|
+
"total": 5,
|
|
207
|
+
"limit": 5,
|
|
208
|
+
"offset": 0,
|
|
209
|
+
"usage": { "credits_charged": 1.84, "credit_rate": 1.4, "balance_remaining": 9994.04 }
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Pricing
|
|
214
|
+
|
|
215
|
+
PG cost is **per-query**: a base rate plus a surcharge for every priced table and column referenced. Most tables/columns are free; sensitive ones (demographics, channel outreach emails) cost more. The `usage.credit_rate` you get back is the effective multiplier the server applied — it's not the static value from `tl describe`. The `pricing` sub-key, when present, breaks the rate into base/per-table/per-column components.
|
|
216
|
+
|
|
217
|
+
### Common rejections
|
|
218
|
+
|
|
219
|
+
- `MISSING_LIMIT` / `LIMIT_TOO_HIGH` — always include `LIMIT N` with `N ≤ 500`.
|
|
220
|
+
- `INSERT` / `UPDATE` / `DELETE` / `CREATE` / `DROP` — sanitiser is SELECT-only.
|
|
221
|
+
- `LEAKY_CAST` — `::regclass`, `::regprocedure`, etc. are blocked.
|
|
222
|
+
- `OFFSET_TOO_DEEP` — paginate via the next-page breadcrumb instead of jumping past 10 000.
|
|
223
|
+
|
|
224
|
+
Run `GET /raw/pg/schema` (below) or `tl schema pg` for the live column catalogue. SELECT-only schema introspection (`information_schema.columns`, most `pg_*` helpers) is blocked by the sanitiser; use the schema endpoint instead.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## db es
|
|
229
|
+
|
|
230
|
+
`POST /raw/es` — execute an Elasticsearch search against the `tl-platform` index family (videos / channels). Accepts the standard ES query body.
|
|
231
|
+
|
|
232
|
+
Body: `{"query": <es_body>}` — either a JSON object or a JSON-encoded string in `query`.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
curl -sS "$TL_API_BASE/raw/es" \
|
|
236
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
237
|
+
-H 'X-TL-Auth: API-KEY' \
|
|
238
|
+
-H 'Content-Type: application/json' \
|
|
239
|
+
-d '{
|
|
240
|
+
"query": {
|
|
241
|
+
"size": 20,
|
|
242
|
+
"query": {"term": {"sponsored_brand_mentions": "5612"}},
|
|
243
|
+
"_source": ["title", "channel.id", "publication_date", "views"]
|
|
244
|
+
}
|
|
245
|
+
}' | jq
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
es_body = {
|
|
250
|
+
"size": 20,
|
|
251
|
+
"query": {"term": {"sponsored_brand_mentions": "5612"}},
|
|
252
|
+
"_source": ["title", "channel.id", "publication_date", "views"],
|
|
253
|
+
}
|
|
254
|
+
print(post('/raw/es', {'query': es_body}))
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Accepted query bodies
|
|
258
|
+
|
|
259
|
+
The server forwards bodies built from `term`, `terms`, `match`, `bool`, `nested`, `range`, `exists`, and standard aggregations. The following are not accepted:
|
|
260
|
+
|
|
261
|
+
- `query_string`, `regexp`, `wildcard`, `fuzzy`, `more_like_this`
|
|
262
|
+
- parent/child joins
|
|
263
|
+
- any `script_*`
|
|
264
|
+
- multiple aggregations in one body (run multiple calls and combine client-side)
|
|
265
|
+
|
|
266
|
+
Deep pagination via `scroll` / `pit` is unavailable — use `search_after` with `sort` to walk past 10 000.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## db fb
|
|
271
|
+
|
|
272
|
+
`POST /raw/fb` — execute a read-only Firebolt `SELECT` against the historical-metrics tables (`article_metrics`, `channel_metrics`). Mandatory: queries against `article_metrics` must filter by `channel_id` (and ideally `id`); without it the index requirement fails with `MISSING_INDEXED_FILTER`. `channel_metrics` requires filtering by `id`.
|
|
273
|
+
|
|
274
|
+
Body: `{"query": "<sql>"}`.
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
curl -sS "$TL_API_BASE/raw/fb" \
|
|
278
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
279
|
+
-H 'X-TL-Auth: API-KEY' \
|
|
280
|
+
-H 'Content-Type: application/json' \
|
|
281
|
+
-d '{
|
|
282
|
+
"query": "SELECT id, age, view_count FROM article_metrics WHERE channel_id = 12345 AND id IN ('abc', 'def') ORDER BY id, age"
|
|
283
|
+
}' | jq
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
sql = """
|
|
288
|
+
SELECT id, age, view_count
|
|
289
|
+
FROM article_metrics
|
|
290
|
+
WHERE channel_id = 12345 AND id IN ('abc', 'def')
|
|
291
|
+
ORDER BY id, age
|
|
292
|
+
"""
|
|
293
|
+
print(post('/raw/fb', {'query': sql}))
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Workflow note
|
|
297
|
+
|
|
298
|
+
A typical Firebolt workflow has two steps:
|
|
299
|
+
|
|
300
|
+
1. Resolve `channel_id` (and optionally video IDs) via `POST /raw/pg` or `POST /raw/es`.
|
|
301
|
+
2. Query Firebolt with those IDs.
|
|
302
|
+
|
|
303
|
+
Calling Firebolt without an indexed filter is rejected before the query runs.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## schema pg
|
|
308
|
+
|
|
309
|
+
`GET /raw/pg/schema` — Markdown-rendered PostgreSQL schema. Free. Pass `?table=<name>` to scope to a single table (matches the `tl schema pg <table>` shape and is dramatically smaller).
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
curl -sS "$TL_API_BASE/raw/pg/schema?table=thoughtleaders_channel" \
|
|
313
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
314
|
+
-H 'X-TL-Auth: API-KEY' | jq -r '.content' | less
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
schema = get('/raw/pg/schema', params={'table': 'thoughtleaders_channel'})
|
|
319
|
+
print(schema['content']) # markdown body
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Response:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"name": "pg",
|
|
327
|
+
"description": "PostgreSQL schema reference for `POST /raw/pg`",
|
|
328
|
+
"content_type": "markdown",
|
|
329
|
+
"content": "# PostgreSQL Schema Reference\n\n..."
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Always pull the table-scoped form when you know which table you need — the unscoped form lists every visible table and is much larger.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## schema es
|
|
338
|
+
|
|
339
|
+
`GET /raw/es/schema` — Markdown reference for the Elasticsearch `tl-platform` index document shape. Free. **No `?table=` parameter** — Elasticsearch is one document shape; passing one returns `400`.
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
curl -sS "$TL_API_BASE/raw/es/schema" \
|
|
343
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
344
|
+
-H 'X-TL-Auth: API-KEY' | jq -r '.content' | less
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
print(get('/raw/es/schema')['content'])
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## schema fb
|
|
354
|
+
|
|
355
|
+
`GET /raw/fb/schema` — Markdown reference for the two Firebolt tables (`article_metrics`, `channel_metrics`). Free. Pass `?table=article_metrics` or `?table=channel_metrics` to scope.
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
curl -sS "$TL_API_BASE/raw/fb/schema?table=article_metrics" \
|
|
359
|
+
-H "Authorization: Bearer $TL_API_KEY" \
|
|
360
|
+
-H 'X-TL-Auth: API-KEY' | jq -r '.content'
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
print(get('/raw/fb/schema', params={'table': 'article_metrics'})['content'])
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## End-to-end example
|
|
370
|
+
|
|
371
|
+
Find Holafly's most-viewed sponsored videos in the past 6 months, then pull their view-curves from Firebolt:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
import os, requests
|
|
375
|
+
|
|
376
|
+
BASE = 'https://app.thoughtleaders.io/api/cli/v1'
|
|
377
|
+
H = {'Authorization': f"Bearer {os.environ['TL_API_KEY']}", 'X-TL-Auth': 'API-KEY'}
|
|
378
|
+
|
|
379
|
+
def post(path, body):
|
|
380
|
+
r = requests.post(f'{BASE}{path}', headers=H, json=body, timeout=60)
|
|
381
|
+
r.raise_for_status()
|
|
382
|
+
return r.json()
|
|
383
|
+
|
|
384
|
+
# 1) Find Holafly's brand id
|
|
385
|
+
brand = post('/raw/pg', {'query':
|
|
386
|
+
"SELECT id FROM thoughtleaders_brand WHERE name = 'Holafly' LIMIT 1 OFFSET 0"
|
|
387
|
+
})['results'][0]['id']
|
|
388
|
+
|
|
389
|
+
# 2) Pull the top Holafly-sponsored videos from ES, ranked by views.
|
|
390
|
+
es_resp = post('/raw/es', {'query': {
|
|
391
|
+
'size': 0,
|
|
392
|
+
'query': {'term': {'sponsored_brand_mentions': str(brand)}},
|
|
393
|
+
'aggs': {'top_videos': {
|
|
394
|
+
'terms': {'field': '_id', 'size': 10, 'order': {'max_views': 'desc'}},
|
|
395
|
+
'aggs': {'max_views': {'max': {'field': 'views'}}},
|
|
396
|
+
}},
|
|
397
|
+
}})
|
|
398
|
+
video_ids = [b['key'] for b in es_resp['results'][0]['aggregations']['top_videos']['buckets']]
|
|
399
|
+
|
|
400
|
+
# 3) Each video id is `<channel_id>:<youtube_id>` — split for the Firebolt query.
|
|
401
|
+
pairs = [vid.split(':') for vid in video_ids]
|
|
402
|
+
channel_ids = sorted({int(c) for c, _ in pairs})
|
|
403
|
+
youtube_ids = [y for _, y in pairs]
|
|
404
|
+
|
|
405
|
+
fb_resp = post('/raw/fb', {'query': f"""
|
|
406
|
+
SELECT id, channel_id, age, view_count
|
|
407
|
+
FROM article_metrics
|
|
408
|
+
WHERE channel_id IN ({','.join(map(str, channel_ids))})
|
|
409
|
+
AND id IN ({','.join(repr(y) for y in youtube_ids)})
|
|
410
|
+
ORDER BY channel_id, id, age
|
|
411
|
+
"""})
|
|
412
|
+
|
|
413
|
+
for row in fb_resp['results']:
|
|
414
|
+
print(row)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The same shape works with the synchronous `httpx` client; swap `requests` for `httpx` and the API is the same.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## See also
|
|
422
|
+
|
|
423
|
+
- [README.md](README.md) — install, the `tl` CLI, agent integrations.
|
|
424
|
+
- `tl describe` — discover every endpoint, its fields/filters, and current credit rates.
|
|
425
|
+
- `tl schema pg|fb|es [<table>]` — fetch the same schema bodies these endpoints serve.
|
|
426
|
+
- `tl doctor` — verify auth and latency against the API base from the CLI before integrating.
|