SuperPay Bank Partner API

Partner Guide

API field naming convention: All request and response fields use snake_case (e.g. 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:

No shopper PII. Both endpoints work purely on catalog data. You send an amount, MCC code, and optional merchant name — never a cardholder name, account number, or transaction ID. SuperPay does not receive or store any identifiable customer data through the Bank Partner API.

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.

Origin allowlists. When you apply, register the exact origins your app runs on (e.g. 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

FieldTypeStatusNotes
amountnumberrequiredUSD, e.g. 87.45. Must be positive and ≤ 1,000,000.
mccstringpreferredISO 18245 4-digit MCC. Takes precedence over category.
categorystringfallbackUsed when MCC is absent or unmapped. See categories.
merchant_namestringrecommendedDisplay 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.

Access level required. This endpoint is only available to partners with accessLevel: "both". Partners with accessLevel: "recommend" receive HTTP 403 ACCESS_DENIED.

Request body

FieldTypeStatusNotes
card_product_idsstring[]requiredArray of SuperPay card product IDs. Max 100. Example: ["amex_gold", "chase_sapphire_preferred"].
spend_profileobjectrequiredObject 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:

ValueDescription
groceriesSupermarkets, wholesale clubs (Costco, Sam's)
diningRestaurants, fast food, bars, cafes
gasGas stations, fuel
travelAirlines, hotels, car rentals, cruises, travel agencies
entertainmentMovies, concerts, sports, streaming
online_shoppingE-commerce, Amazon, general online retail
streamingVideo and music streaming services
drugstoresPharmacies, drug stores
home_improvementHome Depot, Lowe's, hardware stores
transitSubway, bus, rideshare, commuter rail
shoppingDepartment stores, clothing, general retail
otherEverything else — uses base reward rate only

Errors & Rate Limits

StatusCodeMeaning
400INVALID_AMOUNTamount missing or out of range (0–1,000,000 USD)
400MISSING_CARD_IDSPortfolio: card_product_ids missing or empty
400PORTFOLIO_NOT_CONFIGUREDRecommend: 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
401MISSING_API_KEYNo key in Authorization or X-SuperPay-Key header
401INVALID_API_KEYKey not recognized or hash mismatch
401API_KEY_REVOKEDKey has been revoked — contact hello@superpayrewards.com
401INVALID_API_KEY_FORMATKey doesn't start with sp_bank_
403ORIGIN_NOT_ALLOWEDBrowser request from unregistered origin
403ACCESS_DENIEDPortfolio endpoint requested with recommend-only key
429RATE_LIMITEDOver 300 requests per minute on this key; see Retry-After header
500INTERNAL_ERRORSuperPay-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 prefixTypeEndpointsHow to get
sp_bank_… (sandbox)SandboxBothShown on /for-banks — rotates on server restart
sp_bank_… (live)LivePer access levelApply 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

  1. Submit your application at /for-banks#signup.
  2. Verify your email via the link in the verification email (expires in 24h).
  3. SuperPay reviews your application — typically within 1 business day.
  4. On approval, your sp_bank_… key is emailed to you. It is shown exactly once — store it in your secrets manager immediately.
  5. To rotate a lost key or update registered origins, email hello@superpayrewards.com.
Key security. Treat 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:

StepActionVerify
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.
Questions? Email hello@superpayrewards.com with your partner institution name and the issue you're seeing. Include the recommendation_id or analysis_id from the response for faster diagnosis.

Questions? Email hello@superpayrewards.com or visit /for-banks.