SayFin API
Predictive payment routing, delivered as a sub-10ms API. SayFin sits between your PSP and acquirer, scoring each transaction before the authorization attempt.
| Property | Value |
|---|---|
| Protocol | HTTP / HTTPS |
| Version | v1.0.0 |
| Content-Type | application/json |
| Authentication | HMAC-SHA256 signature |
| Avg latency | 7–8 ms (embedded model) |
Authentication
All production endpoints require HMAC-SHA256 signature authentication. Our official SDKs handle this automatically.
SDK quick-start
from sayfin import SayFin
client = SayFin(
api_key="your_api_key_id",
api_secret="your_api_secret"
)
recommendations = client.get_recommendations({...})
import { SayFin } from '@sayfin/sdk';
const client = new SayFin({
apiKey: 'your_api_key_id',
apiSecret: 'your_api_secret'
});
const recommendations = await client.getRecommendations({...});
# Python
pip install sayfin
# TypeScript / Node.js
npm install @sayfin/sdk
Manual HMAC-SHA256
For languages without an official SDK, implement the signature yourself in three steps.
Step 1 — Create the signature message
Concatenate request details in this exact format:
{METHOD}\n{PATH}\n{BODY}\n{TIMESTAMP}
Step 2 — Calculate HMAC-SHA256
import hmac, hashlib
signature = hmac.new(
api_secret.encode('utf-8'),
signature_message.encode('utf-8'),
hashlib.sha256
).hexdigest()
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', apiSecret)
.update(signatureMessage)
.digest('hex');
Step 3 — Add request headers
POST /v1/recommendations HTTP/1.1
Host: api.sayfin.ai
Content-Type: application/json
Authorization: HMAC-SHA256 Signature={signature}, KeyId={api_key_id}
X-Request-Timestamp: {timestamp}
Authentication headers
| Header | Required | Description | Example |
|---|---|---|---|
| Authorization | Yes | HMAC signature + Key ID | HMAC-SHA256 Signature=abc…, KeyId=key_456 |
| X-Request-Timestamp | Yes | Unix timestamp (seconds) | 1706356800 |
| Content-Type | Yes | Must be application/json | application/json |
Getting credentials
Contact [email protected] to receive your api_key_id and api_secret via a secure channel. Store them as environment variables, never in source code.
export SAYFIN_API_KEY_ID="key_abc123xyz"
export SAYFIN_API_SECRET="secret_def456uvw"
Common authentication errors
| Status | Error | Cause | Fix |
|---|---|---|---|
| 401 | Invalid signature | Incorrect HMAC calculation | Double-check signature logic |
| 401 | Unknown key | Invalid api_key_id | Verify correct key |
| 401 | Timestamp expired | Timestamp > 5 min old | Use current timestamp |
| 403 | Key expired | API key has expired | Contact support |
| 403 | Key revoked | API key was revoked | Contact support |
Health Check
Check API and database connectivity. No authentication required.
curl https://api.sayfin.ai/health
Responses
Get Recommendations
Returns the top 3 acquirer recommendations ranked by predicted approval probability. Call this before routing each transaction.
Merchant → PSP → [SayFin] → Acquirer
Request body
basic_info (required)
| Field | Type | Required | Description |
|---|---|---|---|
| transaction_id | string | Yes | Unique transaction identifier |
| amount | float | Yes | Transaction amount (e.g. 2.20) |
| currency_code | string | Yes | ISO 4217 code: USD, EUR, GBP |
| currency_numeric | string | No | ISO 4217 numeric code: "826" (GBP) |
| timeout_ms | integer | No | Request timeout in milliseconds |
| payment_method_id | integer | No | Payment method type identifier |
| billing_provider_id | integer | No | Billing provider identifier |
| rrn | string | No | Retrieval Reference Number |
actor_info (recommended)
| Field | Type | Required | Description |
|---|---|---|---|
| merchant_id | string | Yes | Merchant identifier |
| merchant_name | string | No | Business name |
| mcc | integer | Recommended | Merchant Category Code (e.g. 5499) |
| merchant_country | object | No | Country object (see below) |
| geo_location | object | No | Geo-location object (see below) |
| phone_number | string | No | Merchant phone number |
card_data (recommended)
| Field | Type | Required | Description |
|---|---|---|---|
| masked_card_number | string | No | Masked PAN — never send full PAN |
| entry_mode | string | Recommended | NFC, Chip, Swipe, Manual |
| brand_info | object | Recommended | Card brand (see below) |
| is_debit_card | boolean | No | Whether card is debit vs credit |
| exp_year | string | No | Expiration year (YY) |
| exp_month | string | No | Expiration month (MM) |
Nested objects
{
"primary_id": "6",
"primary_description": "VISA",
"secondary_id": "0"
}
{
"name": "UNITED KINGDOM",
"numeric_code": "826",
"alpha2_code": "GB",
"alpha3_code": "GBR"
}
{
"state": "England",
"city": "Tunbridge Wells",
"country_code": "GB",
"zip_code": "TN2 4QJ",
"address": "Moat Way",
"latitude": 51.1484526,
"longitude": 0.3074703
}
Minimal payload
For quick integration, start with just the essential fields:
{
"basic_info": {
"transaction_id": "txn_12345",
"amount": 2.2,
"currency_code": "GBP"
},
"actor_info": {
"merchant_id": "merchant_abc",
"mcc": 5499
},
"card_data": {
"entry_mode": "NFC",
"brand_info": {
"primary_description": "VISA"
}
}
}
Response
{
"transaction_id": "txn_12345",
"recommendations": [
{ "acquirer": "Adyen", "probability": 0.92, "rank": 1 },
{ "acquirer": "Stripe", "probability": 0.05, "rank": 2 },
{ "acquirer": "Checkout.com", "probability": 0.02, "rank": 3 }
],
"model_version": "v1.0.0",
"timestamp": "2025-12-27T21:00:00Z"
}
| Field | Type | Description |
|---|---|---|
| transaction_id | string | Echo of request transaction ID |
| recommendations | array | Top 3 acquirers, sorted by probability |
| recommendations[].acquirer | string | Acquirer name |
| recommendations[].probability | float | Approval probability (0.0–1.0) |
| recommendations[].rank | integer | Ranking position (1–3) |
| model_version | string | ML model version used |
| timestamp | string | ISO 8601 prediction timestamp |
Performance
Examples
from sayfin import SayFin
client = SayFin(api_key="your_api_key")
recommendations = client.get_recommendations({
"basic_info": {
"transaction_id": "txn_001",
"amount": 2.20,
"currency_code": "GBP",
"timeout_ms": 15000
},
"actor_info": {
"merchant_id": "merchant_123",
"merchant_name": "My Store Ltd",
"mcc": 5499,
"merchant_country": { "alpha2_code": "GB" }
},
"card_data": {
"entry_mode": "NFC",
"brand_info": { "primary_description": "VISA" },
"is_debit_card": True
}
})
top = recommendations.recommendations[0]
print(f"Route to: {top.acquirer} ({top.probability:.1%})")
import { SayFin } from '@sayfin/sdk';
const client = new SayFin({ apiKey: 'your_api_key' });
const rec = await client.getRecommendations({
basicInfo: { transactionId: 'txn_001', amount: 2.2, currencyCode: 'GBP' },
actorInfo: { merchantId: 'merchant_123', mcc: 5499 },
cardData: { entryMode: 'NFC', brandInfo: { primaryDescription: 'VISA' } }
});
const top = rec.recommendations[0];
console.log(`Route to: ${top.acquirer} (${(top.probability * 100).toFixed(1)}%)`);
curl -X POST https://api.sayfin.ai/v1/recommendations \
-H "Content-Type: application/json" \
-H "Authorization: HMAC-SHA256 Signature={sig}, KeyId={key}" \
-H "X-Request-Timestamp: {timestamp}" \
-d '{
"basic_info": {
"transaction_id": "txn_001",
"amount": 2.20,
"currency_code": "GBP"
},
"actor_info": { "merchant_id": "merchant_123", "mcc": 5499 },
"card_data": { "entry_mode": "NFC", "brand_info": { "primary_description": "VISA" } }
}'
const crypto = require('crypto');
const axios = require('axios');
const apiKeyId = process.env.SAYFIN_API_KEY_ID;
const apiSecret = process.env.SAYFIN_API_SECRET;
const timestamp = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify({ basic_info: { transaction_id: 'txn_001', amount: 2.2, currency_code: 'GBP' } });
const sig = crypto.createHmac('sha256', apiSecret)
.update(`POST\n/v1/recommendations\n${body}\n${timestamp}`)
.digest('hex');
const res = await axios.post('https://api.sayfin.ai/v1/recommendations', body, {
headers: {
'Content-Type': 'application/json',
'Authorization': `HMAC-SHA256 Signature=${sig}, KeyId=${apiKeyId}`,
'X-Request-Timestamp': timestamp
}
});
console.log(res.data.recommendations[0].acquirer);
Submit Feedback
Submit transaction outcome feedback for model retraining and accuracy tracking. Always call this after a transaction closes.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| transaction_id | string | Yes | Must match a previous recommendation request |
| acquirer_used | string | Yes | Which acquirer was actually used |
| outcome | string | Yes | approved, declined, or error |
| processing_time_ms | integer | No | Processing time in milliseconds |
| recommended_rank_used | integer | No | Which rank was used: 1, 2, or 3 |
Response
{
"feedback_id": 42,
"transaction_id": "txn_12345",
"status": "accepted"
}
Example
curl -X POST https://api.sayfin.ai/v1/feedback \
-H "Content-Type: application/json" \
-H "Authorization: HMAC-SHA256 Signature={sig}, KeyId={key}" \
-H "X-Request-Timestamp: {timestamp}" \
-d '{
"transaction_id": "txn_12345",
"acquirer_used": "Adyen",
"outcome": "approved",
"processing_time_ms": 250,
"recommended_rank_used": 1
}'
Compare Prediction
Compare the PSP's acquirer choice against the model's top recommendation. Use this to measure model adoption rate and validate routing decisions.
Request body
{
"transaction_id": "txn_12345",
"acquirer_used": "Adyen"
}
Response — match found
{
"transaction_id": "txn_12345",
"match_found": true,
"predicted_rank": 1,
"predicted_probability": 0.92,
"all_recommendations": [
{ "acquirer": "Adyen", "probability": 0.92, "rank": 1 },
{ "acquirer": "Stripe", "probability": 0.05, "rank": 2 },
{ "acquirer": "Checkout.com", "probability": 0.02, "rank": 3 }
]
}
Response — no match
{
"transaction_id": "txn_12345",
"match_found": false,
"predicted_rank": null,
"predicted_probability": null,
"all_recommendations": [...]
}
Data Models
RecommendationResponse
{
"transaction_id": "string",
"recommendations": [{
"acquirer": "string",
"probability": "float (0.0–1.0)",
"rank": "integer (1–3)"
}],
"model_version": "string",
"timestamp": "string (ISO 8601)"
}
FeedbackRequest
{
"transaction_id": "string",
"acquirer_used": "string",
"outcome": "approved | declined | error",
"processing_time_ms": "integer (optional)",
"recommended_rank_used": "integer 1–3 (optional)"
}
CompareResponse
{
"transaction_id": "string",
"match_found": "boolean",
"predicted_rank": "integer | null",
"predicted_probability": "float | null",
"all_recommendations": "[...RecommendationItem]"
}
Error Handling
HTTP status codes
Error response format
{
"error": "Brief error description",
"details": "Detailed error message for debugging"
}
Rate Limiting
| Endpoint | Limit | Window |
|---|---|---|
| /v1/recommendations | 100,000 requests | 1 minute |
| /v1/feedback | 100,000 requests | 1 minute |
| /v1/compare | 1,000 requests | 1 minute |
| /health | Unlimited | — |
Rate limit headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 1703721600
Rate limit exceeded (429)
{
"error": "Too Many Requests",
"details": "Rate limit exceeded. Retry after 42 seconds.",
"retry_after_seconds": 42
}
Best Practices
1. Use unique transaction IDs
import uuid
transaction_id = f"txn_{uuid.uuid4()}"
2. Implement retry with exponential backoff
import time, requests
def call_with_retry(url, payload, max_retries=3):
for attempt in range(max_retries):
try:
res = requests.post(url, json=payload, timeout=5)
res.raise_for_status()
return res.json()
except requests.exceptions.RequestException:
if attempt == max_retries - 1: raise
time.sleep(2 ** attempt) # 1s, 2s, 4s
3. Always close the feedback loop
# 1. Get recommendation
rec = get_recommendations(txn)
route = rec['recommendations'][0]['acquirer']
# 2. Process transaction
result = process_transaction(route, txn)
# 3. Submit feedback
submit_feedback({
"transaction_id": txn['transaction_id'],
"acquirer_used": route,
"outcome": "approved" if result.success else "declined",
"recommended_rank_used": 1
})
4. Set request timeouts
response = requests.post(url, json=payload, timeout=5) # 5 second timeout
5. Monitor key metrics
- Request latency (p50, p95, p99)
- Error rate by status code
- Recommendation accuracy via feedback
- Rank distribution (which rank is most used)
Support
| Channel | Contact |
|---|---|
| Technical support | [email protected] |
| Sales | [email protected] |
| General support | [email protected] |
API Version: v1.0.0 · Last Updated: 2025-12-27