Card Provisioning

liveEnd-to-end card provisioning: bulkCardGen creates cards on Tribe, bulkCardImport mirrors them into the cpm database.· 2026-05-28
0

The Whole Lifecycle

Provisioning a card is two stages. bulkCardGen reads a template and creates the cards on Tribe, logging the new ids to a file. Those ids then feed bulkCardImport, which reads each card back from Tribe and mirrors it into the cpm database. Gen writes only to Tribe; import is what populates our DB.

input
template.csv
3 sections · fields per card
stage 1 · acli
bulkCardGen
≤6 workers → createCard
artifact
out.csv
cardId, extId, ...
stage 2 · acli
bulkCardImport
≤6 workers · per-card tx
destination
cpm DB
card graph
1

Stage 1: bulkCardGen — Create on Tribe

shell
docker exec ... acli.sh bulkCardGen -input pc-gen.csv -output pc-out.csv -threads 1 -tribeCredential DEFAULT -rundry false

The Input File — A 3-Section Template

SectionPurpose
[bulk_card_create] One row per kind of card. ~70 columns mapping to Tribe's create-card params; a card_count column says how many to mint from that row.
[create_card_defaults] request_param,default_value pairs. Any column a row leaves blank falls back here. Unknown param names are rejected.
[address_templates] Named, reusable address blocks. A row references one via holder_address / delivery_address / bulk_address instead of repeating fields.

Sample Template

csv
# comments start with # [bulk_card_create] card_program_id,card_design_id,holder_address,card_count 380107,55,uk_office,500 380107,55,uk_office,250 [create_card_defaults] request_param,default_value card_country_ison,826 card_virtual,0 default_locale,en_GB [address_templates] template_name,city,zipcode,country_ison,address_line_1 uk_office,London,EC1A 1BB,826,1 High St

How Each Field Is Resolved

Fields are resolved in this priority order: row value → if blank, defaults section → if blank and an address field, named address template → else null / omitted.

card_count fans out: the first row above mints 500 identical cards, the second 250 — so the file's batch size is the sum of every row's count. Country/currency codes are normalised to numeric-3. A field that stays null is simply left out of the Tribe request.

Flow — What Happens Per Card

StepTagDescription
1. Build resolve Pull each column for this card (row -> defaults -> address template), normalise ISO codes, and assemble the Tribe CreateCardAction request.
2. Gate pool Card #1 runs synchronously (fail-fast -- bad creds/program surface before the whole batch fires); the rest fan out across the pool, capped at 6 concurrent for Tribe.
3. Tribe RSA new Cards(api).createCard(req) creates the card on Tribe and returns its ids. -rundry true fabricates a response and skips Tribe -- for validating a template safely.
4. Check error A Tribe rejection is counted; with -maxErrorCount at its default 0, the first error stops the batch. Pass a higher value to push through.
5. Record output A single notifier thread appends the result -- cardId,cardExtId,programId,designId,... on success, or seq,rootCause on failure.
2

The Hand-off

Gen's output rows are exactly the ids import needs — Tribe's card id becomes import's spiCardId; the external ref carries over.

bulkCardGen OutputbulkCardImport Input
cardId (e.g. 1499713) spiCardId
cardExtId (e.g. 9295121628797499) externalId
(not used) PAN (optional)

Field mapping:cardIdspiCardId · cardExtIdexternalId · PAN optional (else import fetches it from Tribe).

3

Stage 2: bulkCardImport — Mirror into cpm

shell
docker exec ... acli.sh bulkCardImport -input cards.csv -threads 6 -progressEvery 10 -tribeCredential DEFAULT

The Input File — A Flat Card List

FieldDescription
spiCardId Tribe card id (required, first numeric field)
externalId Your external reference (required, second numeric field)
PAN Optional. If >= 16 digits, masked locally; if omitted, import fetches from Tribe via getCardNumber.
Extra columns Ignored silently.

Sample cards.csv

csv
# spiCardId, externalId, optional PAN (extra columns ignored) 1499713,9295121628797499, 1499714,9295121628797500,5480391234568598 # header / blank lines that don't match the pattern are silently skipped

Input Rules

  • Row pattern ^[0-9]{6,20}\s*,\s*[0-9]{6,20}.* — the first two numeric fields (spiCardId, externalId) are required; non-matching rows are silently skipped.
  • PAN is optional. Supplied (≥16 digits) → masked locally; omitted → import makes an extra Tribe getCardNumber call to fetch + mask + hash it.
  • Duplicate spiCardId → hard error — the whole run aborts before importing anything.
  • On launch it counts the rows and prompts "...importing N cards? [y/n]" — only y proceeds.
  • This list is usually gen's out.csv reshaped (see the hand-off above): cardIdspiCardId, cardExtIdexternalId.

Flow — Per Card (Its Own Committed Transaction)

StepTagDescription
1. Context credential Set Tribe credential. DEFAULT / VISA selects the RSA keys for the calls below.
2. Tribe getCardDetails Reads the card from Tribe. Not on Tribe -> VendorException (card fails). Returns program, holder, account, currency, status...
3. Tribe getCardNumber Only if no PAN supplied. Fetches + masks + hashes the PAN.
4. Resolve database Find PartnerProgram by Tribe program id; look up existing card by SpiId / ExternalId.
5. Idempotent check Already imported? Skip. Re-running the same list is safe.
6. Commit database Write the card graph and commit this card's own transaction. One bad card rolls back alone.

What Lands in cpm

EntityTableKey Details
PaymentCardEntity PAYMENT_CARD maskedPan, panHash, homeCurrency, isVirtual, validUntil, designId, kyc_locked = false, requires_kyc = null
CardOrderEntity CARD_ORDER new, status COMPLETE
CustomerAccount CUSTOMER_ACCOUNT get-or-create by SpiId
AccountOwner ACCOUNT_OWNER KycStatus Unverified, get-or-create
status ACTION_REQUEST spiImport + Tribe status
EntityExtRef (x N) external refs Links every entity (SpiId, ExternalId, panHash)

External references: SpiId (Tribe ids) · ExternalId (your ref) · panHash (lookup).

4

Concurrency — The Tribe Limit

Tribe throttles / flags us above 6 concurrent calls. Both commands cap their worker pool at 6 — and for these commands, pool size == concurrent Tribe calls.

WorkerOperationNotes
1-6 createCard / getCardDetails Active Tribe calls via the worker pool
7+ waits Blocked until a slot opens
output thread write results Never calls Tribe
5

Where Everything Ends Up

template
your input
→ gen →
Tribe
card created
→ import →
cpm DB
card graph mirrored

Gen's out.csv is the bridge — its card ids become import's input. Skip import and the cards exist on Tribe but never appear in cpm.

Source Classes

BulkCardGenCommand · BulkCardOrderProcessor · BulkCardGenRequest · BulkCardImport · CardImportTask