v0.2.0 · Beta
Getting Started
Python SDK
An async Python client for the Polari API. Handles authentication, the submit/poll/retrieve
job pattern, retries, and cost tracking out of the box.
Beta SDK. The Python SDK has full support for all four layers — Layer 0 through Layer 3.
Layer 3 (Intelligence Graph) requires a Professional or higher tier key.
Installation
Install via pip:
Authentication
Pass your API key directly to the client, or set the POLARI_API_KEY environment variable and use
from_env().
from polari import PolariClient
# Explicit key
client = PolariClient(api_key="pk_live_your_key")
# From environment variable POLARI_API_KEY
client = PolariClient.from_env()
Basic usage
The client is async. Use it as a context manager to ensure the underlying HTTP connection is properly closed.
import asyncio
from polari import PolariClient, ArticleInput
async def main():
async with PolariClient(api_key="pk_live_your_key")
as client:
article = ArticleInput(
title="Fed Holds Rates Steady",
content="The Federal Reserve held interest rates steady on Wednesday, as
policymakers...",
url="https://reuters.com/fed-rates-2026",
source="Reuters",
)
result = await client.layer0.analyze(article)
print(f"Quality:
{result.quality_score:.3f}") # → 0.810
print(f"Tokens:
{result.token_count}") # → 842
print(f"ID:
{result.article_id}") # → art_8f7h2k9s
asyncio.run(main())
The SDK abstracts the submit/poll/retrieve pattern — analyze() handles polling internally and
returns when processing is complete.
Batch processing
Process multiple articles with analyze_batch(). Articles are submitted in a single batch
request, then all jobs are polled concurrently — significantly faster than processing sequentially.
The batch_size parameter controls how many are sent per API call (max 50).
articles = [
ArticleInput(title="Article One", content="...",
source="Reuters"),
ArticleInput(title="Article Two", content="...",
source="Bloomberg"),
ArticleInput(title="Article Three", content="...",
source="AP"),
]
results = await client.layer0.analyze_batch(articles,
batch_size=10)
for r in results:
print(f"{r.article_id}
quality={r.quality_score:.2f}")
Full pipeline
Each layer builds on the previous. Layer 1 requires a Layer 0 article ID; Layer 2 requires both Layer 0
and Layer 1 to have run first.
async with PolariClient(api_key="pk_live_your_key") as client:
article = ArticleInput(
title="Fed Holds Rates Steady",
content="The Federal Reserve held interest rates steady...",
url="https://reuters.com/fed-rates-2026",
source="Reuters",
)
# Layer 0 — quality score + token embeddings
l0 = await client.layer0.analyze(article)
print(f"Quality:
{l0.quality_score:.3f} ID: {l0.article_id}")
# Layer 1 — entity extraction + sentence semantics
l1 = await client.layer1.process(
article_id=l0.article_id,
title=article.title,
content=article.content,
)
print(f"Entities:
{l1.stats.entity_count} Sentences: {l1.stats.sentence_count}")
print(f"People:
{l1.entities.get('PERSON', [])}")
# Layer 2 — story clustering
l2 = await client.layer2.cluster(l0.article_id)
print(f"Cluster:
{l2.cluster_id} Confidence: {l2.confidence:.3f}
")
# Layer 3 — intelligence graph (Pro+ only)
trends = await client.layer3.get_trending_entities(limit=10)
for t in trends["trends"]:
print(f"{t['entity']}:
velocity={t['velocity']}")
Configuration
Fine-tune timeouts and retry behavior at client initialization. All layer URLs default to the Polari API
endpoints.
from polari import PolariClient
client = PolariClient(
api_key="pk_live_your_key",
timeout=60, # seconds
max_retries=3,
enable_metrics=True,
enable_cost_tracking=True,
)
Result object
analyze() returns a Layer0Result with the following fields:
| Field |
Type |
Description |
| article_id |
string |
Unique article identifier for use in downstream API calls |
| quality_score |
float |
0–1 content quality. Articles below 0.53 are filtered before Layer 1 and Layer 2. |
| token_count |
integer |
Article length in tokens |
| semantic_hash |
string |
Content fingerprint for deduplication |
| embedding |
List[float] |
384-dim token embedding. Returns [0.0] * 384 unless include_embedding=True
is passed to analyze(). |
| is_duplicate |
bool |
Whether this URL was already processed. If True, the returned article_id is
the existing article — still valid for Layer 1 and Layer 2. |
| processing_time |
float |
End-to-end processing time in seconds |
Embeddings
By default, the 384-dimensional token embedding is not included in the response. Pass
include_embedding=True to fetch it from the vector store. Note this adds latency as the embedding
is retrieved from ChromaDB.
result = await client.layer0.analyze(article, include_embedding=True)
result.embedding # List[float], length 384
result.token_count # number of tokens the embedding was averaged from
If include_embedding=False (default), result.embedding returns
[0.0] * 384.
Error handling
The SDK raises structured exceptions. AuthenticationError and ValidationError are
not retried — all other errors use exponential backoff with jitter.
from polari.exceptions import (
AuthenticationError,
RateLimitError,
ValidationError,
RetryExhaustedError,
PolariError,
)
try:
result = await client.layer0.analyze(article)
except AuthenticationError:
print("Invalid API key")
except RateLimitError:
print("Rate limit hit — back off and retry")
except ValidationError as e:
print(f"Bad request: {e}")
# e.g. text too short
except RetryExhaustedError:
print("Max retries exceeded")
except PolariError as e:
print(f"Unexpected error:
{e}")
Exception hierarchy
PolariError
├── PolariAPIError(status_code, message)
│ ├── RateLimitError # 429 — retried with backoff
│ ├── AuthenticationError # 401 — not retried
│ ├── ValidationError # 400/422 — not retried
│ └── ServerError # 5xx — retried with backoff
├── NetworkError # connectivity — retried
├── TimeoutError # request timeout — retried
├── ConfigurationError # invalid config — not retried
├── ProcessingError # article processing failure
└── RetryExhaustedError # max retries exceeded
Metrics & cost tracking
The client tracks per-request latency, success rate, and per-layer cost automatically.
# After processing some articles...
metrics = client.get_metrics()
print(metrics.total_requests) # → 42
print(metrics.average_latency) # → 1.24s
print(metrics.successful_requests) # → 41
cost = client.get_cost_summary()
print(cost.total_cost) # → $0.042
print(cost.cost_by_layer) # → {"layer0": 0.042}
| Layer |
Cost per call |
| Layer 0 |
$0.001 |
| Layer 1 |
$0.002 |
| Layer 2 |
$0.001 |
| Layer 3 |
$0.003 |
ArticleInput reference
| Field |
Type |
Description |
| contentrequired |
string |
Article body. Minimum 100 characters. |
| titlerequired |
string |
Article headline |
| url |
string |
Canonical URL — used as deduplication key when provided |
| source |
string |
Publisher name |
| author |
string |
Byline |
| published_at |
datetime |
Original publication time |
| metadata |
dict |
Arbitrary key/value pairs passed through to the result |