Sayfin API Reference
Version: v1.0.0 · Base URL: https://api.sayfin.ai · Protocol: HTTP/HTTPS · Content-Type: application/json
Sayfin sits between the PSP and the acquirer, not after the merchant. The PSP calls Sayfin to get routing recommendations before selecting an acquirer.
Merchant → PSP → [Sayfin] → Acquirer (Stripe/Adyen/etc.)
Average latency 7–8ms. p95 <15ms · p99 <25ms · Cold start ~58ms (first request after container start).
Authentication
All API requests are secured with HMAC-SHA256 signature-based authentication. Use our official SDKs to handle signing automatically, or implement manually for other languages.
Contact [email protected] or [email protected] to receive your api_key_id and api_secret via secure channel.
Required Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | HMAC-SHA256 Signature={sig}, KeyId={api_key_id} |
| X-Request-Timestamp | Yes | Unix timestamp in seconds. Rejected if >5 minutes old. |
| Content-Type | Yes | Must be application/json |
Step 1 — Create the Signature Message
Concatenate request details in this exact format:
{METHOD}\n{PATH}\n{BODY}\n{TIMESTAMP}
Example:
POST
/v1/recommendations
{"basic_info":{"transaction_id":"txn_001",...}}
1706356800
Step 2 — Calculate HMAC-SHA256
import hmac, hashlib, time, json
timestamp = str(int(time.time()))
signature_message = f"POST\n/v1/recommendations\n{body}\n{timestamp}"
signature = hmac.new(
api_secret.encode('utf-8'),
signature_message.encode('utf-8'),
hashlib.sha256
).hexdigest()
import crypto from "crypto";
const timestamp = Math.floor(Date.now() / 1000).toString();
const signatureMessage = `POST\n/v1/recommendations\n${body}\n${timestamp}`;
const signature = crypto
.createHmac("sha256", API_SECRET)
.update(signatureMessage)
.digest("hex");
Step 3 — Include 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 Errors
| Status | Error | Fix |
|---|---|---|
| 401 | Invalid signature | Double-check your signature calculation |
| 401 | Unknown key | Verify your api_key_id is correct |
| 401 | Timestamp expired | Request timestamp is >5 minutes old — use a current timestamp |
| 403 | Key expired | Contact us for a new key |
| 403 | Key revoked | Contact [email protected] |
Base URL & Versioning
https://api.sayfin.ai
API versioning is handled via URL path. The current version is v1. Breaking changes are released as new versions (v2, etc.). Non-breaking changes (new optional fields) may be added to existing versions without notice.
GET /health
Check API and database connectivity. No authentication required. Rate limit: unlimited. Use for Kubernetes liveness/readiness probes and load balancer health checks.
curl https://api.sayfin.ai/health
| Status | Body | Meaning |
|---|---|---|
| 200 OK | OK | API and database healthy |
| 503 | Service Unavailable | Database unavailable |
POST /v1/recommendations
The primary endpoint. Returns the top 3 payment acquirer recommendations ranked by approval probability. Authentication: HMAC-SHA256. Rate limit: 100,000 req/min.
More fields = better ML accuracy. Include as many fields as available from your payment flow. The minimum required fields are transaction_id, amount, and currency_code.
Minimum Required Payload
{
"basic_info": {
"transaction_id": "txn_12345",
"amount": 4.50,
"currency_code": "USD"
},
"actor_info": {
"merchant_id": "merchant_abc",
"mcc": 5499
},
"card_data": {
"entry_mode": "NFC",
"brand_info": { "primary_description": "VISA" }
}
}
Basic Info Fields
| Field | Type | Description |
|---|---|---|
| transaction_idrequired | string | Unique transaction identifier |
| amountrequired | float | Transaction amount (e.g. 4.50) |
| currency_coderequired | string | ISO 4217 uppercase (e.g. "USD", "EUR") |
| currency_numeric | string | ISO 4217 numeric code (e.g. "840") |
| site_id | integer | Site/location identifier |
| machine_au_time | string | Machine authorization time |
| machine_au_time_exact | string | Exact ISO 8601 timestamp |
| timeout_ms | integer | Request timeout in milliseconds (e.g. 15000) |
| payment_method_id | integer | Payment method type identifier |
| billing_provider_id | integer | Billing provider identifier |
| rrn | string | Retrieval Reference Number |
Actor Info (Merchant) — Recommended
| Field | Type | Description |
|---|---|---|
| merchant_idrequired | string | Merchant identifier |
| merchant_name | string | Merchant business name |
| mcc | integer | Merchant Category Code (e.g. 5499, 5812) |
| merchant_country | object | Country object with name, numeric_code, alpha2_code, alpha3_code |
| geo_location | object | Geographic location with state, city, country_code, zip_code, address, latitude, longitude |
| phone_number | string | Merchant phone number |
Card Data — Recommended
| Field | Type | Description |
|---|---|---|
| masked_card_number | string | Masked PAN — never send full PAN (e.g. "411111xxxxxx1234") |
| entry_mode | string | "NFC", "Chip", "Swipe", or "Manual" |
| exp_year | string | Expiration year YY (e.g. "30") |
| exp_month | string | Expiration month MM (e.g. "12") |
| brand_info | object | Card brand: primary_id, primary_description, secondary_id |
| is_debit_card | boolean | Whether card is debit vs credit |
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"
}
SDK Examples
from sayfin import SayFin
client = SayFin(api_key="your_api_key_id", api_secret="your_api_secret")
rec = client.get_recommendations({...})
print(rec.recommendations[0].acquirer) # "Adyen"
print(rec.recommendations[0].probability) # 0.92
import { SayFin } from "@sayfin/sdk";
const client = new SayFin({ apiKey: "your_api_key_id", apiSecret: "your_api_secret" });
const rec = await client.getRecommendations({...});
console.log(rec.recommendations[0].acquirer); // "Adyen"
POST /v1/feedback
Submit transaction outcome feedback for model retraining and accuracy tracking. Authentication: HMAC-SHA256. Rate limit: 100,000 req/min per merchant.
Request Body
| Field | Type | Description |
|---|---|---|
| transaction_idrequired | string | Must match a previous /v1/recommendations request |
| acquirer_usedrequired | string | Which acquirer was actually used (e.g. "Adyen") |
| outcomerequired | string | "approved", "declined", or "error" |
| processing_time_ms | integer | Processing time in milliseconds |
| recommended_rank_used | integer | Which recommendation rank was used: 1, 2, or 3 |
{
"transaction_id": "txn_12345",
"acquirer_used": "Adyen",
"outcome": "approved",
"processing_time_ms": 250,
"recommended_rank_used": 1
}
Response
{
"feedback_id": 42,
"transaction_id": "txn_12345",
"status": "accepted"
}
Use cases: track recommendation accuracy · measure which rank is typically used · collect data for model retraining · monitor approval rates per acquirer. Returns 409 Conflict if feedback was already submitted for a transaction.
POST /v1/compare
Compare PSP's acquirer choice against the model's top recommendation. Validate if manual routing aligns with the model, measure adoption rate, and A/B test routing strategies. Rate limit: 1,000 req/min.
Request Body
| Field | Type | Description |
|---|---|---|
| transaction_idrequired | string | Must match a previous /v1/recommendations request |
| acquirer_usedrequired | string | Which acquirer was actually used |
{
"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 }
]
}
When the acquirer used is not in the top 3, match_found is false and predicted_rank / predicted_probability are null. Returns 404 if no recommendations exist for the given transaction ID.
Error Handling
All errors follow a consistent JSON structure. HTTP status codes map to error categories.
{
"error": "Invalid HMAC signature",
"details": "Signature mismatch for key sk_live_..."
}
| Status | Description |
|---|---|
| 400 | Request body is malformed JSON or missing required fields |
| 401 | HMAC signature verification failed or timestamp outside 5-minute window |
| 403 | API key does not have access to this endpoint |
| 404 | Referenced resource (e.g. transaction_id) does not exist |
| 429 | Rate limit exceeded - see Retry-After header |
| 500 | Sayfin internal error - implement exponential backoff |
On 500 errors, implement exponential backoff with a maximum of 3 retries. For 429 errors, respect the Retry-After response header exactly.
Rate Limits
The GET /health endpoint is not rate limited. Rate limit headers are included in every response:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Your configured requests/min limit |
| X-RateLimit-Remaining | Requests remaining in the current minute |
| X-RateLimit-Reset | Unix timestamp when the window resets |
| Retry-After | Seconds to wait before retrying (only on 429) |
SDKs
Official SDKs are available for Python and TypeScript. Both are async-first, fully typed, and handle HMAC signing automatically.
# Install
pip install sayfin
# Usage
from sayfin import SayFin
client = SayFin(api_key="sk_live_...", api_secret="...")
result = await client.recommendations(
transaction_id="txn_8f2k9d",
amount=284700,
currency="GBP",
merchant_id="mer_xyz",
)
print(result.recommendations[0].acquirer_id) # "acquirer_b"
// Install
npm install @sayfin/sdk
// Usage
import { SayFin } from "@sayfin/sdk";
const client = new SayFin({ api_key: "sk_live_...", api_secret: "..." });
const result = await client.recommendations({
transaction_id: "txn_8f2k9d",
amount: 284700,
currency: "GBP",
merchant_id: "mer_xyz",
});
console.log(result.recommendations[0].acquirer_id); // "acquirer_b"