API Reference¶
Base URL: http://localhost:8080
All endpoints under /v1/ require API key authentication when PULSEROUTE_API_KEYS is set. Pass the key via X-API-Key header. The /metrics endpoint is always public.
Routing¶
POST /v1/route¶
Get a routing decision for a transaction. Call this before processing the payment.
Request:
{
"country": "US",
"currency": "USD",
"payment_method": "card",
"card_type": "visa",
"transaction_type": "one_time",
"amount": 99.99,
"amount_tier": null
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
country |
string | yes | ISO 3166-1 alpha-2 | |
currency |
string | yes | ISO 4217 | |
payment_method |
string | no | card |
card, bank_transfer, wallet, bnpl |
card_type |
string | no | * |
visa, mastercard, amex, etc. |
transaction_type |
string | no | one_time |
one_time, recurring, subscription |
amount |
float | no | null | Transaction amount |
amount_tier |
string | no | auto | micro, standard, high_value, premium. Derived from amount if omitted. |
Response (200):
{
"processor_id": "stripe",
"weight": 0.95,
"fallback_processor_id": "adyen",
"decision_source": "mab",
"failure_probability": 0.02
}
| Field | Description |
|---|---|
processor_id |
Recommended processor |
weight |
Traffic allocation weight (0-1) |
fallback_processor_id |
Secondary processor if primary fails |
decision_source |
rules (Tier 1), prediction (Tier 2), or mab (Tier 3) |
failure_probability |
Tier 2 LSTM prediction (null if Tier 2 disabled) |
Errors: 404 if no matching rule exists.
POST /v1/outcome¶
Report a transaction outcome. Call this after the payment completes.
Request:
{
"rule_id": "us_cards",
"processor_id": "stripe",
"success": true,
"latency_ms": 120.0,
"error_code": null
}
| Field | Type | Required | Description |
|---|---|---|---|
rule_id |
string | yes | Routing rule that was used |
processor_id |
string | yes | Processor that handled the transaction |
success |
bool | yes | Whether the transaction succeeded |
latency_ms |
float | yes | End-to-end latency in milliseconds |
error_code |
string | no | Processor error code (if failed) |
Response (202):
Rules¶
GET /v1/rules¶
List all routing rules.
Response (200):
[
{
"rule_id": "us_cards",
"primary": {"processor_id": "stripe", "name": "Stripe"},
"secondary": {"processor_id": "adyen", "name": "Adyen"},
"primary_weight": 0.9,
"secondary_weight": 0.1
}
]
POST /v1/rules¶
Add a routing rule.
Request:
{
"rule_id": "us_cards",
"attributes": ["US", "USD", "card", "*", "*", "*"],
"primary_id": "stripe",
"primary_name": "Stripe",
"secondary_id": "adyen",
"secondary_name": "Adyen",
"primary_weight": 0.9
}
Attribute padding
Attribute lists shorter than 6 are padded with * (wildcard). So ["US", "USD", "*"] is equivalent to ["US", "USD", "*", "*", "*", "*"].
Response (201): Returns the created rule.
DELETE /v1/rules/{rule_id}¶
Remove a routing rule.
Response (200): {"deleted": "us_cards"}
Errors: 404 if rule not found.
Health & Monitoring¶
GET /v1/health¶
Overall engine health with per-rule breakdown.
Response (200):
{
"status": "ok",
"engine_running": true,
"tier": 3,
"rules": {
"us_cards": {
"rule_id": "us_cards",
"primary": {
"processor_id": "stripe",
"status": "healthy",
"success_rate": 0.98,
"error_rate": 0.02,
"avg_latency_ms": 115.0,
"p95_latency_ms": 180.0,
"p99_latency_ms": 220.0,
"sample_count": 1500,
"failure_probability": 0.03
},
"secondary": { "..." : "..." },
"decision": {
"processor_id": "stripe",
"weight": 0.95,
"decision_source": "mab"
}
}
}
}
GET /v1/health/{rule_id}¶
Health for a specific rule.
Errors: 404 if rule not found.
GET /v1/stats¶
Engine statistics.
Response (200):
{
"transactions_routed": 15000,
"transactions_reported": 14800,
"failover_events": 3,
"running": true,
"shadow_mode": false,
"tier": 3,
"trie": {"total_nodes": 15, "total_rules": 5},
"bandit": {"rule_count": 5}
}
GET /v1/failover-events¶
List failover events (audit trail).
Response (200):
[
{
"rule_id": "us_cards",
"from_processor": "stripe",
"to_processor": "adyen",
"reason": "Primary error rate 35.0% exceeds threshold 30.0%",
"trigger_value": 0.35,
"threshold": 0.30,
"timestamp": "2026-04-01T12:00:00"
}
]
GET /v1/shadow-report¶
Shadow mode comparison report showing what PulseRoute would have done differently.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
average_order_value |
float | 50.0 | Customer AOV for revenue impact estimate |
Response (200):
{
"shadow_mode_active": true,
"observation_period_seconds": 3600.0,
"total_transactions": 500,
"total_outcomes": 480,
"routing_overrides": 45,
"failover_events_detected": 2,
"transactions_on_degraded_processor": 30,
"estimated_saved_transactions": 12,
"estimated_revenue_impact": 600.0,
"rules": {
"us_cards": {
"total_outcomes": 480,
"failures": 18,
"overrides": 45,
"failures_on_degraded": 12,
"recommended_processor": "adyen"
}
},
"earliest_observation": "2026-04-01T10:00:00+00:00",
"latest_observation": "2026-04-01T11:00:00+00:00"
}
Key metrics explained:
- routing_overrides — transactions where PulseRoute would have used a different processor than the customer's system chose
- estimated_saved_transactions — failures on processors PulseRoute flagged as degraded (these would have been avoided)
- estimated_revenue_impact — estimated_saved_transactions × average_order_value
Configuration¶
GET /v1/config/tiers¶
Get current tier configuration.
POST /v1/config/tiers¶
Toggle tiers at runtime.
Request:
Onboarding¶
POST /v1/onboard¶
Bulk setup: define processor capabilities and auto-generate routing rules. This is the API behind the dashboard's onboarding wizard.
Request:
{
"processors": [
{
"processor_id": "stripe",
"name": "Stripe",
"countries": ["US", "GB"],
"currencies": ["USD", "GBP"],
"payment_methods": ["card", "wallet"],
"priority": 1
},
{
"processor_id": "adyen",
"name": "Adyen",
"countries": ["US", "GB", "DE"],
"currencies": ["USD", "GBP", "EUR"],
"payment_methods": ["card"],
"priority": 2
}
],
"primary_weight": 0.9
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
processors |
array | Yes | — | At least 2 processors |
processors[].processor_id |
string | Yes | — | Unique processor ID |
processors[].name |
string | Yes | — | Display name |
processors[].countries |
array | Yes | — | ISO 3166-1 country codes |
processors[].currencies |
array | Yes | — | ISO 4217 currency codes |
processors[].payment_methods |
array | No | ["card"] |
Payment method types |
processors[].priority |
int | No | 1 |
Lower = higher priority |
primary_weight |
float | No | 0.9 |
Traffic weight for primary (0.5-1.0) |
Response (201):
{
"rules_created": 4,
"rules": [
{
"rule_id": "gb_gbp_card",
"attributes": ["GB", "GBP", "card"],
"primary_id": "stripe",
"primary_name": "Stripe",
"secondary_id": "adyen",
"secondary_name": "Adyen",
"primary_weight": 0.9
}
],
"processors": ["stripe", "adyen"]
}
For each (country, currency, payment_method) combination where processors overlap, a routing rule is created with the highest-priority processor as primary and the next as secondary. Combinations served by only one processor get a self-fallback rule.
Webhooks¶
POST /v1/webhooks¶
Register a webhook endpoint.
Request:
{
"url": "https://your-service.com/webhook",
"event_types": ["failover.triggered", "failover.recovered"],
"secret": "your-hmac-secret"
}
Event types:
| Event | Fired when |
|---|---|
failover.triggered |
Traffic fails over from primary to secondary |
failover.recovered |
Traffic returns to primary after recovery |
processor.degraded |
Processor status changes to degraded |
processor.healthy |
Processor status returns to healthy |
Response (201):
{
"id": "ab02e80d58cb",
"url": "https://your-service.com/webhook",
"event_types": ["failover.triggered", "failover.recovered"],
"active": true,
"created_at": "2026-04-01T12:00:00"
}
GET /v1/webhooks¶
List all webhooks.
GET /v1/webhooks/{webhook_id}¶
Get a specific webhook.
DELETE /v1/webhooks/{webhook_id}¶
Remove a webhook.
Webhook Delivery¶
Webhook payloads are delivered via HTTP POST with:
Content-Type: application/jsonX-PulseRoute-Signature: sha256=<hmac>(if secret is set)- Retry: 3 attempts with exponential backoff
- Timeout: 10 seconds per attempt
Payload:
{
"event_id": "evt_abc123",
"event_type": "failover.triggered",
"timestamp": "2026-04-01T12:00:00",
"data": {
"rule_id": "us_cards",
"from_processor": "stripe",
"to_processor": "adyen"
}
}
Verifying signatures (Python):
import hashlib, hmac
def verify_signature(body: bytes, secret: str, signature: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Prometheus Metrics¶
Endpoint: GET /metrics/ (no auth required)
| Metric | Type | Description |
|---|---|---|
pulseroute_requests_total |
Counter | HTTP requests by method, path, status |
pulseroute_request_duration_seconds |
Histogram | Request latency by method, path |
pulseroute_transactions_routed_total |
Counter | Total routing decisions |
pulseroute_transactions_reported_total |
Counter | Total outcomes reported |
pulseroute_failover_events_total |
Counter | Failover events by rule |
pulseroute_processor_health_status |
Gauge | 1=healthy, 0.5=degraded, 0=failed_over |
pulseroute_processor_success_rate |
Gauge | Current success rate |
pulseroute_processor_latency_p95 |
Gauge | P95 latency in ms |
pulseroute_processor_failure_probability |
Gauge | Tier 2 LSTM prediction |
pulseroute_bandit_weight |
Gauge | Tier 3 traffic allocation |
pulseroute_engine_tier |
Gauge | Current engine tier (1/2/3) |
pulseroute_redis_connected |
Gauge | Redis connectivity (0/1) |
Authentication & Multi-Tenancy¶
API keys authenticate requests and determine tenant scope. Set PULSEROUTE_API_KEYS with key:tenant_id pairs:
# Multi-tenant
PULSEROUTE_API_KEYS=key1:acme,key2:beta,key3:acme
# Legacy (all keys map to "default" tenant)
PULSEROUTE_API_KEYS=key1,key2,key3
Pass the key in requests:
Each tenant has fully isolated routing rules, metrics, health state, and webhooks. When PULSEROUTE_API_KEYS is not set, authentication is disabled (dev mode, all requests use the "default" tenant).
Tenants¶
GET /v1/tenants¶
List all active tenants.
Response (200):
{
"tenants": [
{
"tenant_id": "acme",
"active": true,
"rules_count": 5,
"engine_running": true
}
],
"total": 1
}
GET /v1/tenants/{tenant_id}¶
Get details for a specific tenant.
Response (200):
{
"tenant_id": "acme",
"active": true,
"rules_count": 5,
"engine_running": true,
"tier": 3,
"transactions_routed": 15000,
"transactions_reported": 14800,
"failover_events": 3
}
Errors: 404 if tenant not found.
OpenAPI / Swagger¶
FastAPI auto-generates interactive API docs:
- Swagger UI:
http://localhost:8080/docs - ReDoc:
http://localhost:8080/redoc