vgrid fetch
Fetch transaction data from financial service APIs and convert it to VisiGrid’s canonical truth format.
vgrid fetch <source> [options]VisiGrid connects to 11 financial data sources. Each adapter produces the same canonical output format below.
Output format
Section titled “Output format”All fetchers produce the same canonical 9-column CSV:
| Column | Type | Required | Description |
|---|---|---|---|
effective_date | string | yes | ISO 8601 date (YYYY-MM-DD) |
posted_date | string | no | Settlement/posting date |
amount_minor | int | yes | Minor units (cents). Never float. |
currency | string | yes | ISO 4217 uppercase (USD, EUR) |
type | string | yes | Transaction type (charge, refund, …) |
source | string | yes | Adapter name |
source_id | string | yes | Unique ID from upstream system |
group_id | string | no | Grouping key (payout ID, invoice, …) |
description | string | no | Human-readable memo |
Output is deterministically sorted (default: group_id, effective_date, source_id) so identical data always produces byte-identical CSV.
Bring-your-own-API adapter (http)
Section titled “Bring-your-own-API adapter (http)”Connect any REST API that returns JSON. A mapping file tells VisiGrid how to extract the 9 canonical columns from each JSON item.
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.csvMapping file
Section titled “Mapping file”{ "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/--toto query parameter names. Supportsiso,unix_s,unix_msdate formats.columns— maps each canonical column. Use a string for simple path extraction, or an object for transforms/constants/optional handling.
Auth methods
Section titled “Auth methods”Auth credentials are resolved from environment variables only — never inline secrets.
| Method | Flag | Example |
|---|---|---|
| None | --auth none | Public APIs |
| Bearer token | --auth bearer-env:VAR | bearer-env:FORTE_TOKEN |
| Custom header | --auth header-env:NAME:VAR | header-env:X-API-Key:MY_KEY |
| Basic auth | --auth basic-env:USER:PASS | basic-env:API_USER:API_PASS |
Transforms
Section titled “Transforms”| Transform | Input | Output | Use case |
|---|---|---|---|
cents | 5000 (int) | 5000 | Already in minor units |
dollars_to_cents | "50.00" (string) | 5000 | Dollar amounts → cents |
upper | "usd" | "USD" | Currency normalization |
lower | "CHARGE" | "charge" | Type normalization |
Value mapping
Section titled “Value mapping”Map upstream enum values to your canonical types:
"map": { "payment": "charge", "refund": "refund", "*": "adjustment" }The * wildcard catches any value not explicitly mapped.
Pagination
Section titled “Pagination”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 field | Description | Required |
|---|---|---|
strategy | "cursor" or "offset" | yes |
param | Query param name for cursor/offset value | yes |
page_size_param | Query param name for page size | yes |
page_size | Items per page | no (default: 100) |
next_cursor_path | JSONPath to next cursor value (cursor only) | cursor: yes |
has_more_path | JSONPath to boolean “has more” flag | no (inferred from page size) |
If has_more_path is omitted, pagination stops when a page returns fewer items than page_size.
Safety guards:
--max-pagescaps the number of pages fetched (default: 100)--max-itemscaps 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=trueis treated as an error, not silent truncation
Options
Section titled “Options”| Flag | Description | Default |
|---|---|---|
--url | API endpoint (HTTPS only) | required |
--auth | Auth method | none |
--map | Path to mapping JSON | required |
--from / --to | Date range (YYYY-MM-DD) | required |
--out | Output CSV path | stdout |
--sample | Print raw JSON response and exit | off |
--save-raw | Save raw JSON to file for audit | off |
--timeout | Request timeout in seconds | 15 |
--max-items | Safety cap on total items | 10,000 |
--max-pages | Safety cap on pages fetched | 100 |
--quiet | Suppress progress messages | off |
--fingerprint | Write signed request fingerprint JSON | off |
Request fingerprinting
Section titled “Request fingerprinting”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.
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.jsonThe fingerprint is a SignedEnvelope (same format as vgrid sign) containing:
| Field | Description |
|---|---|
request.url | Full URL with query params (no secrets — tokens go in headers) |
request.auth_method | Auth flag as passed (e.g. bearer-env:VENDOR_TOKEN), not the secret |
request.from / to | Date range |
request.pages_fetched | Number of pages fetched (useful for detecting truncation) |
mapping.path | Mapping file path |
mapping.blake3 | BLAKE3 hash of the mapping file |
output.row_count | Total rows written |
output.csv_blake3 | BLAKE3 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).
Explore first, then map
Section titled “Explore first, then map”Use --sample to inspect the raw API response before writing a mapping:
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