API Documentation

The Saldo.tech REST API lets financial institutions and wallets integrate payment-linked offers into their apps — across cards, PIX and other rails. All endpoints use JSON over HTTPS and authenticate with a per-institution API key (Bearer token) — the institution is always derived from the key, never from the request body.

Overview

The API is organized around these resource areas:

Transactions Webhook

POST /api/v1/transactions

Submit cardholder transaction events for real-time offer matching. The primary integration point for financial institutions.

Batch transactions

POST /api/v1/transactions/batch

Submit many transaction events in one signed request — for legacy or high-volume feeds.

Batch file upload (CSV)

CSV · Institution Portal

Prefer a daily file? Upload a CSV of transactions from the Institution Portal — validated and matched like the webhook.

Offer feed

GET /api/v1/offers

Fetch a cardholder's eligible offers to render in your app or the embeddable widget.

Add offer (enroll)

POST /api/v1/offers/{id}/add

Enroll a cardholder in an offer so their transactions start matching against it.

Reporting — Redemptions

GET /api/v1/reporting/redemptions

Cursor-paginated redemption history for BI/data warehouse integration. Authenticated via institution API key.

Reporting — Settlements

GET /api/v1/reporting/settlements

Cursor-paginated settlement ledger with merchant funded amount, Saldo revenue, and institution payout.

Reporting — Funnel

GET /api/v1/reporting/funnel

Engagement funnel for a date window: widget views, unique cardholders, impressions, adds, and redemptions.

Transaction Webhook

The transaction webhook is the core integration for financial institutions. Post transaction events as they occur, and Saldo.tech's matching engine will determine if a qualifying offer exists.

POST /api/v1/transactions

Authorization: Bearer slk_<your-key>

{
  "institution_id": "8f3e0b1c-...-uuid",  // optional — derived from your key
  "cardholder_token": "tok_ch_abc123",
  "instrument_token": "tok_inst_xyz789",  // card OR wallet/account token (card_token is the card-rail alias)
  "instrument_type": "card",              // card | wallet | account
  "merchant_identifier": "CA1234567890",  // preferred — the id your record carries
  "merchant_identifier_type": "card_acceptor", // card_acceptor | pix_key | upi_vpa | …
  "card_acceptor_id": "CA1234567890",     // legacy alias (implies card_acceptor)
  "mcc": "5411",                          // optional — reserved for enrichment
  "merchant_descriptor": "SOLESTYLE #12", // optional — reserved for enrichment
  "network": "visa",                      // optional
  "merchant_id": "aaaaaaaa-...-uuid",     // optional — only if you already resolved it
  "amount": 225.00,
  "transaction_ref": "txn_unique_id",
  "transacted_at": "2025-03-15T14:30:00Z"
}

Request Fields

institution_iduuidOptional — the account is derived from your API key. Include it only if you want the server to reject a mismatch.
cardholder_tokenstringOpaque, STABLE per-customer id generated by your institution — e.g. HMAC-SHA256 of a stable key like the customer's email or your internal customer id, keyed with a secret you hold (a KEYED HMAC, not a plain hash — email is low-entropy and a plain hash is re-identifiable). Same customer → same token, across every offer and over time. Never send a raw email/customer id; don't rotate or randomize per event, or transactions won't match enrollments.
card_tokenstringTokenized PAN for the card used (FI-generated, never the real card number). Card rail; for QR/A2A use instrument_token instead.
instrument_tokenstringThe payment instrument, tokenized — a card token OR a wallet/account token for QR/A2A consumers with no card. Send this or card_token.
instrument_typestringInstrument kind: card | wallet | account. Optional.
merchant_identifierstringPreferred. The merchant identifier your transaction record carries (a card-acceptor ID, a PIX key, a UPI VPA, …). Saldo resolves it to the merchant.
merchant_identifier_typestringType of merchant_identifier: card_acceptor | pix_key | upi_vpa | codi | psp_merchant_id. Defaults to card_acceptor.
card_acceptor_idstringPreferred. The card-acceptor ID from your transaction record (ISO-8583 field 42). Saldo resolves it to the merchant. Send this or merchant_id.
mccstringOptional. Merchant Category Code. Reserved for future enrichment; not used for matching yet.
merchant_descriptorstringOptional. The merchant descriptor from your transaction record. Reserved for future fallback resolution; not matched on yet.
networkstringOptional. Card network (e.g. visa, mastercard).
merchant_iduuidOptional. Saldo.tech merchant UUID — send only if you have already resolved the merchant yourself; otherwise send card_acceptor_id.
amountnumberTransaction amount, in the offer's currency.
transaction_refstringYour unique reference for this transaction. Used for idempotency (unique per institution).
transacted_atstringISO 8601 timestamp of when the transaction occurred.

Batch transaction ingestion

Legacy or high-volume institutions can post many transactions in one HMAC-signed request instead of one webhook per event. Each item uses the same fields as the single webhook.

POST /api/v1/transactions/batch

{
  "transactions": [
    { "institution_id": "8f3e...uuid",
      "cardholder_token": "v1_ia_kp4EZvf5J2KpiRymLtJfFJd98ItuF07g95--u5YI",
      "card_token": "v1_or_44Zrluj8Iy3On_vABCdjpecpxLFo3_gE8BIp-iwQ",
      "merchant_id": "aaaa...uuid",
      "amount": 225.00, "transaction_ref": "txn_1",
      "transacted_at": "2025-03-15T14:30:00Z" }
  ]
}

// 202 Accepted
{ "received": 1, "accepted": 1, "duplicates": 0, "invalid": [] }

Batch file upload (CSV)

Institutions on Growth or Enterprise plans can upload a daily CSV of transactions from the Institution Portal (Batch uploads) instead of calling the webhook per event. The file is validated row-by-row, deduplicated by transaction_ref and run through the same matching pipeline; a per-row error report is produced for any rejected lines.

Columns (header row required, any order)

cardholder_token,card_token,card_acceptor_id,amount,transaction_ref,transacted_at
tok_ch_abc123,tok_card_xyz789,CA1234567890,225.00,txn_1,2025-03-15T14:30:00Z

institution_id is taken from your account — never the file. Blank lines are skipped; invalid rows are reported by line number without failing the whole file. Include card_acceptor_id (preferred — Saldo resolves it to your merchant) or merchant_id.

Authentication

Every endpoint authenticates with your institution's Bearer API key. Each key carries scopes that decide which endpoints it can call.

API key

Generate from the Institution Portal under Dashboard → API Keys. Plaintext is shown once at creation; only the SHA-256 hash is stored. Each key is scoped to its institution.

Authorization: Bearer slk_<random>
Generate a key →

Transaction webhooks

The transaction and batch endpoints use the same Bearer API key as everything else — no separate signing secret. Send Authorization: Bearer and the institution is taken from the key.

Authorization: Bearer slk_<random>

Key scopes

Choose scopes when you generate a key, and give each key only what it needs — a BI tool only needs reporting. Calling an endpoint outside the key's scopes returns 403. Keys created before scopes existed keep all three.

  • offers — offer feed and enrollment (GET /api/v1/offers, POST /api/v1/offers/:id/add)
  • transactions — transaction webhook, batch and simulate
  • reporting — redemptions, settlements and funnel reporting

Key expiry and rotation

Keys can carry an expiry date (one year by default). Saldo reminds your team 14 and 3 days before a key expires and on the day; an expired key gets 403 (Token expired). To rotate without downtime: create a new key with the same scopes, deploy it, confirm requests succeed, then revoke the old key. Rotate at least once a year, and at once if a key may have been exposed or someone who held it leaves. Creating and revoking keys is recorded in your Security activity.

Offer feed & enrollment

Fetch the offers a cardholder can see, then enroll them. Both endpoints authenticate with your institution API key — the institution is derived from the key, and all cardholder identifiers are tokenized.

GET/api/v1/offers

Returns active, budget-available offers for the cardholder, honoring your program's sort order and per-cardholder eligibility.

Query parameters

  • cardholder_token Required. The opaque cardholder token.
  • locale Optional. en, es or pt-BR — localizes each offer's title and description. If your program doesn't offer that language, the feed uses your program's first supported language.
  • segment Optional. The cardholder's segment code from your own systems (e.g. premium). Offers you limited to segments only appear when it matches; unrestricted offers always appear. Used for this request only — Saldo never stores a cardholder's segment.
  • limit Optional. 1–100 (also capped by your widget configuration).
  • personalize Optional. Set to true to rank the feed by cardholder affinity; adds a reason to each offer.
  • prefer_merchants, prefer_categories Optional. prefer_merchants and prefer_categories accept comma-separated hints, used in-memory only and never stored.
curl "https://saldo-consumer-api-git-staging-kalantech.vercel.app/api/v1/offers?cardholder_token=tok_ch_abc123&locale=es&limit=20" \
  -H "Authorization: Bearer slk_..."

{
  "offers": [
    {
      "id": "0b1...",
      "title": "Spend $200 at Acme, get $15 back",
      "description": "Gasta $200 en Acme y recibe $15",
      "merchant_id": "aaaa...",
      "min_spend": 200.00,
      "cashback_amount": 13.20,
      "currency": "USD",
      "starts_at": "2026-04-01T00:00:00Z",
      "expires_at": "2026-06-30T00:00:00Z",
      "added": false
    }
  ],
  "total": 12,
  "personalized": false
}
POST/api/v1/offers/{id}/add

Creates the cardholder–offer link so matching can begin. Idempotent — re-adding returns already_added.

Request body

curl -X POST "https://saldo-consumer-api-git-staging-kalantech.vercel.app/api/v1/offers/0b1.../add" \
  -H "Authorization: Bearer slk_..." \
  -H "Content-Type: application/json" \
  -d '{ "cardholder_token": "tok_ch_abc123", "card_token": "tok_card_xyz789" }'

// 201 Created
{ "status": "added", "cardholder_offer_id": "c0f..." }

// 200 OK (idempotent re-add)
{ "status": "already_added", "cardholder_offer_id": "c0f..." }

Pass the same segment when adding an offer limited to segments (403 segment_not_eligible otherwise). If the cardholder already holds your program's maximum number of active offers, the add returns 409 max_active_offers.

Redemption webhook (Saldo → you)

When a purchase meets an offer's terms, Saldo POSTs the redemption to the webhook URL in your Institution Portal configuration so you can credit the cardholder.

POST https://your-bank.example/saldo/redemptions
X-Saldo-Signature: sha256=9f2c...
X-Saldo-Timestamp: 1791633600
X-Saldo-Signature-V2: sha256=4b7e...

{
  "event": "redemption.completed",
  "redemption_id": "r1a...",
  "institution_id": "i9f...",
  "offer_id": "0b1...",
  "cardholder_token": "v1_ia_kp4E…(46 characters)…u5YI",
  "cardholder_token_masked": "v1_i...u5YI",
  "institution_payout_amount": 13.20,
  "occurred_at": "2026-10-08T12:00:00.000Z"
}

Reporting API

Pull your institution's redemption, settlement, and engagement data programmatically — for finance reconciliation, BI dashboards, or custom internal tools. All endpoints return JSON, are scoped to the institution that owns the API key, and rate-limited to 60 requests per minute per key.

GET/api/v1/reporting/redemptions

Cursor-paginated list of redemptions for the calling institution. Cardholder tokens are masked to first/last 4 characters.

Query parameters

  • from, to — ISO 8601 timestamps (optional)
  • cursor — opaque value from the previous response (optional)
  • limit — 1 to 1000 (default 100)

Example

curl https://saldo-consumer-api-git-staging-kalantech.vercel.app/api/v1/reporting/redemptions?limit=2 \
  -H "Authorization: Bearer slk_..."

{
  "data": [
    {
      "id": "8f3...",
      "created_at": "2026-04-29T14:32:11.000Z",
      "status": "settled",
      "merchant_funded_amount": 15.00,
      "institution_payout_amount": 13.20,
      "offer": {
        "id": "0b1...",
        "title": "Spend $200 at Acme, get $15 back",
        "merchant_name": "Acme Inc."
      },
      "cardholder_token_masked": "tok_...c123"
    }
  ],
  "pagination": {
    "limit": 2,
    "next_cursor": "2026-04-29T14:32:11.000Z|8f3..."
  }
}
GET/api/v1/reporting/settlements

Cursor-paginated settlement ledger entries with merchant funded amount, Saldo take revenue, institution payout, and status.

Query parameters

  • status — pending | settled | reversed (optional)
  • from, to, cursor, limit — same semantics as redemptions
GET/api/v1/reporting/funnel

Engagement funnel aggregated for a date window. Defaults to the last 30 days when from / to are omitted.

{
  "window": { "from": "...", "to": "..." },
  "data": {
    "widget_views": 12834,
    "unique_cardholders": 4291,
    "offer_impressions": 38420,
    "offer_add_successes": 5102,
    "redemptions": 612
  }
}

Pagination

List endpoints return a pagination.next_cursor string when more rows are available. Pass it back as the cursor query param to fetch the next page. The cursor is opaque — treat it as an arbitrary string. When next_cursor is null, you have reached the end.

Connecting from a BI tool?

Copy-paste recipes for cURL, Python, Node.js, Google Sheets, Power BI, and Metabase.

See integration recipes →

Errors

Privacy and Data Handling

The Saldo.tech API is designed around tokenized identifiers. Your institution generates cardholder_token and card_token values using your own cryptographic keys. Saldo.tech never receives, processes, or stores cardholder PII (names, emails, addresses, or real card numbers). See our Privacy Policy for full details.

Development Environment

Use the simulation endpoint to test transaction matching without real payment data:

POST /api/v1/transactions/simulate

This endpoint accepts the same payload as the production transaction webhook but skips settlement processing. Use it to verify your integration handles match/no-match responses correctly.

Ready to integrate?

Contact our team to get your API credentials and start building.