Route Matching Overview
ApiCharge uses YARP's powerful route matching system to determine which incoming requests should be forwarded to which backend services. Routes can match based on multiple criteria including paths, hosts, HTTP methods, headers, and query parameters.
Match Criteria
A route's Match
section defines the criteria that incoming requests must satisfy to be handled by that route.
You can combine multiple match criteria for precise routing control.
{
"ApiChargeProxy": {
"Routes": {
"example-route": {
"ClusterId": "backend-cluster",
"Match": {
"Path": "/api/users/{id}",
"Hosts": ["api.example.com"],
"Methods": ["GET", "POST"],
"Headers": [
{
"Name": "Content-Type",
"Values": ["application/json"],
"Mode": "ExactHeader"
}
],
"QueryParameters": [
{
"Name": "version",
"Values": ["v1", "v2"],
"Mode": "Exact"
}
]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
}
}
}
}
Path Matching
Path matching is the most common way to route requests. ApiCharge supports ASP.NET Core route templates with rich pattern matching capabilities.
Basic Path Patterns
Exact Match
"Match": {
"Path": "/api/users"
}
Matches exactly /api/users
with no additional path segments.
Single Parameter
"Match": {
"Path": "/api/users/{id}"
}
Matches /api/users/123
, /api/users/abc
, etc. The {id}
parameter captures the value.
Multiple Parameters
"Match": {
"Path": "/api/users/{userId}/posts/{postId}"
}
Matches /api/users/123/posts/456
with both parameters captured.
Catch-All Pattern
"Match": {
"Path": "/api/{**catch-all}"
}
Matches any path starting with /api/
and captures the remainder in the catch-all
parameter.
Optional Parameters
"Match": {
"Path": "/api/users/{id?}"
}
Matches both /api/users
and /api/users/123
.
Parameter Constraints
You can apply constraints to route parameters to ensure they match specific patterns:
"Match": {
"Path": "/api/users/{id:int}/posts/{slug:alpha}"
}
Common constraints include:
:int
- Must be an integer:alpha
- Must contain only letters:guid
- Must be a valid GUID:datetime
- Must be a valid DateTime:length(6)
- Must be exactly 6 characters:min(18)
- Must be at least 18 (for integers):range(1,100)
- Must be between 1 and 100
Advanced Path Examples
API Versioning
{
"ApiChargeProxy": {
"Routes": {
"v1-users": {
"ClusterId": "users-v1-cluster",
"Match": {
"Path": "/api/v1/users/{**remainder}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 50000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
},
"v2-users": {
"ClusterId": "users-v2-cluster",
"Match": {
"Path": "/api/v2/users/{**remainder}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 200
}
]
}
}
}
}
}
Microservice Routing
{
"ApiChargeProxy": {
"Routes": {
"users-service": {
"ClusterId": "users-cluster",
"Match": {
"Path": "/services/users/{**catch-all}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 75000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 150
}
]
}
},
"orders-service": {
"ClusterId": "orders-cluster",
"Match": {
"Path": "/services/orders/{**catch-all}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
}
}
}
}
HTTP Method Matching
Method matching allows you to route requests based on the HTTP verb (GET, POST, PUT, DELETE, etc.).
Single Method
"Match": {
"Path": "/api/users/{id}",
"Methods": ["GET"]
}
Multiple Methods
"Match": {
"Path": "/api/users/{id}",
"Methods": ["GET", "PUT", "DELETE"]
}
REST API Example
{
"ApiChargeProxy": {
"Routes": {
"users-read": {
"ClusterId": "users-read-cluster",
"Match": {
"Path": "/api/users/{**remainder}",
"Methods": ["GET", "HEAD", "OPTIONS"]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 50000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 200
}
]
}
},
"users-write": {
"ClusterId": "users-write-cluster",
"Match": {
"Path": "/api/users/{**remainder}",
"Methods": ["POST", "PUT", "PATCH", "DELETE"]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
}
}
}
}
Header Matching
Header matching enables routing based on HTTP headers, useful for API versioning, content negotiation, or custom routing logic.
Header Match Modes
Mode | Description | Example |
---|---|---|
ExactHeader |
Header value must match exactly | Content-Type: application/json |
HeaderPrefix |
Header value must start with the specified prefix | Accept: application/ |
Contains |
Header value must contain the specified text | User-Agent contains mobile |
Exists |
Header must be present (any value) | Authorization header exists |
NotContains |
Header value must not contain the specified text | User-Agent doesn't contain bot |
NotExists |
Header must not be present | No X-Internal header |
API Versioning via Headers
{
"ApiChargeProxy": {
"Routes": {
"api-v1": {
"ClusterId": "api-v1-cluster",
"Match": {
"Path": "/api/{**remainder}",
"Headers": [
{
"Name": "API-Version",
"Values": ["1.0", "1.1"],
"Mode": "ExactHeader"
}
]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 75000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 150
}
]
}
},
"api-v2": {
"ClusterId": "api-v2-cluster",
"Match": {
"Path": "/api/{**remainder}",
"Headers": [
{
"Name": "API-Version",
"Values": ["2.0"],
"Mode": "ExactHeader"
}
]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 200
}
]
}
}
}
}
}
Query Parameter Matching
Query parameter matching allows routing based on URL query strings, useful for feature flags, A/B testing, or API versioning.
Query Parameter Match Modes
Mode | Description | Example |
---|---|---|
Exact |
Parameter value must match exactly | ?version=1.0 |
Prefix |
Parameter value must start with prefix | ?filter=user_ |
Contains |
Parameter value must contain text | ?search=api |
Exists |
Parameter must be present (any value) | ?debug or ?debug=true |
Feature Flag Routing
{
"ApiChargeProxy": {
"Routes": {
"beta-features": {
"ClusterId": "beta-cluster",
"Match": {
"Path": "/api/{**remainder}",
"QueryParameters": [
{
"Name": "beta",
"Mode": "Exists"
}
]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 150000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 50
}
]
}
},
"stable-features": {
"ClusterId": "stable-cluster",
"Match": {
"Path": "/api/{**remainder}"
},
"Order": 1,
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
}
}
}
}
Route Precedence and Ordering
When multiple routes could match the same request, ApiCharge uses a precedence system to determine which route is selected.
Default Precedence Order
Routes are evaluated in this order of precedence:
- Path - More specific paths have higher precedence
- HTTP Method - Routes with method constraints
- Host - Routes with host constraints
- Headers - Routes with header constraints
- Query Parameters - Routes with query parameter constraints
Explicit Route Ordering
You can override the default precedence by setting the Order
property. Lower numbers have higher precedence.
{
"ApiChargeProxy": {
"Routes": {
"high-priority-route": {
"ClusterId": "priority-cluster",
"Order": 0,
"Match": {
"Path": "/api/{**remainder}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 200000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 50
}
]
}
},
"specific-route": {
"ClusterId": "specific-cluster",
"Order": 1,
"Match": {
"Path": "/api/users/{id}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 100000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
},
"fallback-route": {
"ClusterId": "fallback-cluster",
"Order": 100,
"Match": {
"Path": "{**catch-all}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 50000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 25
}
]
}
}
}
}
}
Complex Matching Examples
Multi-Criteria Matching
Combine multiple match criteria for sophisticated routing:
{
"ApiChargeProxy": {
"Routes": {
"admin-api": {
"ClusterId": "admin-cluster",
"Match": {
"Path": "/api/admin/{**remainder}",
"Methods": ["GET", "POST", "PUT", "DELETE"],
"Headers": [
{
"Name": "Authorization",
"Mode": "Exists"
},
{
"Name": "X-Admin-Token",
"Mode": "Exists"
}
]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 500000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 1000
}
]
}
},
"public-api": {
"ClusterId": "public-cluster",
"Match": {
"Path": "/api/public/{**remainder}",
"Headers": [
{
"Name": "Authorization",
"Mode": "NotExists"
}
]
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 25000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 50
}
]
}
}
}
}
}
ApiCharge-Specific Considerations
Monetized Route Patterns
When designing routes for ApiCharge, consider how the routing patterns align with your monetization strategy:
{
"ApiChargeProxy": {
"Routes": {
"basic-tier": {
"ClusterId": "basic-cluster",
"Match": {
"Path": "/api/basic/{**remainder}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 50000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
]
}
},
"premium-tier": {
"ClusterId": "premium-cluster",
"Match": {
"Path": "/api/premium/{**remainder}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 150000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 500
},
{
"$type": "DataLimitDownload",
"Bytes": 52428800
}
]
}
},
"enterprise-tier": {
"ClusterId": "enterprise-cluster",
"Match": {
"Path": "/api/enterprise/{**remainder}"
},
"Quote": {
"ServerFundsRecipient": "GA3RQ7FWMT6INHS2R4KEKWENPYQOPLRNPYDAJFFRY5AUSD2GP6VG3OPY",
"PricingStrategy": {
"$type": "Basic",
"Duration": 3600,
"MicroUnitPrice": 300000
},
"RateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 1000
},
{
"$type": "DataLimitDownload",
"Bytes": 104857600
}
]
}
}
}
}
}
Quote
section within the route definition for ApiCharge to properly associate ApiCharge Subscriptions with routes. The route ID (the key in the Routes object) is automatically used as the RouteId for the quote.
Best Practices
Route Design Guidelines
- Start Specific, End General - Order routes from most specific to most general
- Use Meaningful Route IDs - Choose descriptive names that align with your API structure
- Plan for Growth - Design route patterns that can accommodate future API versions
- Consider Performance - More complex matching criteria may impact routing performance
- Test Route Precedence - Verify that routes match requests as expected
- Align Quotes with Routes - Ensure each monetized route has an appropriate Quote section
Common Pitfalls
- Catch-All Routes - Place broad catch-all routes last or use explicit ordering
- Case Sensitivity - Be aware of case sensitivity in path and header matching
- Missing Quote Sections - Routes without Quote sections cannot be monetized
- Query Parameter Order - Query parameters have lower precedence than other match criteria
- Regex Limitations - YARP doesn't support regular expressions in path patterns
Testing Routes
Use tools like curl or Postman to test your route configurations:
# Test path matching
curl -X GET https://your-apicharge-instance.com/api/users/123
# Test header-based routing
curl -X GET -H "API-Version: 2.0" https://your-apicharge-instance.com/api/users
# Test query parameter routing
curl -X GET https://your-apicharge-instance.com/api/users?version=v1
# Test method-specific routing
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"test"}' \
https://your-apicharge-instance.com/api/users
Next Steps
Now that you understand route matching, explore:
- Transforms - How to modify requests and responses as they pass through routes
- Rate Limiters - Quality of service controls for your routes
- Purchase Flow - How clients purchase access to your routes