Python client
tnl-intelligence gives you sync and async API clients plus webhook verification.
pip install tnl-intelligenceRequires Python 3.10 or newer.
Quick start
import os
from tnl_intelligence import TnlClient
with TnlClient(os.environ["TNL_API_KEY"]) as tnl:
page = tnl.list_news(page_size=10, sort="pipeline")
for story in page.data:
print(story.published_at, story.title)The client is a context manager so the underlying HTTP connection pool is closed properly. Without with, close it yourself.
Options
tnl = TnlClient(
os.environ["TNL_API_KEY"],
base_url="https://theneuralledger.com", # default
timeout=30.0,
retries=2, # 429 and 5xx, with backoff
)Async
import asyncio
from tnl_intelligence import AsyncTnlClient
async def main() -> None:
async with AsyncTnlClient(os.environ["TNL_API_KEY"]) as tnl:
japan, korea = await asyncio.gather(
tnl.list_news(country="Japan", page_size=20),
tnl.list_news(country="South Korea", page_size=20),
)
print(len(japan.data), len(korea.data))
asyncio.run(main())Same methods as the sync client, awaited. Use it when fetching several slices concurrently; there is no benefit for a single sequential call.
Pagination
There is also iterate_news, which walks pages for you. The explicit loop below is worth knowing when you need control over checkpointing:
def every_story(tnl: TnlClient, since: str):
cursor = None
while True:
page = tnl.list_news(published_since=since, page_size=100, cursor=cursor)
yield from page.data
cursor = page.page.next_cursor
if not cursor:
return
with TnlClient(os.environ["TNL_API_KEY"]) as tnl:
for story in every_story(tnl, "2026-07-01T00:00:00Z"):
save(story)Cursors stay stable while new stories arrive; offsets do not. Use cursors for anything walking more than one page.
Typed models
Responses are typed — Story, NewsPage, PageMetadata, RateLimit — so attribute access is checked rather than dictionary guesswork:
story = page.data[0]
story.title # str
story.published_at # datetime
story.impact_paths # list[str]Rate limits
page = tnl.list_news(page_size=1)
print(tnl.last_rate_limit) # RateLimit(limit=..., remaining=..., reset_at=...)Errors
from tnl_intelligence import TnlError, TnlRateLimitError
try:
page = tnl.list_news(page_size=1)
except TnlRateLimitError:
... # exhausted after built-in retries
except TnlError as error:
print(error.status, error.code, error.request_id)Verifying webhooks
The same package verifies webhook deliveries, so a Python receiver needs no extra dependency:
from tnl_intelligence import verify_webhook, WebhookVerificationError
KEYS = {os.environ["TNL_WEBHOOK_KEY_ID"]: os.environ["TNL_WEBHOOK_SECRET"].encode()}
@app.post("/hooks/tnl")
def receive():
try:
verify_webhook(request.get_data(), request.headers, KEYS)
except WebhookVerificationError:
return "", 401
enqueue(request.get_json())
return "", 204request.get_data() gives the raw bytes the signature was computed over. Verify before parsing. Full detail in verifying webhooks.
Event type constants are exported too:
from tnl_intelligence import WEBHOOK_EVENT_TYPES, WEBHOOK_SCHEMA_VERSION