Skip to content

Python client

tnl-intelligence gives you sync and async API clients plus webhook verification.

bash
pip install tnl-intelligence

Requires Python 3.10 or newer.

Quick start

python
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

python
tnl = TnlClient(
    os.environ["TNL_API_KEY"],
    base_url="https://theneuralledger.com",  # default
    timeout=30.0,
    retries=2,          # 429 and 5xx, with backoff
)

Async

python
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:

python
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:

python
story = page.data[0]
story.title          # str
story.published_at   # datetime
story.impact_paths   # list[str]

Rate limits

python
page = tnl.list_news(page_size=1)
print(tnl.last_rate_limit)   # RateLimit(limit=..., remaining=..., reset_at=...)

Errors

python
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:

python
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 "", 204

request.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:

python
from tnl_intelligence import WEBHOOK_EVENT_TYPES, WEBHOOK_SCHEMA_VERSION

The Neural Ledger API