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/transactionsSubmit cardholder transaction events for real-time offer matching. The primary integration point for financial institutions.
Batch transactions
POST /api/v1/transactions/batchSubmit many transaction events in one signed request — for legacy or high-volume feeds.
Batch file upload (CSV)
CSV · Institution PortalPrefer a daily file? Upload a CSV of transactions from the Institution Portal — validated and matched like the webhook.
Offer feed
GET /api/v1/offersFetch a cardholder's eligible offers to render in your app or the embeddable widget.
Add offer (enroll)
POST /api/v1/offers/{id}/addEnroll a cardholder in an offer so their transactions start matching against it.
Reporting — Redemptions
GET /api/v1/reporting/redemptionsCursor-paginated redemption history for BI/data warehouse integration. Authenticated via institution API key.
Reporting — Settlements
GET /api/v1/reporting/settlementsCursor-paginated settlement ledger with merchant funded amount, Saldo revenue, and institution payout.
Reporting — Funnel
GET /api/v1/reporting/funnelEngagement 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": [] }- Send a transactions array of up to 1,000 items.
- The response reports per-item results — invalid or duplicate items are listed by index without failing the whole batch.
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 simulatereporting— 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/offersReturns active, budget-available offers for the cardholder, honoring your program's sort order and per-cardholder eligibility.
Query parameters
cardholder_tokenRequired. The opaque cardholder token.localeOptional. 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.segmentOptional. 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.limitOptional. 1–100 (also capped by your widget configuration).personalizeOptional. Set to true to rank the feed by cardholder affinity; adds a reason to each offer.prefer_merchants,prefer_categoriesOptional. 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}/addCreates 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.
- Signed with X-Saldo-Signature: sha256= followed by the HMAC-SHA256 of the raw body using your signing secret. Each delivery also carries X-Saldo-Timestamp (Unix seconds) and X-Saldo-Signature-V2: the HMAC-SHA256 of "<timestamp>.<raw body>". Verify V2 and reject timestamps older than 5 minutes so a captured delivery can't be replayed. Rotate the secret in Configuration — the new value is shown once.
- cardholder_token is your own full token, so you can find the customer to credit; cardholder_token_masked is for display and logs.
- A non-2xx response or network error is retried up to 8 times with exponential backoff (about two hours). After the last failed attempt, your portal inbox gets an alert.
- Every attempt — time, HTTP status, error and duration — is listed under Configuration → Redemption webhook deliveries.
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/redemptionsCursor-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/settlementsCursor-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/funnelEngagement 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
401— missing or invalid Bearer token403— token revoked, or the key doesn't have the reporting scope429— rate limit exceeded (60 / minute / key)500— upstream error; safe to retry with backoff
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.