Linking Cards to Bank Accounts

Introduction

Linking a card to a connected bank account is what turns on earlier settlement for that card: when a card authorization matches a posted bank transaction on the account, the transaction flips PENDING→SETTLED on the bank's evidence, typically within hours of the swipe rather than days.

Linking cards is optional and independent of the bank connection itself. Bank-only events (fees, transfers, payroll, FX markup) fire on a connected bank account with no card link at all. See Quick Start: Bank Linking. This page is about the second step: attaching cards for earlier settlement.

📘

You may not need to call anything

Card links are detected automatically. Read on for when to link manually: pre-linking before any spend, correcting an assignment, or importing history.

Automatic detection

Astrada links cards automatically at two moments, with no API call required. At bank-link completion, each discovered credit account's mask is compared against the subaccount's enrolled cards, and a card is linked when its last 4 digits match exactly one way; a mask matching multiple cards, or claimed by multiple accounts, links nothing.

What happens next depends on what that mask matched. If none of those matches is a card you've already enrolled, the account gets its own bank-feed card instead, so its feed still has an identity (see Unified Card & Bank Feeds). If the mask matched a card you've already enrolled, no bank-feed card is created either: a duplicate guard blocks the mint, so you don't end up with a second, phantom card for one you already have.

After completion, the same last-4 rule keeps running. Every few hours Astrada re-checks each bank account that still has no card against the subaccount's enrolled cards, and links one when the match is again exactly one-to-one. So the common card-after-bank path needs no API call either: connect the bank, enroll the card whenever you like, and the link appears on the next pass.

It is deliberately the same rule in both places. A link is decided by last 4 digits and institution, never by guesswork about which card a transaction belongs to, so the answer you get after completion is the answer you would have got at completion had the card existed then.

Two things to know about auto-detected links:

  • They do not backfill history. Only transactions from the moment of linking forward benefit from earlier settlement. To import earlier history, link the card manually with backfill (below).
  • The current set of linked cards is always readable. See Reading the current links.

Manual linking

PATCH /bank-accounts/{id} (scope banking:write) sets the account's card set explicitly. It's the manual override for the automatic detector. Use it to:

  • Pre-link a card before any spend exists, so the very first matching transaction settles early.
  • Correct an assignment, or move a card between accounts.
  • Import history for newly linked cards, via backfill.
PATCH /bank-accounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890 HTTP/1.1
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "cardIds": [
    "5dec2c49-0aa5-4683-a317-427eb5d115f3",
    "8abc1d23-4e56-7f89-0abc-def123456789"
  ],
  "backfill": { "startDate": "2026-01-27", "endDate": "2026-04-27" }
}
curl -X PATCH https://api.astrada.co/bank-accounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cardIds": [
      "5dec2c49-0aa5-4683-a317-427eb5d115f3",
      "8abc1d23-4e56-7f89-0abc-def123456789"
    ],
    "backfill": { "startDate": "2026-01-27", "endDate": "2026-04-27" }
  }'
{
  "cardIds": [
    "5dec2c49-0aa5-4683-a317-427eb5d115f3",
    "8abc1d23-4e56-7f89-0abc-def123456789"
  ],
  "backfill": { "startDate": "2026-01-27", "endDate": "2026-04-27" }
}
FieldTypeRequiredDescription
cardIdsUUID[]YesThe cards to link. Full replacement: send the complete desired set, not a delta.
backfillobjectNoOpt-in historical import. Runs only for cards being added in this request.
backfill.startDateISO dateNoDefaults to 90 days ago.
backfill.endDateISO dateNoDefaults to today.

Response (200 OK):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "cardIds": [
    "5dec2c49-0aa5-4683-a317-427eb5d115f3",
    "8abc1d23-4e56-7f89-0abc-def123456789"
  ],
  "state": "active",
  "updatedAt": "2026-04-27T15:00:00Z",
  "backfill": { "transactionsProcessed": 50, "matchesFound": 10 }
}

The backfill block appears only when a backfill was requested.

🚧

cardIds is a full replacement

The endpoint replaces the account's card set with exactly what you send. It is not a delta. To add a card, send the existing cardIds plus the new one; to remove one, send the set without it. Sending cardIds: [] clears every link on the account: manual, mask-matched, and the link to any card created from this account at completion. Unlinking a created card changes how the account's feed is attributed and removes it from the account's cards[] list; the card and its bank-feed subscription continue to exist, and re-linking it later restores it to cardIds only.

A removal is not permanent. If the card you removed still matches the account's last 4, automatic detection re-links it within a few hours. That is designed convergence, not a bug: a manual removal is a point-in-time override, and the last-4 rule keeps applying. To keep a card off an account for good, link it to the account it does belong to, or deactivate it. Always start from the account's current cardIds, which includes created cards, and edit that set.

Backfill history

When you add cards, pass backfill: { startDate, endDate } to import historical bank transactions for those cards (default: the last 90 days). Earlier settlement then runs against any historical card authorizations still in PENDING, and bank-only events fire retroactively for historical transactions that remain unmatched.

Backfill runs only for the cards added in that request, not cards already linked to the account.

Multi-card linking & constraints

A bank account can carry many cards (1:N); a card links to at most one bank account at a time. Each rule below returns a specific error. Handle them in your linking UI:

RuleOn violation
A card links to only one bank account at a time.409 Conflict: the card is already linked to another bank account.
Every card and the bank account must share the same subaccountId.Enforced upstream by card issuance; banking trusts the cardIds you pass.

To move a card from account A to account B: first PATCH A with a cardIds set that omits the card (freeing it), then PATCH B to add it. Linking a card to B while it's still linked to A returns 409 Conflict (the card is already linked to another bank account).

Reading the current links

  • GET /bank-accounts/{id} (scope banking:read): the account, including its cardIds (linked cards) and cards[] (cards created from the account at bank-link completion).
  • GET /bank-accounts?cardId={cardId}: the account a given card is linked to (empty if none).
  • GET /bank-accounts?subaccountId={subaccountId}: every account for a subaccount, each with its cardIds.

Next steps


Did this page help you?