thoughtleaders-cli 0.6.46__tar.gz → 0.6.47__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.
Files changed (97) hide show
  1. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/.claude-plugin/plugin.json +1 -1
  2. thoughtleaders_cli-0.6.47/API.md +426 -0
  3. thoughtleaders_cli-0.6.47/PKG-INFO +305 -0
  4. thoughtleaders_cli-0.6.47/README.md +277 -0
  5. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/pyproject.toml +1 -1
  6. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/__init__.py +1 -1
  7. thoughtleaders_cli-0.6.47/src/tl_cli/commands/describe.py +314 -0
  8. thoughtleaders_cli-0.6.46/PKG-INFO +0 -245
  9. thoughtleaders_cli-0.6.46/README.md +0 -217
  10. thoughtleaders_cli-0.6.46/docs/architecture.md +0 -574
  11. thoughtleaders_cli-0.6.46/src/tl_cli/commands/describe.py +0 -177
  12. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/.claude-plugin/marketplace.json +0 -0
  13. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/.github/workflows/python-publish.yml +0 -0
  14. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/.gitignore +0 -0
  15. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/AGENTS.md +0 -0
  16. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/CLAUDE.md +0 -0
  17. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/LICENSE +0 -0
  18. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/agents/tl-analyst.md +0 -0
  19. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/hooks/hooks.json +0 -0
  20. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/hooks/scripts/load-tl-skill.mjs +0 -0
  21. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/hooks/scripts/post-usage.sh +0 -0
  22. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/hooks/scripts/pre-check.sh +0 -0
  23. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl/SKILL.md +0 -0
  24. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl/references/business-glossary.md +0 -0
  25. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl/references/elasticsearch-schema.md +0 -0
  26. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl/references/firebolt-schema.md +0 -0
  27. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl/references/postgres-schema.md +0 -0
  28. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-import/SKILL.md +0 -0
  29. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/SKILL.md +0 -0
  30. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
  31. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/examples/golden_queries.md +0 -0
  32. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/columns_brands.md +0 -0
  33. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/columns_channels.md +0 -0
  34. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/columns_content.md +0 -0
  35. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
  36. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
  37. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
  38. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/report_glossary.md +0 -0
  39. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/sortable_columns.json +0 -0
  40. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
  41. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
  42. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/references/widgets.md +0 -0
  43. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/column_builder.md +0 -0
  44. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/database_query.md +0 -0
  45. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/keyword_research.md +0 -0
  46. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/name_resolver.md +0 -0
  47. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/sample_judge.md +0 -0
  48. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/similar_channels.md +0 -0
  49. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
  50. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/skills/tl-report-builder/tools/widget_builder.md +0 -0
  51. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/_completions.py +0 -0
  52. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/auth/__init__.py +0 -0
  53. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/auth/commands.py +0 -0
  54. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/auth/finalize.py +0 -0
  55. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/auth/login.py +0 -0
  56. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/auth/pkce.py +0 -0
  57. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/auth/token_store.py +0 -0
  58. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/client/__init__.py +0 -0
  59. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/client/errors.py +0 -0
  60. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/client/http.py +0 -0
  61. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/__init__.py +0 -0
  62. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/_comments_common.py +0 -0
  63. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/ask.py +0 -0
  64. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/balance.py +0 -0
  65. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/brands.py +0 -0
  66. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/bulk_import.py +0 -0
  67. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/changelog.py +0 -0
  68. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/channels.py +0 -0
  69. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/credits.py +0 -0
  70. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/db.py +0 -0
  71. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/deals.py +0 -0
  72. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/doctor.py +0 -0
  73. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/matches.py +0 -0
  74. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/proposals.py +0 -0
  75. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/recommender.py +0 -0
  76. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/reports.py +0 -0
  77. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/schema.py +0 -0
  78. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/setup.py +0 -0
  79. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/snapshots.py +0 -0
  80. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/sponsorships.py +0 -0
  81. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/uploads.py +0 -0
  82. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/commands/whoami.py +0 -0
  83. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/config.py +0 -0
  84. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/filters.py +0 -0
  85. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/hints.py +0 -0
  86. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/main.py +0 -0
  87. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/output/__init__.py +0 -0
  88. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/output/formatter.py +0 -0
  89. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/src/tl_cli/self_update.py +0 -0
  90. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/__init__.py +0 -0
  91. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/test_auth.py +0 -0
  92. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/test_filters.py +0 -0
  93. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/test_http_auth.py +0 -0
  94. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/test_output.py +0 -0
  95. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/test_reports.py +0 -0
  96. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/tests/test_sponsorships.py +0 -0
  97. {thoughtleaders_cli-0.6.46 → thoughtleaders_cli-0.6.47}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.6.46",
3
+ "version": "0.6.47",
4
4
  "description": "ThoughtLeaders CLI — query sponsorship deals, channels, brands, uploads, and intelligence from the terminal",
5
5
  "author": {
6
6
  "name": "ThoughtLeaders",
@@ -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
+ Create an API key from Django Admin → **API keys** → Add. The shown 64-char hex value is the only secret — keep it out of version control. Keys carry an `is_active` flag and an `expires_at` (defaults to 1 year from creation). 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": 9_995.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": 9_995.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": 9_995.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": 320_000_000},
204
+ ...
205
+ ],
206
+ "total": 5,
207
+ "limit": 5,
208
+ "offset": 0,
209
+ "usage": { "credits_charged": 1.84, "credit_rate": 1.4, "balance_remaining": 9_994.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 (free name lookup endpoint covered elsewhere — use db_pg here)
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.