Monopigi

Python SDK

The official Python SDK provides typed sync and async clients, automatic pagination, disk caching, retry logic, and data export.

Installation

pip install monopigi-sdk

Optional dependencies for data export:

pip install monopigi-sdk[polars]   # Parquet export + DataFrame conversion

Authentication

Pass your API key directly or store it via the CLI:

from monopigi import MonopigiClient

# Option 1: Pass directly
client = MonopigiClient(token="mp_live_YOUR_KEY")

# Option 2: Use stored credentials (from CLI login)
client = MonopigiClient()

# Option 3: Environment variable
import os
client = MonopigiClient(token=os.environ["MONOPIGI_API_KEY"])

Store credentials via the CLI:

monopigi auth login mp_live_YOUR_KEY

Client Configuration

client = MonopigiClient(
    token="mp_live_...",           # API key
    base_url="https://api.monopigi.com",  # Default
    max_retries=3,                 # Auto-retry on 429 (rate limit)
    cache_ttl=300,                 # Disk cache TTL in seconds (None = disabled)
)

The client supports context manager usage:

with MonopigiClient() as client:
    results = client.search("hospital")
    # connection is properly closed after the block

Core Methods

List Sources

sources = client.sources()
for src in sources:
    print(f"{src.name}: {src.label} ({src.status})")

Full-text search across all or a specific source (Pro tier required):

# Search all sources
results = client.search("hospital procurement", limit=20)
print(f"Found {results.total} results")
for doc in results.results:
    print(f"  {doc.title} ({doc.source})")

# Search a specific source
results = client.search("renewable energy", source="rae", limit=10)

Browse Documents

Query documents from a specific source:

docs = client.documents("ted", limit=50, since="2026-01-01")
print(f"{docs.total} TED documents since 2026")
for doc in docs.documents:
    print(doc.title)

Statistics

stats = client.stats()
print(f"Total documents: {stats.total_documents}")
for name, info in stats.sources.items():
    print(f"  {name}: {info.documents} docs, avg quality {info.avg_quality:.2f}")

Usage

usage = client.usage()
print(f"Tier: {usage.tier}")
print(f"Used: {usage.daily_used}/{usage.daily_quota}")
print(f"Remaining: {usage.daily_remaining}")

Tier Info

# Check current tier
print(client.tier)  # "pro"

# Check rate limit info
print(client.quota)  # QuotaInfo(limit=5000, remaining=4958, reset=...)

# Check if a feature is available
if client.has_feature("full_text"):
    results = client.search("query")

Reports

Create and manage due diligence reports (Pro tier required):

# Create a report by AFM (VAT number)
report = client.create_report(entity_identifier="123456789", identifier_type="afm")
print(f"Report {report['id']} status: {report['status']}")

# List all reports
reports = client.list_reports(limit=10)
for r in reports["items"]:
    print(f"  {r['id']} — {r['entity_identifier']} ({r['status']})")

# Poll until complete
import time
while True:
    report = client.get_report(report["id"])
    if report["status"] == "completed":
        break
    time.sleep(5)

# Access the report data
print(report["report_json"])

# Download PDF
pdf_bytes = client.get_report_pdf(report["id"])
with open("report.pdf", "wb") as f:
    f.write(pdf_bytes)

Alerts

Create and manage procurement alert profiles (Pro tier required):

# Create an alert profile
profile = client.create_alert_profile(
    name="Hospital procurement",
    filters={
        "keywords": ["hospital", "medical equipment"],
        "sources": ["ted", "kimdis"],
        "min_value": 50000,
    },
    channels=["email", "webhook"],
    webhook_url="https://example.com/hooks/monopigi",
    notify_email="alerts@example.com",
)
print(f"Profile {profile['id']}: {profile['name']}")

# List profiles
profiles = client.list_alert_profiles()
for p in profiles["items"]:
    print(f"  {p['name']} — active={p['is_active']}")

# Update a profile
client.update_alert_profile(profile["id"], name="Updated name", is_active=False)

# Deactivate a profile
client.delete_alert_profile(profile["id"])

# List recent alert deliveries
deliveries = client.list_alert_deliveries(limit=20)
for d in deliveries["items"]:
    print(f"  {d['document_source_id']} via {d['channel']} ({d['delivery_status']})")

Monitoring

Add entities to continuous compliance monitoring (Enterprise tier required):

# Add an entity to monitor
entity = client.add_monitored_entity(
    entity_identifier="123456789",
    identifier_type="afm",
    label="ACME SA",
)
print(f"Monitoring {entity['label']} ({entity['id']})")

# List monitored entities
entities = client.list_monitored_entities()
for e in entities["items"]:
    print(f"  {e['label']} — last checked: {e['last_checked_at']}")

# Stop monitoring
client.remove_monitored_entity(entity["id"])

# List events for all monitored entities
events = client.list_entity_events(event_type="contract_award", since="2026-03-01T00:00:00Z")
for ev in events["items"]:
    print(f"  [{ev['event_type']}] {ev['summary']} (detected: {ev['detected_at']})")

# Acknowledge an event
client.acknowledge_event(ev["id"])

# Trigger a health report for a monitored entity
result = client.entity_health_report(entity["id"])
print(f"Report {result['report_id']} is {result['status']}")

Models

List available LLM models (no authentication required):

models = client.models()
for m in models.models:
    default = " (default)" if m.default else ""
    print(f"  {m.id}{default}")

Source-Specific Clients

Each data source has a typed sub-client:

with MonopigiClient() as client:
    # TED procurement notices
    notices = client.ted.notices(limit=10, since="2026-01-01")

    # Diavgeia government decisions
    decisions = client.diavgeia.decisions(limit=10)

    # ELSTAT statistical datasets
    datasets = client.elstat.datasets(limit=10)

    # RAE energy permits
    permits = client.rae.permits(limit=10)

    # data.gov.gr open data
    data = client.data_gov_gr.datasets(limit=10)

    # MITOS public service organizations
    orgs = client.mitos.organizations(limit=10)

Auto-Pagination

Iterator methods automatically handle pagination:

# Iterate through all search results
for doc in client.search_iter("public health", page_size=100):
    print(doc.title)

# Iterate through all documents from a source
for doc in client.documents_iter("diavgeia", since="2025-01-01"):
    process(doc)

Data Export

Export documents to JSON, CSV, or Parquet files:

# Export to JSON
count = client.export("ted", "ted_data.json", format="json")
print(f"Exported {count} documents")

# Export to CSV with date filter
client.export("diavgeia", "decisions.csv", format="csv", since="2026-01-01")

# Export to Parquet (requires polars)
client.export("elstat", "stats.parquet", format="parquet", limit=5000)

DataFrame Conversion

Search and document responses can be converted to Polars DataFrames:

results = client.search("procurement")
df = results.to_df()  # requires polars
print(df.head())

Async Client

For async applications (FastAPI, async scripts):

import asyncio
from monopigi import AsyncMonopigiClient

async def main():
    async with AsyncMonopigiClient() as client:
        results = await client.search("energy regulation")
        for doc in results.results:
            print(doc.title)

        sources = await client.sources()
        stats = await client.stats()

asyncio.run(main())

Caching

Enable disk caching to avoid redundant API calls:

# Cache responses for 5 minutes
client = MonopigiClient(cache_ttl=300)

# First call hits the API
sources = client.sources()

# Second call returns cached result (no API call)
sources = client.sources()

Cache files are stored in ~/.monopigi/cache/ as SHA-256-keyed JSON files.

Error Handling

from monopigi import MonopigiClient, AuthError, RateLimitError, NotFoundError, TierError

with MonopigiClient() as client:
    try:
        results = client.search("test")
    except AuthError:
        print("Invalid or missing API key")
    except RateLimitError as e:
        print(f"Rate limited, resets at: {e.reset_at}")
    except TierError:
        print("Feature not available on your tier — upgrade to Pro")
    except NotFoundError:
        print("Resource not found")

Response Format

All SDK methods return plain Python dicts matching the JSON responses from the API. See the API Reference for the full response schemas.