Metadata-Version: 2.5
Name: verirouteintel
Version: 1.3.0
Summary: Official VeriRoute Intel SDK for phone number intelligence - CNAM, LRN, carrier lookup, spam detection
Project-URL: Homepage, https://verirouteintel.com
Project-URL: Documentation, https://verirouteintel.com/api-docs
Project-URL: Repository, https://github.com/iamdavid-vaughan/verirouteintel-python
Project-URL: Issues, https://github.com/iamdavid-vaughan/verirouteintel-python/issues
Author-email: VeriRoute Intel <support@verirouteintel.com>
License-Expression: MIT
Keywords: caller-id,carrier,cnam,lookup,lrn,phone,robocall,sms,spam,telecom,verification,voip
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Telephony
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.22.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# VeriRoute Intel Python SDK

Official Python SDK for [VeriRoute Intel](https://verirouteintel.com) phone number intelligence API.

Get caller ID (CNAM), carrier info (LRN), spam detection, and messaging provider data for any North American phone number.

## Installation

```bash
pip install verirouteintel
```

## Quick Start

```python
from verirouteintel import VeriRoute

# Initialize client
vri = VeriRoute('your_api_key')

# Look up caller ID
caller = vri.cnam('+15551234567')
print(caller.cnam)  # "JOHN DOE"

# Get carrier info
info = vri.lrn('+15551234567')
print(info.carrier)     # "Verizon Wireless"
print(info.line_type)   # "mobile"

# Check spam status
trust = vri.trust('+15551234567')
print(trust.is_spam)          # False
print(trust.complaint_count)  # 0
```

## Features

- **CNAM Lookup** - Caller ID name for any number
- **LRN/Carrier Lookup** - Carrier, line type (mobile/landline/VoIP)
- **Enhanced Data** - City, state, ZIP, timezone, rate center
- **Spam Detection** - Spam, scam, and robocall identification
- **Messaging Provider** - SMS routing information
- **Bulk Operations** - Process up to 1000 numbers per request
- **Async Jobs** - Background jobs for up to 100,000 numbers with completion webhooks
- **Webhook Verification** - One-call HMAC-SHA256 signature verification
- **Enhanced Spam** - Multi-source spam lookup with composite scoring
- **Pricing & Status** - Programmatic rate card and platform status
- **Full Type Hints** - Complete type annotations for IDE support
- **Automatic Retries** - Built-in retry logic with exponential backoff

## Endpoint Coverage

| SDK method | Endpoint |
|---|---|
| `cnam()` / `cnam_bulk()` | `POST /api/v1/cnam` / `POST /api/v1/cnam/bulk` |
| `lrn()` / `lrn_bulk()` | `POST /api/v1/lrn` / `POST /api/v1/lrn/bulk` |
| `messaging()` | `POST /api/v1/messaging` |
| `trust()` / `trust_v2()` | `POST /api/v1/trust` / `POST /api/v2/trust` |
| `spam()` / `spam_batch()` | `POST /api/v1/spam` / `POST /api/v1/spam/batch` |
| `spam_enhanced()` | `POST /api/v1/spam/lookup/enhanced` |
| `spam_report()` | `POST /api/v1/spam/report` |
| `submit_job()` / `job_status()` / `job_results()` / `list_jobs()` | `POST /api/v1/jobs` / `GET /api/v1/jobs/<id>` / `GET /api/v1/jobs/<id>/results` / `GET /api/v1/jobs` |
| `analytics()` | `GET /api/v1/analytics` |
| `usage()` / `usage_all()` | `GET /api/v1/reports/usage` / `GET /api/v1/reports/usage/all` |
| `export_history()` | `GET /api/v1/reports/export` |
| `pricing()` | `GET /api/v1/pricing/all` |
| `status()` | `GET /api/v1/status` |
| `validate_key()` | `POST /api/v1/auth/validate-key` |

The single-number `lrn()` call with `include_enhanced` / `include_cnam` /
`include_trust` / `messaging_lookup` is the combined-products lookup - one
call, one number, every product you select.

**Unrecognized response fields are preserved**: every typed result carries
the untransformed response item on its `.raw` attribute, so fields added to
the API after this SDK release are never silently dropped.

## API Reference

### CNAM (Caller ID)

```python
from verirouteintel import VeriRoute

vri = VeriRoute('your_api_key')

# Basic CNAM lookup
result = vri.cnam('+15551234567')
print(result.number)  # "+15551234567"
print(result.cnam)    # "JOHN DOE"

# With spam detection
result = vri.cnam('+15551234567', include_spam=True)
print(result.spam_type)  # "NONE", "SPAM", "SCAM", or "ROBOCALL"
```

### LRN (Carrier & Line Type)

```python
# Basic LRN lookup
info = vri.lrn('+15551234567')
print(info.carrier)    # "Verizon Wireless"
print(info.line_type)  # "mobile", "landline", "voip", or "unknown"
print(info.lrn)        # Local Routing Number

# With enhanced location data
info = vri.lrn('+15551234567', include_enhanced=True)
print(info.enhanced.city)       # "New York"
print(info.enhanced.state)      # "NY"
print(info.enhanced.zip_code)   # "10001"
print(info.enhanced.timezone)   # "America/New_York"
print(info.enhanced.county)     # "New York"
print(info.enhanced.rate_center)  # "NWYRCYZN01"

# With messaging provider
info = vri.lrn('+15551234567', messaging_lookup=True)
print(info.messaging.provider)  # "Verizon Wireless"
print(info.messaging.enabled)   # True

# With CNAM (caller name)
info = vri.lrn('+15551234567', include_cnam=True)
print(info.cnam.caller_name)  # "ACME CORP"

# With trust/reputation data
info = vri.lrn('+15551234567', include_trust=True)
print(info.trust.reputation_score)  # 85 (0-100, higher = more trustworthy)
print(info.trust.trust_level)       # "high", "medium", or "low"
print(info.trust.is_spam)           # False
print(info.trust.last_updated)      # ISO 8601 timestamp

# All options - single API call with everything
info = vri.lrn('+15551234567',
    include_enhanced=True,
    messaging_lookup=True,
    include_cnam=True,
    include_trust=True
)
```

### Trust (Spam/Scam Detection)

```python
# Basic trust check (v1)
trust = vri.trust('+15551234567')

print(trust.is_spam)         # Boolean
print(trust.is_robocall)     # Boolean
print(trust.is_scam)         # Boolean
print(trust.spam_type)       # "NONE", "SPAM", "SCAM", "ROBOCALL", "TELEMARKETER"
print(trust.complaint_count) # Number of complaints
print(trust.subjects)        # List of complaint subjects
print(trust.first_reported)  # First complaint date
print(trust.last_reported)   # Most recent complaint

# Trust v2 - with reputation scoring
trust = vri.trust_v2('+15551234567')

print(trust.reputation_score)  # 0-100, higher = more trustworthy
print(trust.trust_level)       # "high" (>=70), "medium" (40-69), "low" (<40)
print(trust.last_updated)      # ISO 8601 timestamp
print(trust.is_spam)           # Boolean
print(trust.complaint_count)   # Number of complaints
```

### Spam Check (Lightweight)

```python
# Quick spam check (faster than trust)
spam = vri.spam('+15551234567')
print(spam.is_spam)     # Boolean
print(spam.spam_type)   # Spam classification
print(spam.cached)      # Whether result was cached
```

### Enhanced Spam Lookup (Multi-Source)

```python
# Composite spam verdict across the primary provider, crowdsourced
# complaint sources, and internal user reports
result = vri.spam_enhanced('+15551234567')
print(result.spam_score)       # 0.0-1.0 composite spam score
print(result.robocall_score)   # 0.0-1.0
print(result.scam_score)       # 0.0-1.0
print(result.confidence)       # 0.0-1.0 confidence in the verdict
print(result.sources)          # Sources with a finding
print(result.total_complaints) # Complaints found across sources
print(result.categories)       # e.g. ['robocall', 'telemarketer']

# Skip crowdsourced web sources (provider + internal reports only)
result = vri.spam_enhanced('+15551234567', include_web_sources=False)
```

### Report Spam

```python
# Report a spam number
report = vri.spam_report('+15551234567',
    report_type='robocall',
    details='Automated car warranty scam'
)
print(report.success)          # True
print(report.phone_number)     # "+15551234567" (E.164)
print(report.complaint_count)  # Total complaints now on record

# Report types: 'spam', 'robocall', 'scam', 'telemarketing', 'fraud', 'phishing'
```

### Messaging Provider

```python
msg = vri.messaging('+15551234567')
print(msg.messaging_provider)      # "Twilio"
print(msg.messaging_enabled)       # True
print(msg.messaging_country)       # "US"
print(msg.messaging_country_code)  # "1"
```

### Analytics & Usage

```python
# Get analytics
analytics = vri.analytics(preset='30d')
print(analytics.total_lookups)
print(analytics.carrier_type_breakdown)  # {"mobile": 500, "landline": 200, ...}
print(analytics.spam_breakdown)          # {"clean": 650, "spam": 50}

# Custom date range
analytics = vri.analytics(
    start_date='2024-01-01',
    end_date='2024-01-31'
)

# Get usage report - with period parameter
usage = vri.usage(period='week')  # 'day', 'week', or 'month'
print(usage.total_lookups)
print(usage.total_spent)
print(usage.by_product)    # {"cnam": 100, "lrn": 200, "spam": 50, ...}
print(usage.by_interface)  # {"api": 300, "web": 50, "batch": 0}
print(usage.spam_breakdown)  # {"spam": 10, "scam": 5, "robocall": 3}

# Custom date range (overrides period)
usage = vri.usage(
    start_date='2024-12-01',
    end_date='2024-12-31',
    group_by='week'  # Time series grouping
)
for entry in usage.time_series:
    print(f"{entry['date']}: {entry['count']} lookups, ${entry['spent']:.2f}")

# Aggregated usage across ALL of your API keys (same parameters)
all_usage = vri.usage_all(period='month')
print(all_usage.total_lookups)

# Export lookup history for this key as CSV
csv_text = vri.export_history(start_date='2026-08-01', limit=5000)
with open('history.csv', 'w') as f:
    f.write(csv_text)
```

### Pricing (Rate Card)

```python
# Current price per lookup for each product - estimate costs programmatically
rates = vri.pricing()
print(rates)  # {"lrn": 0.0005, "cnam": 0.006, "spam": 0.007, ...}

estimated = rates['lrn'] * len(numbers)
```

### Platform Status

```python
# Cheap connectivity check - no API key required, never billed
s = vri.status()
print(s.status)  # "operational", "degraded", or "outage"
for component in s.components:
    print(component.name, component.status, component.detail)
```

### Validate API Key

```python
if vri.validate_key():
    print("API key is valid")
else:
    print("Invalid API key")
```

## Bulk Operations

Process up to 1000 numbers in a single request:

```python
numbers = ['+15551234567', '+15559876543', '+15551112222']

# Bulk CNAM
results = vri.cnam_bulk(numbers)
for result in results.results:
    print(f"{result.number}: {result.cnam}")

print(f"Success: {results.successful}/{results.total}")

# Bulk LRN with enhanced data
results = vri.lrn_bulk(numbers, include_enhanced=True)
for result in results.results:
    print(f"{result.phone_number}: {result.carrier} ({result.line_type})")
    if result.enhanced:
        print(f"  Location: {result.enhanced.city}, {result.enhanced.state}")

# Bulk spam check
results = vri.spam_batch(numbers)
for result in results.results:
    if result.is_spam:
        print(f"{result.phone_number}: SPAM ({result.spam_type})")
```

## Async Jobs (up to 100,000 numbers)

For lists beyond the 1000-number synchronous cap, submit an async job. The
job runs in the background at the same per-lookup pricing; the estimated
cost is reserved from your balance at submission and settled to actual
usage on completion. Duplicates are removed (charged once) and invalid
numbers are skipped, reported, and never charged.

```python
# Submit a job
job = vri.submit_job(numbers,
    include_enhanced=True,
    include_cnam=True,
)
print(job.job_id)                  # UUID
print(job.status)                  # "SUBMITTED"
print(job.summary.unique)          # Unique valid numbers to process
print(job.summary.invalid)         # Invalid inputs skipped (with examples)
print(job.billing.estimated_cost)  # Amount reserved from your balance

# Poll for completion
import time
while True:
    job = vri.job_status(job.job_id)
    if job.status in ('COMPLETED', 'FAILED'):
        break
    time.sleep(5)

# Download the result CSV
if job.status == 'COMPLETED':
    csv_text = vri.job_results(job.job_id)
    with open('results.csv', 'w') as f:
        f.write(csv_text)
    print(job.billing.actual_cost)  # Settled cost

# List your 50 most recent jobs
recent = vri.list_jobs()
for job in recent.jobs:
    print(job.job_id, job.status)
```

### Completion Webhooks

Instead of polling, supply a `webhook_url` and VeriRoute Intel POSTs a
`job.completed` event when the job finishes. With a `webhook_secret`, the
request carries an HMAC-SHA256 signature in the `X-Webhook-Signature`
header (`sha256=<hex>`), computed over the raw request body:

```python
job = vri.submit_job(numbers,
    webhook_url='https://example.com/hooks/vri',
    webhook_secret='your-shared-secret',
)
```

Verify the signature in your handler with `verify_webhook_signature` -
always against the raw body, never re-serialized JSON:

```python
from verirouteintel import verify_webhook_signature

@app.route('/hooks/vri', methods=['POST'])
def vri_webhook():
    raw_body = request.get_data()  # raw bytes, NOT request.json
    signature = request.headers.get('X-Webhook-Signature', '')
    if not verify_webhook_signature(raw_body, signature, 'your-shared-secret'):
        abort(401)

    event = request.get_json()
    print(event['job']['id'], event['job']['status'])
    return '', 204
```

Webhook URLs must be publicly reachable HTTPS/HTTP addresses; delivery is
retried on failure and your endpoint should answer 2xx within 10 seconds.

## Error Handling

```python
from verirouteintel import (
    VeriRoute,
    VeriRouteError,
    AuthenticationError,
    RateLimitError,
    InsufficientBalanceError,
    InvalidPhoneError,
    InternationalNotSupportedError,
)

vri = VeriRoute('your_api_key')

try:
    result = vri.cnam('+15551234567')
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after}s")
except InsufficientBalanceError:
    print("Add credits at verirouteintel.com/dashboard")
except InvalidPhoneError as e:
    print(f"Invalid phone: {e.phone_number}")
except InternationalNotSupportedError as e:
    print(f"Only NANP numbers supported. Got country code: {e.detected_country_code}")
except VeriRouteError as e:
    print(f"API error [{e.code}]: {e}")
```

## Configuration

```python
vri = VeriRoute(
    'your_api_key',
    base_url='https://api-service.verirouteintel.io',  # Default
    timeout=30.0,   # Request timeout in seconds
    retries=3,      # Retry attempts for failed requests
)
```

## Context Manager

The client supports context managers for automatic cleanup:

```python
with VeriRoute('your_api_key') as vri:
    result = vri.cnam('+15551234567')
    print(result.cnam)
# Client automatically closed
```

## Type Hints

Full type annotations for all methods and return types:

```python
from verirouteintel import VeriRoute, CnamResult, LrnResult

vri = VeriRoute('your_api_key')

# IDE knows result is CnamResult
result: CnamResult = vri.cnam('+15551234567')

# IDE knows info is LrnResult with optional enhanced/messaging
info: LrnResult = vri.lrn('+15551234567', include_enhanced=True)
if info.enhanced:
    city: str = info.enhanced.city
```

## Shorthand Import

```python
from verirouteintel import VRI

# Same as VeriRoute
vri = VRI('your_api_key')
```

## Requirements

- Python 3.8+
- httpx

## Links

- [API Documentation](https://verirouteintel.com/api-docs)
- [Dashboard](https://verirouteintel.com/dashboard)
- [Support](mailto:support@verirouteintel.com)

## Changelog

### 1.3.0

- Async Jobs API: `submit_job()` (up to 100,000 numbers), `job_status()`,
  `job_results()` (result CSV download), `list_jobs()`
- `verify_webhook_signature()` helper for HMAC-SHA256 verification of
  job-completion webhooks
- New endpoints: `spam_enhanced()` (multi-source composite scoring),
  `usage_all()` (all API keys), `export_history()` (CSV export),
  `pricing()` (rate card), `status()` (platform status)
- Bulk results fixed: bulk LRN rows now populate `lrn`, `carrier`,
  `enhanced`, and `messaging` (the rows use `lrn_value` /
  `voice_provider` / `enhanced_lrn_data` / flat `messaging_*` field names);
  bulk CNAM rows now populate `cnam` (rows use `cnam_record`); batch spam
  rows now populate `source` and `cached` (rows use `spam_source` /
  `spam_cached`)
- `spam_report()` now returns the real response fields (`phone_number`,
  `report_type`, `reported_at`, `complaint_count`); the legacy `report_id` /
  `carrier_id` / `carrier_name` attributes remain but always read 0/None
- `usage()` now exposes the `api_key` the report covers
- Every typed result preserves the untransformed response on `.raw`, so
  future API fields are never silently dropped
- Bulk fixes: CNAM parsed as a plain string, per-number failures surfaced
  in `errors`, canonical bulk parameter names

## License

MIT
