API Reference

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.

Base URL https://api.sayfin.ai
PropertyValue
ProtocolHTTP / HTTPS
Versionv1.0.0
Content-Typeapplication/json
AuthenticationHMAC-SHA256 signature
Avg latency7–8 ms (embedded model)

Authentication

All production endpoints require HMAC-SHA256 signature authentication. Our official SDKs handle this automatically.

Recommended: Use an SDK. The Python and TypeScript SDKs handle authentication, retries, and serialization automatically. Manual HMAC is only needed for other languages.

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

HeaderRequiredDescriptionExample
AuthorizationYesHMAC signature + Key IDHMAC-SHA256 Signature=abc…, KeyId=key_456
X-Request-TimestampYesUnix timestamp (seconds)1706356800
Content-TypeYesMust be application/jsonapplication/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

StatusErrorCauseFix
401Invalid signatureIncorrect HMAC calculationDouble-check signature logic
401Unknown keyInvalid api_key_idVerify correct key
401Timestamp expiredTimestamp > 5 min oldUse current timestamp
403Key expiredAPI key has expiredContact support
403Key revokedAPI key was revokedContact support

Health Check

Check API and database connectivity. No authentication required.

GET /health
No auth Unlimited
curl https://api.sayfin.ai/health

Responses

200 OK — API and database are healthy. Body: OK
503 Service Unavailable — Database connection failed. Body: Service Unavailable
Use cases: Kubernetes liveness/readiness probes, load balancer health checks, monitoring dashboards.

Get Recommendations

Returns the top 3 acquirer recommendations ranked by predicted approval probability. Call this before routing each transaction.

POST /v1/recommendations
HMAC-SHA256 100k req/min
Architecture note: SayFin sits between the PSP and the acquirer — not after the merchant.
Merchant → PSP → [SayFin] → Acquirer

Request body

More fields = better accuracy. While only transaction_id, amount, and currency_code are strictly required, including card, merchant, and device data significantly improves ML predictions.

basic_info (required)

FieldTypeRequiredDescription
transaction_idstringYesUnique transaction identifier
amountfloatYesTransaction amount (e.g. 2.20)
currency_codestringYesISO 4217 code: USD, EUR, GBP
currency_numericstringNoISO 4217 numeric code: "826" (GBP)
timeout_msintegerNoRequest timeout in milliseconds
payment_method_idintegerNoPayment method type identifier
billing_provider_idintegerNoBilling provider identifier
rrnstringNoRetrieval Reference Number

actor_info (recommended)

FieldTypeRequiredDescription
merchant_idstringYesMerchant identifier
merchant_namestringNoBusiness name
mccintegerRecommendedMerchant Category Code (e.g. 5499)
merchant_countryobjectNoCountry object (see below)
geo_locationobjectNoGeo-location object (see below)
phone_numberstringNoMerchant phone number

card_data (recommended)

FieldTypeRequiredDescription
masked_card_numberstringNoMasked PAN — never send full PAN
entry_modestringRecommendedNFC, Chip, Swipe, Manual
brand_infoobjectRecommendedCard brand (see below)
is_debit_cardbooleanNoWhether card is debit vs credit
exp_yearstringNoExpiration year (YY)
exp_monthstringNoExpiration 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"
}
FieldTypeDescription
transaction_idstringEcho of request transaction ID
recommendationsarrayTop 3 acquirers, sorted by probability
recommendations[].acquirerstringAcquirer name
recommendations[].probabilityfloatApproval probability (0.0–1.0)
recommendations[].rankintegerRanking position (1–3)
model_versionstringML model version used
timestampstringISO 8601 prediction timestamp

Performance

7–8ms Avg latency
<15ms p95
<25ms p99
~58ms Cold start

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.

POST /v1/feedback
HMAC-SHA256 100k req/min

Request body

FieldTypeRequiredDescription
transaction_idstringYesMust match a previous recommendation request
acquirer_usedstringYesWhich acquirer was actually used
outcomestringYesapproved, declined, or error
processing_time_msintegerNoProcessing time in milliseconds
recommended_rank_usedintegerNoWhich 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
  }'
Error 409 Conflict — feedback already submitted for this transaction_id. Each transaction can only have one feedback record.

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.

POST /v1/compare
HMAC-SHA256 1k req/min

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

200OK — Successful request
400Bad Request — Missing required field, invalid JSON
401Unauthorized — Invalid or expired API key
403Forbidden — API key lacks permissions
404Not Found — Transaction ID not found
409Conflict — Duplicate feedback submission
429Too Many Requests — Rate limit exceeded
500Internal Server Error — Model prediction failed
503Service Unavailable — Database unavailable

Error response format

{
  "error": "Brief error description",
  "details": "Detailed error message for debugging"
}

Rate Limiting

EndpointLimitWindow
/v1/recommendations100,000 requests1 minute
/v1/feedback100,000 requests1 minute
/v1/compare1,000 requests1 minute
/healthUnlimited

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

ChannelContact
Technical support[email protected]
Sales[email protected]
General support[email protected]

API Version: v1.0.0 · Last Updated: 2025-12-27