Skip to content

vgrid fetch

Fetch transaction data from financial service APIs and convert it to VisiGrid’s canonical truth format.

Terminal window
vgrid fetch <source> [options]

VisiGrid connects to 11 financial data sources. Each adapter produces the same canonical output format below.

All fetchers produce the same canonical 9-column CSV:

ColumnTypeRequiredDescription
effective_datestringyesISO 8601 date (YYYY-MM-DD)
posted_datestringnoSettlement/posting date
amount_minorintyesMinor units (cents). Never float.
currencystringyesISO 4217 uppercase (USD, EUR)
typestringyesTransaction type (charge, refund, …)
sourcestringyesAdapter name
source_idstringyesUnique ID from upstream system
group_idstringnoGrouping key (payout ID, invoice, …)
descriptionstringnoHuman-readable memo

Output is deterministically sorted (default: group_id, effective_date, source_id) so identical data always produces byte-identical CSV.


Connect any REST API that returns JSON. A mapping file tells VisiGrid how to extract the 9 canonical columns from each JSON item.

Terminal window
vgrid fetch http \
--url https://api.example.com/v1/transactions \
--auth bearer-env:EXAMPLE_API_KEY \
--map mapping.json \
--from 2026-01-01 --to 2026-02-01 \
--out transactions.csv
{
"root": "$.data.transactions",
"params": {
"from": { "query": "start_date", "format": "iso" },
"to": { "query": "end_date", "format": "iso" }
},
"columns": {
"effective_date": "$.created_at",
"posted_date": { "path": "$.settled_at", "optional": true },
"amount_minor": { "path": "$.amount_usd", "transform": "dollars_to_cents" },
"currency": { "path": "$.currency", "transform": "upper" },
"type": { "path": "$.category", "map": { "payment": "charge", "refund": "refund", "*": "adjustment" } },
"source": { "const": "example_api" },
"source_id": "$.id",
"group_id": { "path": "$.invoice_id", "optional": true },
"description": { "path": "$.memo", "optional": true }
},
"sort_by": ["effective_date", "source_id"]
}

Key concepts:

  • root — dot-path to the response array ($.data, $.payments[0].items, etc.)
  • params — maps --from/--to to query parameter names. Supports iso, unix_s, unix_ms date formats.
  • columns — maps each canonical column. Use a string for simple path extraction, or an object for transforms/constants/optional handling.

Auth credentials are resolved from environment variables only — never inline secrets.

MethodFlagExample
None--auth nonePublic APIs
Bearer token--auth bearer-env:VARbearer-env:FORTE_TOKEN
Custom header--auth header-env:NAME:VARheader-env:X-API-Key:MY_KEY
Basic auth--auth basic-env:USER:PASSbasic-env:API_USER:API_PASS
TransformInputOutputUse case
cents5000 (int)5000Already in minor units
dollars_to_cents"50.00" (string)5000Dollar amounts → cents
upper"usd""USD"Currency normalization
lower"CHARGE""charge"Type normalization

Map upstream enum values to your canonical types:

"map": { "payment": "charge", "refund": "refund", "*": "adjustment" }

The * wildcard catches any value not explicitly mapped.

Most APIs paginate responses. Add a pagination section to your mapping file to automatically fetch all pages.

Two strategies are supported:

Cursor-based (Stripe, Brex style):

{
"root": "$.data",
"pagination": {
"strategy": "cursor",
"param": "starting_after",
"page_size_param": "limit",
"page_size": 100,
"next_cursor_path": "$.data[-1].id",
"has_more_path": "$.has_more"
},
"columns": { ... }
}

Offset-based (Mercury style):

{
"root": "$.transactions",
"pagination": {
"strategy": "offset",
"param": "offset",
"page_size_param": "limit",
"page_size": 500,
"has_more_path": "$.meta.has_more"
},
"columns": { ... }
}
Pagination fieldDescriptionRequired
strategy"cursor" or "offset"yes
paramQuery param name for cursor/offset valueyes
page_size_paramQuery param name for page sizeyes
page_sizeItems per pageno (default: 100)
next_cursor_pathJSONPath to next cursor value (cursor only)cursor: yes
has_more_pathJSONPath to boolean “has more” flagno (inferred from page size)

If has_more_path is omitted, pagination stops when a page returns fewer items than page_size.

Safety guards:

  • --max-pages caps the number of pages fetched (default: 100)
  • --max-items caps the total number of items across all pages (default: 10,000)
  • Stuck cursor detection: if the cursor value doesn’t change between pages, the adapter errors instead of looping forever
  • Empty page with has_more=true is treated as an error, not silent truncation
FlagDescriptionDefault
--urlAPI endpoint (HTTPS only)required
--authAuth methodnone
--mapPath to mapping JSONrequired
--from / --toDate range (YYYY-MM-DD)required
--outOutput CSV pathstdout
--samplePrint raw JSON response and exitoff
--save-rawSave raw JSON to file for auditoff
--timeoutRequest timeout in seconds15
--max-itemsSafety cap on total items10,000
--max-pagesSafety cap on pages fetched100
--quietSuppress progress messagesoff
--fingerprintWrite signed request fingerprint JSONoff

Add --fingerprint <path> to produce a signed JSON sidecar that records what was requested, when, and from where. This is useful for audit trails — you can later prove that a specific CSV was produced by a specific API call.

Terminal window
vgrid fetch http \
--url https://api.vendor.com/v1/payments \
--auth bearer-env:VENDOR_TOKEN \
--from 2026-01-01 --to 2026-01-31 \
--map mapping.json --out payments.csv \
--fingerprint payments.fingerprint.json

The fingerprint is a SignedEnvelope (same format as vgrid sign) containing:

FieldDescription
request.urlFull URL with query params (no secrets — tokens go in headers)
request.auth_methodAuth flag as passed (e.g. bearer-env:VENDOR_TOKEN), not the secret
request.from / toDate range
request.pages_fetchedNumber of pages fetched (useful for detecting truncation)
mapping.pathMapping file path
mapping.blake3BLAKE3 hash of the mapping file
output.row_countTotal rows written
output.csv_blake3BLAKE3 hash of the output CSV (only when --out is used)

The signing key is auto-generated at ~/.config/vgrid/proof_key.json on first use (same key as vgrid sign).

Use --sample to inspect the raw API response before writing a mapping:

Terminal window
vgrid fetch http \
--url https://api.example.com/v1/transactions \
--auth bearer-env:EXAMPLE_API_KEY \
--from 2026-01-01 --to 2026-01-02 \
--sample