Routing Rules
Overview
A routing rule is a directive that tells the platform which integration to use when a specific capability is invoked. Without routing rules, the platform cannot determine which of your configured providers should handle a payment initiation or message send - routing rules are the bridge between a requested operation and a provider integration.
Routing rules become essential as soon as you operate multiple integrations for the same capability. A business with Stripe and Cashfree both active for payments might route domestic transactions through Cashfree and international transactions through Stripe. A business with Twilio and Plivo for SMS might route high-volume batches differently from one-off transactional messages. Rules let you encode that logic in configuration rather than application code, and the POST /evaluate endpoint lets you dry-run any context before putting rules into production.
How Routing Works
When an operation is triggered, the platform evaluates routing rules for the relevant capability using the following algorithm:
- Load all rules for the capability that have an
activeintegration reference, sorted bypriorityascending (lower numbers are evaluated first). - For each rule, evaluate all conditions in the
conditionsarray against the operation context. All conditions in a rule must be satisfied for the rule to match. - Select the first rule whose conditions all match. If the matched integration is unavailable, use the rule's
fallback_integration_idif one is set. - If no rule with conditions matches but a rule with
is_default: trueexists, use the default rule. - If nothing matches, the operation fails with a
NoActiveProviderException.
Priority Order
Priority values are arbitrary integers - only their relative order matters. Leaving gaps between values (10, 20, 30) makes inserting new rules easier later. Use POST /api/v1/routing-rules/reorder to bulk-update priorities without deleting and recreating rules.
Condition Types
Each condition in a routing rule targets one attribute of the operation context. Multiple conditions on the same rule are evaluated with AND logic - all must match for the rule to fire.
| Type | Operators | Description | Example |
|---|---|---|---|
region | equals, not_equals, in | Geographic region code of the operation | Route India traffic to Cashfree |
currency | equals, not_equals | ISO 4217 currency code | Route USD payments to Stripe |
amount_threshold | gt, gte, lt, lte | Payment amount in the operation's currency | Apply premium routing above ₹100,000 |
recipient_count | gt, gte, lt, lte | Number of message recipients in a batch | Use bulk SMS provider for large sends |
message_type | equals, not_equals, in | Message channel type | Route WhatsApp messages to Twilio only |
Supported Operators:
| Operator | Description |
|---|---|
equals | Exact match |
not_equals | Excludes a specific value |
in | Matches any value in a provided array |
gt | Greater than |
gte | Greater than or equal to |
lt | Less than |
lte | Less than or equal to |
API Endpoints
List Routing Rules
Returns all routing rules for your tenant. Filter by capability to retrieve rules relevant to a specific operation type.
Endpoint: GET /api/v1/routing-rules
Authentication: Required (Bearer token)
Query Parameters:
capability(string) - Filter by capability name:initiate_payment,process_refund,verify_payment,send_sms,send_whatsappper_page(integer) - Results per page (default: 25, max: 100)page(integer) - Page number
Example Request:
GET /api/v1/routing-rules?capability=send_smsResponse (200 OK):
{
"success": true,
"data": [
{
"id": "01hwrule1abc456def789ghi00",
"capability": "send_sms",
"integration_id": "01hwxyz456abc789def012ghi3",
"integration": {
"id": "01hwxyz456abc789def012ghi3",
"provider": "twilio",
"display_name": "Twilio",
"status": "active"
},
"conditions": [
{ "type": "region", "operator": "equals", "value": "IN" }
],
"fallback_integration_id": "01hwxyz789abc012def345ghi6",
"priority": 10,
"is_default": false,
"created_at": "2026-01-15T09:00:00Z",
"updated_at": "2026-01-15T09:00:00Z"
},
{
"id": "01hwrule2abc456def789ghi00",
"capability": "send_sms",
"integration_id": "01hwxyz789abc012def345ghi6",
"integration": {
"id": "01hwxyz789abc012def345ghi6",
"provider": "plivo",
"display_name": "Plivo",
"status": "active"
},
"conditions": [],
"fallback_integration_id": null,
"priority": 100,
"is_default": true,
"created_at": "2026-01-15T09:10:00Z",
"updated_at": "2026-01-15T09:10:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 2,
"last_page": 1
},
"links": {
"first": "/api/v1/routing-rules?page=1",
"last": "/api/v1/routing-rules?page=1",
"prev": null,
"next": null
}
}Create Routing Rule
Creates a new routing rule. Conditions are optional - a rule with no conditions acts as an unconditional match and is typically used as the default fallback rule for a capability.
Endpoint: POST /api/v1/routing-rules
Authentication: Required (Bearer token)
Request Body:
{
"capability": "initiate_payment",
"integration_id": "01hwxyz123abc456def789ghi0",
"conditions": [
{ "type": "region", "operator": "equals", "value": "IN" },
{ "type": "currency", "operator": "equals", "value": "INR" }
],
"fallback_integration_id": "01hwxyz456abc789def012ghi3",
"priority": 10,
"is_default": false
}Required Fields:
capability- Capability name this rule applies tointegration_id- ULID of the target integration (must beactive)priority- Integer priority; lower values are evaluated firstis_default- Iftrue, this rule is used when no other conditions match
Optional Fields:
conditions- Array of condition objects; each requirestype,operator, andvaluefallback_integration_id- Integration to use if the primary is unavailable
Response (201 Created):
{
"success": true,
"message": "Routing rule created successfully",
"data": {
"id": "01hwrule3abc456def789ghi00",
"capability": "initiate_payment",
"integration_id": "01hwxyz123abc456def789ghi0",
"conditions": [
{ "type": "region", "operator": "equals", "value": "IN" },
{ "type": "currency", "operator": "equals", "value": "INR" }
],
"fallback_integration_id": "01hwxyz456abc789def012ghi3",
"priority": 10,
"is_default": false,
"created_at": "2026-01-20T10:00:00Z",
"updated_at": "2026-01-20T10:00:00Z"
},
"meta": {}
}Update Routing Rule
Updates an existing routing rule. You can change the target integration, conditions, fallback, priority, or default flag independently.
Endpoint: PATCH /api/v1/routing-rules/{id}
Authentication: Required (Bearer token)
Request Body (partial update allowed):
{
"conditions": [
{ "type": "region", "operator": "in", "value": ["IN", "LK", "NP"] },
{ "type": "currency", "operator": "equals", "value": "INR" }
],
"priority": 5
}Response (200 OK):
{
"success": true,
"message": "Routing rule updated successfully",
"data": {
"id": "01hwrule3abc456def789ghi00",
"capability": "initiate_payment",
"conditions": [
{ "type": "region", "operator": "in", "value": ["IN", "LK", "NP"] },
{ "type": "currency", "operator": "equals", "value": "INR" }
],
"priority": 5,
"updated_at": "2026-01-25T14:30:00Z"
},
"meta": {}
}Delete Routing Rule
Permanently deletes a routing rule. If the deleted rule was the only match for certain operation contexts, those contexts will fall through to the default rule or raise a NoActiveProviderException.
Endpoint: DELETE /api/v1/routing-rules/{id}
Authentication: Required (Bearer token)
Response (200 OK):
{
"success": true,
"message": "Routing rule deleted successfully"
}Reorder Rules
Updates the priority of multiple rules in a single request. Use this to restructure evaluation order without deleting and recreating rules.
Endpoint: POST /api/v1/routing-rules/reorder
Authentication: Required (Bearer token)
Request Body:
{
"rules": [
{ "id": "01hwrule1abc456def789ghi00", "priority": 10 },
{ "id": "01hwrule3abc456def789ghi00", "priority": 20 },
{ "id": "01hwrule2abc456def789ghi00", "priority": 100 }
]
}Response (200 OK):
{
"success": true,
"message": "Routing rules reordered successfully",
"data": {
"updated": 3
},
"meta": {}
}Evaluate Routing Context
Performs a dry-run evaluation against your current routing rules without triggering any actual operation. Use this to verify rule logic before deploying changes to production.
Endpoint: POST /api/v1/routing-rules/evaluate
Authentication: Required (Bearer token)
Request Body:
{
"capability": "initiate_payment",
"region": "IN",
"currency": "INR",
"amount": 250000,
"recipient_count": null,
"message_type": null
}Request Fields:
capability(required) - Capability to evaluateregion(optional) - Region code forregionconditionscurrency(optional) - Currency code forcurrencyconditionsamount(optional) - Numeric amount foramount_thresholdconditionsrecipient_count(optional) - Integer forrecipient_countconditionsmessage_type(optional) - String formessage_typeconditions
Response (200 OK):
{
"success": true,
"message": "Routing rule evaluated successfully",
"data": {
"matched_rule": {
"id": "01hwrule3abc456def789ghi00",
"capability": "initiate_payment",
"priority": 5,
"is_default": false,
"conditions": [
{ "type": "region", "operator": "in", "value": ["IN", "LK", "NP"] },
{ "type": "currency", "operator": "equals", "value": "INR" }
]
},
"selected_integration": {
"id": "01hwxyz123abc456def789ghi0",
"provider": "cashfree",
"display_name": "Cashfree",
"status": "active"
},
"fallback_integration": {
"id": "01hwxyz456abc789def012ghi3",
"provider": "stripe",
"display_name": "Stripe",
"status": "active"
}
},
"meta": {}
}Response when no rule matches:
{
"success": false,
"message": "No matching routing rule found for the given context.",
"data": null,
"meta": {}
}Business Scenarios
Scenario 1: Regional SMS Routing
Context: You have Twilio for South Asian traffic and Plivo as the global default. Regional pricing and deliverability differ significantly.
Workflow:
- Create a Twilio rule for
send_smswith aregioncondition matching South Asian country codes - Set Plivo as the default catch-all rule with a higher priority number
- Use
POST /evaluateto confirm the logic before going live
API Calls:
# 1. Create regional rule for South Asia
POST /api/v1/routing-rules
{
"capability": "send_sms",
"integration_id": "01hwxyz456abc789def012ghi3",
"conditions": [
{ "type": "region", "operator": "in", "value": ["IN", "LK", "NP", "BD", "PK"] }
],
"fallback_integration_id": "01hwxyz789abc012def345ghi6",
"priority": 10,
"is_default": false
}
# 2. Create global default rule
POST /api/v1/routing-rules
{
"capability": "send_sms",
"integration_id": "01hwxyz789abc012def345ghi6",
"conditions": [],
"priority": 100,
"is_default": true
}
# 3. Verify with evaluate
POST /api/v1/routing-rules/evaluate
{
"capability": "send_sms",
"region": "IN"
}Scenario 2: High-Value Payment Routing
Context: Transactions above ₹500,000 should route through Stripe for its advanced fraud tooling; lower-value domestic payments go to Cashfree.
Workflow:
- Create a Stripe rule for
initiate_paymentwith anamount_thresholdcondition - Create a Cashfree rule with a lower priority number as the domestic default
- Verify with two evaluate calls - one above the threshold, one below
API Calls:
# 1. High-value rule → Stripe
POST /api/v1/routing-rules
{
"capability": "initiate_payment",
"integration_id": "01hwxyz123abc456def789ghi0",
"conditions": [
{ "type": "currency", "operator": "equals", "value": "INR" },
{ "type": "amount_threshold", "operator": "gte", "value": 500000 }
],
"priority": 10,
"is_default": false
}
# 2. Default domestic rule → Cashfree
POST /api/v1/routing-rules
{
"capability": "initiate_payment",
"integration_id": "01hwcashfree23abc456def78",
"conditions": [],
"priority": 100,
"is_default": true
}
# 3. Verify above threshold
POST /api/v1/routing-rules/evaluate
{
"capability": "initiate_payment",
"currency": "INR",
"amount": 750000
}
# 4. Verify below threshold
POST /api/v1/routing-rules/evaluate
{
"capability": "initiate_payment",
"currency": "INR",
"amount": 25000
}Scenario 3: Configuring a Fallback
Context: Your primary SMS provider occasionally has regional outages. You want automatic failover to a secondary provider without manual intervention.
Workflow:
- Identify the secondary provider's integration ID
- Update your primary routing rule to include
fallback_integration_id - Verify via evaluate that the fallback is shown in the response
API Calls:
# Update existing rule to add fallback
PATCH /api/v1/routing-rules/01hwrule2abc456def789ghi00
{
"fallback_integration_id": "01hwxyz789abc012def345ghi6"
}
# Verify fallback appears in evaluation
POST /api/v1/routing-rules/evaluate
{
"capability": "send_sms",
"region": "US"
}Best Practices
1. Always Define One Default Rule Per Capability
Every capability that your application uses should have exactly one rule with is_default: true. This rule acts as the catch-all and ensures operations never fail simply because no condition-based rule matched. Without a default, any context that does not match a condition rule raises an exception.
2. Use Sparse Priority Values
Assign priorities in increments of 10 or 20 rather than 1, 2, 3. Sparse priority values make it straightforward to insert new rules between existing ones without a full reorder. Use POST /reorder when you need to restructure a large set of rules.
3. Test with Evaluate Before Deploying
Run POST /api/v1/routing-rules/evaluate with representative contexts after any change to your routing rules. Cover edge cases - the threshold boundary, regions on either side of a condition, and empty/null context values. This prevents misrouted operations that are difficult to diagnose after the fact.
4. Document Your Routing Logic
Use the metadata field on integrations to annotate which routing decisions they serve. Keep a record of why each rule exists and which business requirement it satisfies. Routing rules tend to accumulate over time; undocumented rules become obstacles during incident response and provider migrations.
Troubleshooting
No Provider Found for Operation
Symptom: Operations fail with a NoActiveProviderException or 404-style error indicating no provider is available for the capability.
Possible Causes:
- No routing rule exists for the capability
- All matching rules reference integrations that are not in
activestatus - No
is_default: truerule exists and no condition-based rule matched the context
Resolution:
- List rules for the capability:
GET /api/v1/routing-rules?capability={capability} - Check the status of each referenced integration:
GET /api/v1/integrations/{id} - Run evaluate with the failing context to confirm which rule (if any) matches:
POST /api/v1/routing-rules/evaluate - Create a default rule or activate the referenced integration as appropriate
Wrong Integration Selected
Symptom: Operations are routed to the correct provider in testing but to the wrong one in production, or vice versa.
Possible Causes:
- Condition values are incorrect (region codes, currency casing, threshold units)
- Priority order is not as expected
- A higher-priority rule matches before the intended rule
Resolution:
# 1. Inspect all rules for the capability in priority order
GET /api/v1/routing-rules?capability=initiate_payment
# 2. Run evaluate with the exact production context
POST /api/v1/routing-rules/evaluate
{
"capability": "initiate_payment",
"region": "US",
"currency": "USD",
"amount": 1500
}
# 3. If the matched rule is wrong, adjust conditions or reorder
PATCH /api/v1/routing-rules/01hwrule3abc456def789ghi00
{
"priority": 5
}Evaluate Returns an Error
Symptom: POST /evaluate returns success: false with "No matching routing rule found."
Possible Causes:
- No default rule configured for the capability
- The context fields provided do not satisfy any rule's conditions
- All matching integrations are inactive
Resolution:
- Confirm a default rule exists:
GET /api/v1/routing-rules?capability={capability}and look foris_default: true - Check that the integration referenced by the default rule is
active - If no default exists, create one:
POST /api/v1/routing-rules
{
"capability": "send_sms",
"integration_id": "01hwxyz789abc012def345ghi6",
"conditions": [],
"priority": 100,
"is_default": true
}Related Documentation
- Managing Integrations - Configure providers and manage integration status
- Delivery Tracking - Monitor async delivery attempts after routing
- Integrations Overview - Module architecture and supported providers