typesearch
The official Python client for the typesearch API: news search for AI agents, with a calibrated relevance score on every result.
pip install typesearch
Python 3.9+. Sync and async. Built on httpx and pydantic: every response is a typed model, and unknown
fields from a newer API are kept instead of breaking your code.
Quickstart
Create a key in the dashboard and set it as TYPESEARCH_API_KEY:
from typesearch import Typesearch
ts = Typesearch() # reads TYPESEARCH_API_KEY
res = ts.search("inflation", mode="fast", max_results=5)
for r in res.results:
print(f"{r.score:.2f}", r.title, f"({r.source})")
Every result carries a score: the calibrated probability that it is about your query. Threshold on it
directly — 0.9 means relevant, and anything between 0.35 and 0.65 means the model is undecided.
Create a client
| Argument | Default | |
|---|---|---|
api_key |
TYPESEARCH_API_KEY |
Your key. |
base_url |
https://api.typesearch.ai |
Or TYPESEARCH_BASE_URL. |
timeout |
70.0 |
Seconds before a request is aborted. A deep search can take about a minute. |
max_retries |
2 |
Retries on connection errors, 429 rate_limited and 5xx. |
default_headers |
— | Headers sent with every request. |
http_client |
— | Your own httpx.Client (or httpx.AsyncClient for async), for proxies or tests. |
Use it as a context manager to close connections when you are done: with Typesearch() as ts: …. Keep
keys on the server: never ship one to a browser or a mobile app.
What you can do
| Method | Endpoint | |
|---|---|---|
search(query, **options) |
POST /v1/search |
Search the index: one query, or up to five judged together. |
search_stream(query, **options) |
POST /v1/search |
The same, as events: steps, partial results, the final result. |
similar(url, **options) |
POST /v1/similar |
Articles about the same story as a URL. |
contents(urls, query=…) |
POST /v1/contents |
Metadata and a short verbatim excerpt of up to 10 URLs. |
site_search(site, query, **options) |
POST /v1/search/site |
Search a live site. Returns a job. |
site_search_and_wait(site, query, **options) |
POST /v1/search/site |
The same, waiting for the result. |
site_search_stream(site, query, **options) |
POST /v1/search/site |
The same, as events. |
jobs.get(id) · jobs.wait(id) |
GET /v1/jobs/{id} |
A job's status and result. |
usage() |
GET /v1/usage |
Usage, limits and credit of your key. |
Keyword arguments have the same names as the HTTP API and are
typed (SearchOptions, SimilarOptions… in typesearch.types), so your editor and type checker know
every one of them. Responses are pydantic models generated from the API's OpenAPI document: r.title,
res.usage.cost_usd, res.model_dump().
Search
res = ts.search(
"el dólar",
mode="normal", # "ultra" | "fast" | "normal" | "deep"
max_results=10,
include_domains=["reddiaria.example", "diarioejemplo.example"],
published_after="2026-09-20", # or a datetime.date / an aware datetime.datetime
highlights=True,
)
# Several queries at once, judged together
res = ts.search(["el dólar", "el FMI"], mode="fast")
for group in res.groups or []:
print(group.query, group.total)
days=None searches the whole index; leaving days out keeps the default of 7. Add questions for typed
answers on every result, and tone, dedupe or essential for enrichments — see the
guides.
mode sets how much is read, and the price: ultra (headlines only) is the cheapest, then fast,
normal and deep — see modes and pricing.
Filter by country and language
res = ts.search(
"inflation",
countries=["AR", "UY"], # ISO 3166-1 alpha-2
languages=["es"], # ISO 639-1; a tag such as pt-BR counts as pt
)
for r in res.results:
print(r.country, r.language, r.title)
Every result carries the country and language of its source (None when unknown), and similar() takes
the same two filters. The index covers news from 130+ countries in 30+ languages. Missing an outlet? Suggest
it from the dashboard (Suggest a source) or write to support@typesearch.ai.
Stream
with ts.search_stream("el dólar", mode="deep") as stream:
for event in stream:
if event.type == "step":
print("·", event.step.text)
elif event.type in ("partial", "result"):
render(event.response.results)
# Or only the final result
final = ts.search_stream("el dólar").final_response()
The request starts when you start iterating. An error event is raised as an APIError.
Similar and contents
similar = ts.similar("https://diarioejemplo.example/economia/…", exclude_domains=["diarioejemplo.example"])
pages = ts.contents(["https://reddiaria.example/economia/…"], query="el dólar")
for page in pages.results:
if page.status == "ok":
print(page.relevance, page.title, page.highlights)
else:
print(page.url, page.error.code)
contents() never returns the full text: at most one excerpt of up to 25 words per article.
Live site search
# Create the job and wait for it
res = ts.site_search_and_wait("diarioejemplo.example", "el dólar", mode="normal")
# Or handle the job yourself
job = ts.site_search("diarioejemplo.example", "el dólar")
done = ts.jobs.wait(job.id, poll_interval=2, timeout=120)
jobs.wait() returns the job's result and raises JobFailedError if the job fails.
Usage
usage = ts.usage()
print(usage.today.remaining_tokens, usage.limits.requests_per_minute)
Async
AsyncTypesearch has the same methods, awaitable:
import asyncio
from typesearch import AsyncTypesearch
async def main():
async with AsyncTypesearch() as ts:
res = await ts.search("el dólar", mode="fast")
async for event in ts.search_stream("el FMI"):
...
asyncio.run(main())
Errors
Every error subclasses TypesearchError. API errors are APIError subclasses with status, code
(stable, for programs), request_id and, for invalid requests, errors per field.
| Class | When |
|---|---|
BadRequestError |
400 |
AuthenticationError |
401 |
BudgetError |
402 — budget_too_small, insufficient_credits or spend_limit_reached |
PermissionDeniedError |
403 |
NotFoundError |
404 |
RateLimitError |
429 — with retry_after |
InternalServerError |
5xx |
APIConnectionError · APITimeoutError |
No response, or too slow |
JobFailedError |
A live site search job failed |
from typesearch import APIError, BadRequestError, RateLimitError
try:
ts.search("x")
except BadRequestError as e:
print(e.code, e.errors)
except RateLimitError as e:
print(e.code, e.retry_after)
except APIError as e:
print(e.status, e.code, e.request_id)
Retries and timeouts
Connection errors, timeouts, 429 rate_limited and 5xx are retried twice with exponential backoff and
jitter, honouring Retry-After; quota_exceeded never is. Set max_retries=0 on the client to handle
them yourself, or override it per call, with the timeout and extra headers:
res = ts.search("el dólar", mode="deep", timeout=90, max_retries=0, extra_headers={"X-Trace-Id": "abc"})
Examples
examples/ has three short scripts: search,
contents and streaming.
Development
uv sync
uv run python scripts/generate_models.py # models from openapi/openapi.json (--fetch pulls the live one)
uv run ruff check . && uv run ruff format --check . && uv run mypy
uv run pytest --cov # against a fake API that validates every request against the OpenAPI
TYPESEARCH_LIVE=1 uv run pytest tests/test_live.py # against the real API: needs TYPESEARCH_API_KEY (spends less than a cent)
License
Metadata
Release files for typesearch 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| typesearch-0.1.0.tar.gz | 62.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| typesearch-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.1 kB
Release files / typesearch-0.1.0.tar.gz
| Download URL | typesearch-0.1.0.tar.gz |
|---|---|
| Size | 62.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
49a9f8adccccdc9da5fc795b71fac904f73a0a1a2488b16e89707dc5a2bc002e
|
|
BLAKE2b-256 checksum How to use checksums |
b6db178305260841c7f2d15c9b041080f9d2a869829b4a3f581c93065b94571e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / typesearch-0.1.0-py3-none-any.whl
| Download URL | typesearch-0.1.0-py3-none-any.whl |
|---|---|
| Size | 30.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e931aeda4362da662e5b94670c0de45bea99097a1e3afd74490e8af8a9e12d17
|
|
BLAKE2b-256 checksum How to use checksums |
84b52e0d45dddeb22958793d7b0e728710a827d4e6dc1b6b8652fce03b4b16f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log