Partner Guide
card_product_ids, spend_profile, top_cards, projected_annual_reward_cents, reward_rate). This applies uniformly to both endpoints.
A complete reference for integrating /v1/bank/recommend and /v1/bank/portfolio into your banking platform.
Overview
The SuperPay Bank Partner API gives financial institutions access to the same card-reward ranking engine that powers the SuperPay consumer wallet. Two endpoints:
- /v1/bank/recommend — Returns the top cards from the 320+ card catalog ranked by reward rate for a specific transaction (amount + MCC + merchant name). Ideal for point-of-sale prompts and mobile banking "earn more" features.
- /v1/bank/portfolio — Takes a list of card IDs you hold and a spending profile. Returns per-category winners within your lineup and a ranked list of gap categories where your cardholders are leaving rewards uncaptured. Ideal for product-management analytics and card lineup planning.
Authentication
All Bank Partner API requests require a sp_bank_… key. Send it in one of two ways:
Authorization: Bearer sp_bank_xxxxxxxx_… X-SuperPay-Key: sp_bank_xxxxxxxx_…
Both headers are accepted; Authorization: Bearer is preferred for server-to-server calls. Keys are sp_bank_ + 8 hex characters + _ + 48 hex characters.
https://banking.example.com). Browser requests (carrying an Origin header) from unlisted origins receive HTTP 403 ORIGIN_NOT_ALLOWED. Server-to-server calls (no Origin header) are authorized by key alone.
POST /v1/bank/recommend
Returns the top 3 catalog cards ranked by reward rate for the requested transaction.
Request body
| Field | Type | Status | Notes |
|---|---|---|---|
| amount | number | required | USD, e.g. 87.45. Must be positive and ≤ 1,000,000. |
| mcc | string | preferred | ISO 18245 4-digit MCC. Takes precedence over category. |
| category | string | fallback | Used when MCC is absent or unmapped. See categories. |
| merchant_name | string | recommended | Display name, e.g. "Whole Foods Market". Used for name-based category resolution. |
Example — curl
curl -X POST https://superpayrewards.com/v1/bank/recommend \
-H "Authorization: Bearer sp_bank_xxxxxxxx_…" \
-H "Content-Type: application/json" \
-d '{
"amount": 87.45,
"mcc": "5411",
"merchant_name": "Whole Foods Market"
}'
Example — JavaScript (fetch)
const res = await fetch('https://superpayrewards.com/v1/bank/recommend', {
method: 'POST',
headers: {
'Authorization': 'Bearer sp_bank_xxxxxxxx_…',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 87.45,
mcc: '5411',
merchant_name: 'Whole Foods Market',
// Optional: scope to your issued cards
card_product_ids: ['amex_gold', 'chase_sapphire_preferred'],
}),
});
const { top_cards, category, portfolio_scoped } = await res.json();
console.log(`Best card: ${top_cards[0].name} — ${top_cards[0].reason}`);
Example — Python (requests)
import requests
response = requests.post(
'https://superpayrewards.com/v1/bank/recommend',
headers={
'Authorization': 'Bearer sp_bank_xxxxxxxx_…',
'Content-Type': 'application/json',
},
json={
'amount': 87.45,
'mcc': '5411',
'merchant_name': 'Whole Foods Market',
},
)
data = response.json()
best = data['top_cards'][0]
print(f"Best card: {best['name']} — {best['reason']}")
Response
{
"recommendation_id": "9f8b2c…",
"source": "bank_partner_api",
"category": "groceries",
"amount_cents": 8745,
"top_cards": [
{
"source": "catalog",
"rank": 1,
"id": "amex_blue_cash_preferred",
"name": "Blue Cash Preferred® Card from American Express",
"issuer": "American Express",
"network": "American Express",
"reward_rate": 6,
"reward_type": "cashback",
"reason": "6% on groceries",
"estimated_reward_cents": 524,
"estimated_reward_display": "$5.24",
"image_url": "https://…",
"apply_url": "https://…"
},
{ "rank": 2, … },
{ "rank": 3, … }
]
}
POST /v1/bank/portfolio
Analyzes a card portfolio against a spending profile. Returns per-category winners within the portfolio and gap analysis against the full catalog.
accessLevel: "both". Partners with accessLevel: "recommend" receive HTTP 403 ACCESS_DENIED.
Request body
| Field | Type | Status | Notes |
|---|---|---|---|
| card_product_ids | string[] | required | Array of SuperPay card product IDs. Max 100. Example: ["amex_gold", "chase_sapphire_preferred"]. |
| spend_profile | object | required | Object mapping category names to monthly USD spend amounts. Example: {"groceries":800,"dining":400}. Omitting this field returns HTTP 400 MISSING_SPEND_PROFILE. |
Example — curl
curl -X POST https://superpayrewards.com/v1/bank/portfolio \
-H "Authorization: Bearer sp_bank_xxxxxxxx_…" \
-H "Content-Type: application/json" \
-d '{
"card_product_ids": ["amex_gold", "chase_sapphire_preferred", "citi_double_cash"],
"spend_profile": {
"groceries": 800,
"dining": 400,
"travel": 600
}
}'
Example — JavaScript (fetch)
const res = await fetch('https://superpayrewards.com/v1/bank/portfolio', {
method: 'POST',
headers: {
'Authorization': 'Bearer sp_bank_xxxxxxxx_…',
'Content-Type': 'application/json',
},
body: JSON.stringify({
card_product_ids: ['amex_gold', 'chase_sapphire_preferred', 'citi_double_cash'],
spend_profile: { groceries: 800, dining: 400, travel: 600 },
}),
});
const data = await res.json();
console.log('Optimal allocation:', data.optimal_allocation);
for (const card of data.cards) {
console.log(`${card.name}: $${(card.projected_annual_reward_cents / 100).toFixed(2)}/yr`);
// card.rewardsByCategory = { groceries: { reward_rate: 4, annual_reward_cents: 38400 }, … }
}
Example — Python (requests)
import requests
response = requests.post(
'https://superpayrewards.com/v1/bank/portfolio',
headers={
'Authorization': 'Bearer sp_bank_xxxxxxxx_…',
'Content-Type': 'application/json',
},
json={
'card_product_ids': ['amex_gold', 'chase_sapphire_preferred', 'citi_double_cash'],
'spend_profile': {'groceries': 800, 'dining': 400, 'travel': 600},
},
)
data = response.json()
for cat, winner in data['optimal_allocation'].items():
print(f"{cat}: best card = {winner['card_name']} @ {winner['reward_rate']}%")
print(f"Total projected annual rewards: ${data['summary']['total_projected_annual_reward_cents']/100:.2f}")
Response
{
"analysis_id": "7a3b1f…",
"source": "bank_partner_api",
"portfolio_card_count": 3,
"categories_analyzed": 3,
"summary": {
"total_monthly_portfolio_reward_cents": 4800,
"total_monthly_market_best_reward_cents": 6200,
"total_monthly_gap_cents": 1400,
"coverage_rate": 1.0
},
"top_gaps": [
{
"category": "groceries",
"monthly_spend_usd": 800,
"gap_cents_monthly": 800,
"gap_rate": 2,
"market_best_card": {
"id": "amex_blue_cash_preferred",
"name": "Blue Cash Preferred®",
"issuer": "American Express",
"reward_rate": 6
}
}
],
"by_category": [ … ]
}
Spend Categories
When MCC is absent or unmapped, pass one of these values in the category field:
| Value | Description |
|---|---|
| groceries | Supermarkets, wholesale clubs (Costco, Sam's) |
| dining | Restaurants, fast food, bars, cafes |
| gas | Gas stations, fuel |
| travel | Airlines, hotels, car rentals, cruises, travel agencies |
| entertainment | Movies, concerts, sports, streaming |
| online_shopping | E-commerce, Amazon, general online retail |
| streaming | Video and music streaming services |
| drugstores | Pharmacies, drug stores |
| home_improvement | Home Depot, Lowe's, hardware stores |
| transit | Subway, bus, rideshare, commuter rail |
| shopping | Department stores, clothing, general retail |
| other | Everything else — uses base reward rate only |
Errors & Rate Limits
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_AMOUNT | amount missing or out of range (0–1,000,000 USD) |
| 400 | MISSING_CARD_IDS | Portfolio: card_product_ids missing or empty |
| 400 | PORTFOLIO_NOT_CONFIGURED | Recommend: non-sandbox key with no allowedCardProductIds on record and no card_product_ids in the request — configure your portfolio or pass card IDs per-request |
| 401 | MISSING_API_KEY | No key in Authorization or X-SuperPay-Key header |
| 401 | INVALID_API_KEY | Key not recognized or hash mismatch |
| 401 | API_KEY_REVOKED | Key has been revoked — contact hello@superpayrewards.com |
| 401 | INVALID_API_KEY_FORMAT | Key doesn't start with sp_bank_ |
| 403 | ORIGIN_NOT_ALLOWED | Browser request from unregistered origin |
| 403 | ACCESS_DENIED | Portfolio endpoint requested with recommend-only key |
| 429 | RATE_LIMITED | Over 300 requests per minute on this key; see Retry-After header |
| 500 | INTERNAL_ERROR | SuperPay-side failure — safe to retry with exponential backoff |
Rate-limit headers are set on every response: X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After (only on 429).
Key Types
| Key prefix | Type | Endpoints | How to get |
|---|---|---|---|
sp_bank_… (sandbox) | Sandbox | Both | Shown on /for-banks — rotates on server restart |
sp_bank_… (live) | Live | Per access level | Apply on /for-banks |
The sandbox key hits the same recommendation engine as live keys. Usage is logged identically, so it's appropriate for integration testing but not production traffic.
Onboarding & Key Rotation
- Submit your application at /for-banks#signup.
- Verify your email via the link in the verification email (expires in 24h).
- SuperPay reviews your application — typically within 1 business day.
- On approval, your
sp_bank_…key is emailed to you. It is shown exactly once — store it in your secrets manager immediately. - To rotate a lost key or update registered origins, email hello@superpayrewards.com.
sp_bank_… keys like passwords. Never commit them to source control, log them, or expose them in client-side code. SuperPay never stores or displays the plaintext after issuance — we can only rotate, not recover.
Go-Live Checklist
Complete every item below before switching from sandbox to your production key:
| Step | Action | Verify |
|---|---|---|
| 1. Smoke test sandbox | POST /v1/bank/recommend with your sandbox sp_bank_… key and a real MCC. |
HTTP 200, top_cards[0].reward_rate > 0, X-RateLimit-Limit: 300 header present. |
| 2. Portfolio smoke (if using) | POST /v1/bank/portfolio with 2–3 test card IDs and a spend_profile object. |
HTTP 200, optimal_allocation is an object keyed by category, cards[].rewardsByCategory present. |
| 3. Register origins | Confirm all production domains are listed in your approved application. | Browser request from production origin returns 200, not 403 ORIGIN_NOT_ALLOWED. |
| 4. Store key securely | Load sp_bank_… key from your secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.). Never hard-code. |
Key not present in source control, CI logs, or browser network requests. |
| 5. Handle rate limits | Read X-RateLimit-Remaining on every response. Implement exponential back-off on 429 RATE_LIMITED. |
Retry-After header respected; no hard failure on transient 429. |
| 6. Handle errors gracefully | Map 400 / 401 / 403 / 500 codes to appropriate UX states (fallback card, hide widget, etc.). |
A 500 response does not surface a raw error to the end user. |
| 7. Switch to production key | Replace sandbox key with the production sp_bank_… key in your secrets manager. |
First live call returns "source": "bank_partner_api". |
| 8. Monitor usage | Set up alerting on 429 rate (should be near 0) and p95 latency (<150ms typical). | Alert fires when 429 rate > 1% of requests. |
recommendation_id or analysis_id from the response for faster diagnosis.
Questions? Email hello@superpayrewards.com or visit /for-banks.