PSTN2 Python SDK
Python implementation of the PSTN2 distributed telecommunications protocol.
Status: reference API — not yet on PyPI. The
pstn2package has not been published, sopip install pstn2will not work yet. The examples inexamples/and the snippets below illustrate the intended SDK surface.
Installation
Once the SDK is published to PyPI, installation will be:
pip install pstn2
# or
poetry add pstn2
# or
uv add pstn2Requirements
- Python 3.11 or later
- Dependencies:
httpx,cryptography,pydantic
Quick Start
import asyncio
import os
from pstn2.client import PSTN2Client, AuthenticationMode
async def main():
client = PSTN2Client(
cp_id='CP1-UK-0001',
api_endpoint='https://api.yourcp.com/pstn2/v1',
private_key=os.environ['PSTN2_PRIVATE_KEY'],
auth_mode=AuthenticationMode.DIRECT_QUERY,
)
# Verify an inbound call
verification = await client.auth.verify_call(
caller_id='+441234567890',
called_id='+447700900123',
call_reference='abc-123-def-456',
)
print(f'Call verified: {verification.verified}')
await client.close()
asyncio.run(main())Features
- ✅ Authentication: Verify caller IDs using Direct Query or Token Pool
- ✅ Direct Routing: Discover peer-to-peer routing paths
- ✅ End-to-End Encryption: Per-call key exchange with Ed25519
- ✅ Call Branding: Display verified caller information
- ✅ Emergency Services: Real-time GPS location for 999/112/911 calls
- ✅ Directory Service: Distributed number directory lookups
- ✅ Async/Await: Built on modern Python asyncio
- ✅ Type Safety: Full type hints with Pydantic models
Project Setup
Using pip
# Create virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install package
pip install pstn2Using Poetry
# Initialize project
poetry new my-pstn2-app
cd my-pstn2-app
# Add dependency
poetry add pstn2
# Activate shell
poetry shellUsing uv (Recommended for speed)
# Create project
uv init my-pstn2-app
cd my-pstn2-app
# Add dependency
uv add pstn2
# Run script
uv run python main.pyRunning Examples
- Set environment variables:
export PSTN2_PRIVATE_KEY="your-private-key-here"
export CP1_PRIVATE_KEY="your-cp1-key-here"
export PSAP_PRIVATE_KEY="your-psap-key-here"
export TOKEN_POOL_JWT="your-jwt-token-here"- Run an example:
python examples/01_basic_authentication.py
python examples/02_direct_routing.py
python examples/03_token_pool.py
python examples/04_emergency_services.py
python examples/05_complete_call_flow.pySDK API Reference
PSTN2Client
from pstn2.client import PSTN2Client, AuthenticationMode
client = PSTN2Client(
cp_id='CP1-UK-0001',
api_endpoint='https://api.yourcp.com/pstn2/v1',
private_key=os.environ['PSTN2_PRIVATE_KEY'],
auth_mode=AuthenticationMode.DIRECT_QUERY,
timeout=2.0, # seconds
retries=3,
log_level='info',
)Authentication Module
# Verify call (Direct Query)
verification = await client.auth.verify_call(
caller_id='+441234567890',
called_id='+447700900123',
call_reference='abc-123-def-456',
)
# Create token (Token Pool)
token = await client.auth.create_token(
caller_id='+441234567890',
called_id='+447700900123',
call_reference='token-call-123',
ttl=30, # seconds
)
# Verify token
verification = await client.auth.verify_token(token.token_id)Routing Module
from pstn2.types import MediaCapabilities, CallBranding
routing = await client.routing.request_routing(
destination_number='+447700900123',
caller_id='+441234567890',
call_reference='xyz-789-abc-012',
media_capabilities=MediaCapabilities(
codecs=['opus', 'g722', 'pcmu'],
encryption=['srtp-aes256', 'srtp-aes128'],
video=False,
max_bandwidth=128000,
),
branding=CallBranding(
display_name='ACME Support',
logo='https://cdn.acme.com/logo.png',
background_color='#0066cc',
call_purpose='Account Security Alert',
),
)Directory Module
# Lookup number
cp_info = await client.directory.lookup('+441234567890')
print(f'Hosted by: {cp_info.cp_id}')
# Reverse lookup
ranges = await client.directory.reverse('CP1-UK-0001')Emergency Module
# Get caller location (PSAP only)
location = await client.emergency.get_location(
caller_id='+441234567890',
call_reference='emergency-456-789',
psap_id='UK-999-LONDON-CENTRAL',
)
print(f'GPS: {location.location.latitude}, {location.location.longitude}')
print(f'Accuracy: {location.location.accuracy}m')Type Safety with Pydantic
All SDK models use Pydantic for validation:
from pstn2.types import (
VerifyCallRequest,
VerifyCallResponse,
MediaCapabilities,
CallBranding,
)
# Type-safe requests
request = VerifyCallRequest(
caller_id='+441234567890',
called_id='+447700900123',
call_reference='abc-123',
)
# IDE auto-completion for responses
response: VerifyCallResponse = await client.auth.verify_call(**request.dict())
print(response.verified)
print(response.trust_level)Error Handling
from pstn2.exceptions import (
PSTN2Error,
AuthenticationError,
RoutingError,
NetworkError,
)
try:
verification = await client.auth.verify_call(...)
except AuthenticationError as e:
print(f'Authentication failed: {e}')
except NetworkError as e:
print(f'Network error: {e}')
except PSTN2Error as e:
print(f'PSTN2 error: {e}')Best Practices
1. Use Environment Variables
import os
# ✅ Good
private_key = os.environ['PSTN2_PRIVATE_KEY']
# ❌ Bad
private_key = 'hardcoded-key'2. Always Close Clients
client = PSTN2Client(...)
try:
result = await client.auth.verify_call(...)
finally:
await client.close()Or use context manager:
async with PSTN2Client(...) as client:
result = await client.auth.verify_call(...)3. Handle Errors Gracefully
try:
routing = await client.routing.request_routing(...)
if routing.accepted:
# Use PSTN2
pass
else:
# Fall back to PSTN
pass
except Exception as e:
# Always provide fallback
print(f'Error: {e}, falling back to PSTN')Integration Examples
FastAPI Integration
from fastapi import FastAPI, HTTPException
from pstn2.client import PSTN2Client
app = FastAPI()
client = PSTN2Client(...)
@app.post('/api/calls/verify')
async def verify_call(
caller_id: str,
called_id: str,
call_reference: str,
):
try:
verification = await client.auth.verify_call(
caller_id=caller_id,
called_id=called_id,
call_reference=call_reference,
)
return {'success': True, 'verification': verification}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.on_event('shutdown')
async def shutdown():
await client.close()Django Integration
# In your Django app
from pstn2.client import PSTN2Client
from django.conf import settings
client = PSTN2Client(
cp_id=settings.PSTN2_CP_ID,
api_endpoint=settings.PSTN2_API_ENDPOINT,
private_key=settings.PSTN2_PRIVATE_KEY,
auth_mode=settings.PSTN2_AUTH_MODE,
)
# In your view
async def verify_call_view(request):
verification = await client.auth.verify_call(...)
return JsonResponse({'verified': verification.verified})Testing
Unit Tests with pytest
import pytest
from pstn2.client import PSTN2Client, AuthenticationMode
@pytest.fixture
async def client():
client = PSTN2Client(
cp_id='TEST-CP-001',
api_endpoint='https://test.example.com',
private_key='test-key',
auth_mode=AuthenticationMode.DIRECT_QUERY,
)
yield client
await client.close()
@pytest.mark.asyncio
async def test_verify_call(client):
result = await client.auth.verify_call(
caller_id='+441234567890',
called_id='+447700900123',
call_reference='test-123',
)
assert result.verified is TruePerformance Tips
- Reuse Client Instances: Create one client and reuse it across requests
- Connection Pooling: httpx automatically pools connections
- Async All the Way: Use async/await throughout your application
- Cache Directory Results: Store CP lookups with TTL
Examples
See the examples/ directory for complete working
examples:
01_basic_authentication.py- Basic authentication flow02_direct_routing.py- Direct routing with media negotiation03_token_pool.py- Token Pool authentication04_emergency_services.py- Emergency location services05_complete_call_flow.py- End-to-end call scenario
See EXAMPLES.md for detailed documentation.
Further Reading
- SPECIFICATION.md - PSTN2 protocol specification
- API-SPECIFICATION.yaml - REST API documentation
- IMPLEMENTATION-GUIDE.md - Implementation patterns
License
See main project LICENSE file.