Python SDK

Complete guide to using the HOLE Foundation API Python SDK.

Installation

With pip

$pip install hole-foundation-api
$uv pip install hole-foundation-api

From Source

$git clone https://github.com/The-HOLE-Foundation/hole-api.git
$cd hole-api/sdks/python
$uv pip install -e .

Quick Start

1from hole_foundation_api import HoleFoundationClient
2import os
3
4client = HoleFoundationClient(
5 environment="https://api.theholefoundation.org",
6 token=os.environ["AUTH0_TOKEN"]
7)
8
9# Make your first API call
10health = client.health.check()
11print(f"API Status: {health.status}")

Client Configuration

Basic Configuration

1from hole_foundation_api import HoleFoundationClient
2
3client = HoleFoundationClient(
4 environment="https://api.theholefoundation.org",
5 token="your-jwt-token"
6)

Advanced Configuration

1import httpx
2from hole_foundation_api import HoleFoundationClient
3
4# Custom HTTP client
5custom_client = httpx.Client(
6 timeout=30.0,
7 limits=httpx.Limits(max_connections=100)
8)
9
10client = HoleFoundationClient(
11 environment="https://api.theholefoundation.org",
12 token=os.environ["AUTH0_TOKEN"],
13
14 # Custom timeout (default: 60 seconds)
15 timeout=30,
16
17 # Additional headers
18 headers={
19 "X-Client-Version": "1.0.0",
20 "X-Request-ID": lambda: str(uuid.uuid4())
21 },
22
23 # Custom HTTP client
24 httpx_client=custom_client
25)

Async Client

1from hole_foundation_api import AsyncHoleFoundationClient
2
3async def main():
4 client = AsyncHoleFoundationClient(
5 environment="https://api.theholefoundation.org",
6 token=os.environ["AUTH0_TOKEN"]
7 )
8
9 health = await client.health.check()
10 print(f"API Status: {health.status}")
11
12# Run with asyncio
13import asyncio
14asyncio.run(main())

For automatic token refresh:

1from hole_foundation_api import HoleFoundationClient
2import requests
3import time
4import os
5
6class TokenManager:
7 def __init__(self):
8 self.token = None
9 self.expires_at = 0
10
11 def get_token(self) -> str:
12 # Return cached token if still valid
13 if self.token and time.time() < self.expires_at:
14 return self.token
15
16 # Fetch new token from Auth0
17 response = requests.post(
18 f"https://{os.environ['AUTH0_DOMAIN']}/oauth/token",
19 json={
20 "grant_type": "client_credentials",
21 "client_id": os.environ["AUTH0_M2M_CLIENT_ID"],
22 "client_secret": os.environ["AUTH0_M2M_CLIENT_SECRET"],
23 "audience": "https://api.theholefoundation.org"
24 }
25 )
26
27 data = response.json()
28 self.token = data["access_token"]
29 self.expires_at = time.time() + data["expires_in"] - 60 # Refresh 1 min early
30
31 return self.token
32
33token_manager = TokenManager()
34
35client = HoleFoundationClient(
36 environment="https://api.theholefoundation.org",
37 token=lambda: token_manager.get_token()
38)

API Methods

Health

1# Check API health
2health = client.health.check()
3print(health.status) # "healthy"
4print(health.database) # "connected"
5print(health.version) # "2.0.0"
1# Search legal documents
2results = client.vector_search.search(
3 query="FOIA exemptions for national security",
4 limit=10,
5 include_context=True,
6 filters={
7 "jurisdiction": "federal",
8 "document_type": "statute"
9 }
10)
11
12# Access results
13for result in results.results:
14 print(result.title)
15 print(result.score)
16 print(result.excerpt)
17
18# Get specific document
19document = client.vector_search.get_document("doc-123")
20print(document.content)

Transparency

1# List jurisdictions
2jurisdictions = client.transparency.list_jurisdictions(
3 limit=50,
4 type="state" # 'federal', 'state', or 'all'
5)
6
7# Search rights of access
8rights = client.transparency.search_rights(
9 query="public records",
10 jurisdiction="california",
11 limit=20
12)
13
14# Search exemptions
15exemptions = client.transparency.search_exemptions(
16 query="privacy",
17 jurisdiction="federal",
18 category="personal_information"
19)
20
21# Get statute text
22statute = client.transparency.get_statute("5-USC-552")
23print(statute.full_text)

FOIA Dashboard

1# Create FOIA request (requires authentication)
2request = client.foia_dashboard.create_request(
3 jurisdiction="federal",
4 agency="Department of Justice",
5 subject="Freedom of Information Request",
6 description="Request for records regarding...",
7 requester_name="John Doe",
8 requester_email="john@example.com",
9 delivery_method="email"
10)
11
12# List your requests
13my_requests = client.foia_dashboard.list_requests(
14 status="pending",
15 limit=10
16)
17
18# Get request details
19request_details = client.foia_dashboard.get_request(request.id)
20
21# Update request
22updated = client.foia_dashboard.update_request(
23 request.id,
24 status="submitted",
25 notes="Request submitted to agency"
26)

Chat

1# Non-streaming chat
2response = client.chat.send_message(
3 message="What is the Freedom of Information Act?",
4 stream=False
5)
6print(response.message)
7
8# Streaming chat
9for chunk in client.chat.send_message(
10 message="Explain FOIA exemption (b)(6)",
11 stream=True
12):
13 print(chunk, end="", flush=True)

Error Handling

1from hole_foundation_api.errors import TheholetruthApiError
2
3try:
4 results = client.vector_search.search(query="test")
5except TheholetruthApiError as error:
6 print(f"API Error: {error.status_code}")
7 print(f"Message: {error.message}")
8 print(f"Body: {error.body}")
9except Exception as error:
10 print(f"Unknown error: {error}")

Type Hints

The SDK includes full type hints:

1from hole_foundation_api import HoleFoundationClient
2from hole_foundation_api.types import (
3 ChatResponse,
4 SearchRequest,
5 SearchResponse,
6 Jurisdiction,
7 RightOfAccess,
8 Exemption,
9 FoiaRequest
10)
11
12# Use types for better IDE support
13def search_documents(query: str) -> SearchResponse:
14 client = HoleFoundationClient(
15 environment="https://api.thoholefoundation.org",
16 token=os.environ["AUTH0_TOKEN"]
17 )
18
19 return client.vector_search.search(query=query, limit=10)

Framework Integration

Flask

1from flask import Flask, request, jsonify
2from hole_foundation_api import HoleFoundationClient
3
4app = Flask(__name__)
5
6client = HoleFoundationClient(
7 environment=os.environ["HOLE_API_URL"],
8 token=os.environ["AUTH0_M2M_TOKEN"]
9)
10
11@app.route('/api/search', methods=['POST'])
12def search():
13 data = request.json
14 results = client.vector_search.search(query=data['query'])
15 return jsonify(results)
16
17if __name__ == '__main__':
18 app.run()

FastAPI

1from fastapi import FastAPI
2from hole_foundation_api import HoleFoundationClient
3from pydantic import BaseModel
4
5app = FastAPI()
6
7client = HoleFoundationClient(
8 environment=os.environ["HOLE_API_URL"],
9 token=os.environ["AUTH0_M2M_TOKEN"]
10)
11
12class SearchRequest(BaseModel):
13 query: str
14 limit: int = 10
15
16@app.post("/api/search")
17async def search(request: SearchRequest):
18 results = client.vector_search.search(
19 query=request.query,
20 limit=request.limit
21 )
22 return results

Django

1# views.py
2from django.http import JsonResponse
3from hole_foundation_api import HoleFoundationClient
4import os
5
6client = HoleFoundationClient(
7 environment=os.environ["HOLE_API_URL"],
8 token=os.environ["AUTH0_M2M_TOKEN"]
9)
10
11def search_view(request):
12 query = request.POST.get('query')
13 results = client.vector_search.search(query=query)
14 return JsonResponse(results)

Testing

1# test_api.py
2import pytest
3from hole_foundation_api import HoleFoundationClient
4import os
5
6@pytest.fixture
7def client():
8 return HoleFoundationClient(
9 environment=os.environ["HOLE_API_URL"],
10 token=os.environ["AUTH0_TOKEN"]
11 )
12
13def test_health_check(client):
14 health = client.health.check()
15 assert health.status == "healthy"
16
17def test_search(client):
18 results = client.vector_search.search(query="FOIA", limit=5)
19 assert len(results.results) > 0
20
21def test_list_jurisdictions(client):
22 jurisdictions = client.transparency.list_jurisdictions()
23 assert len(jurisdictions) == 52

Run tests with uv:

$uv run pytest

Best Practices

1. Use Context Manager (Async)

1from hole_foundation_api import AsyncHoleFoundationClient
2
3async with AsyncHoleFoundationClient(
4 environment="https://api.theholefoundation.org",
5 token=os.environ["AUTH0_TOKEN"]
6) as client:
7 results = await client.vector_search.search(query="FOIA")

2. Connection Pooling

1import httpx
2
3# Reuse HTTP client for better performance
4http_client = httpx.Client(
5 limits=httpx.Limits(
6 max_connections=100,
7 max_keepalive_connections=20
8 )
9)
10
11client = HoleFoundationClient(
12 environment="https://api.theholefoundation.org",
13 token=os.environ["AUTH0_TOKEN"],
14 httpx_client=http_client
15)

3. Logging

1import logging
2
3logging.basicConfig(level=logging.DEBUG)
4logger = logging.getLogger("hole_foundation_api")
5
6# SDK will log all requests/responses

Package Information

Support