← Back to Home

PSTN2 Python SDK

PSTN2 Python SDK

Python implementation of the PSTN2 distributed telecommunications protocol.

Status: reference API — not yet on PyPI. The pstn2 package has not been published, so pip install pstn2 will not work yet. The examples in examples/ 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 pstn2

Requirements

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

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 pstn2

Using Poetry

# Initialize project
poetry new my-pstn2-app
cd my-pstn2-app

# Add dependency
poetry add pstn2

# Activate shell
poetry shell
# Create project
uv init my-pstn2-app
cd my-pstn2-app

# Add dependency
uv add pstn2

# Run script
uv run python main.py

Running Examples

  1. 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"
  1. 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.py

SDK 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 True

Performance Tips

  1. Reuse Client Instances: Create one client and reuse it across requests
  2. Connection Pooling: httpx automatically pools connections
  3. Async All the Way: Use async/await throughout your application
  4. Cache Directory Results: Store CP lookups with TTL

Examples

See the examples/ directory for complete working examples:

See EXAMPLES.md for detailed documentation.

Further Reading

License

See main project LICENSE file.