CSV upload
This recipe shows how to upload CSV files for batch transaction ingestion. FVI accepts CSV files up to 1 GB via a two-step upload (initiate then complete), with automatic header detection, column type inference, and validation against your tenant's source configuration.
Endpoints
POST /v1/ingestion/csv/initiate # Initiate a CSV upload session
POST /v1/ingestion/csv/{id}/chunk # Upload a chunk of CSV data
POST /v1/ingestion/csv/{id}/complete # Finalize the upload and trigger ingestion
Prerequisites
- A valid
tenantIdand bearer JWT. - The user must have the
tenant_adminortenant_data_engineerrole. - A pre-configured source definition (under Settings → Data sources → Sources) that matches the CSV format.
Step 1: Initiate
curl -X POST https://api.falconveritas.com/v1/ingestion/csv/initiate \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"source_id": "src-jpmorgan",
"filename": "jpmorgan-2026-08-22.csv",
"size_bytes": 52428800,
"content_type": "text/csv",
"delimiter": ",",
"has_header": true,
"encoding": "utf-8",
"date_format": "ISO8601",
"idempotency_key": "csv-init-fvi-$RANDOM"
}'
Response
{
"upload_id": "upload-001",
"source_id": "src-jpmorgan",
"chunk_size_bytes": 10485760,
"total_chunks": 5,
"expires_at": "2026-08-22T20:00:00Z",
"upload_url": "https://api.falconveritas.com/v1/ingestion/csv/upload-001/chunk"
}
Step 2: Upload chunks
curl -X POST https://api.falconveritas.com/v1/ingestion/csv/upload-001/chunk \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/octet-stream" \
--data-binary @chunk-001.csv
Step 3: Complete
curl -X POST https://api.falconveritas.com/v1/ingestion/csv/upload-001/complete \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"expected_chunks": 5,
"trigger_ingestion": true,
"idempotency_key": "csv-complete-fvi-$RANDOM"
}'
Response
{
"upload_id": "upload-001",
"status": "completed",
"rows_parsed": 124567,
"rows_valid": 124500,
"rows_invalid": 67,
"validation_errors": [
{"row": 1234, "column": "amount", "error": "invalid_number"},
{"row": 5678, "column": "currency", "error": "unknown_currency"}
],
"ingestion_run_id": "ingest-001",
"triggered_at": "2026-08-22T17:30:00Z"
}
Validation rules
FVI's CSV validator applies the following checks:
- Header match — first row must match the configured source's expected headers. FVI supports header auto-detection if exact match fails.
- Required columns — all required columns must be present and non-null.
- Type validation — numeric columns are validated against the configured regex; date columns are parsed against the configured date format.
- Reference data — counterparty codes, currencies, and GL accounts are validated against the FVI reference data.
- Duplicate detection — FVI detects duplicate transactions based on the configured uniqueness key.
Invalid rows are written to a quarantine table for review and excluded from the ingestion run. The ingestion run proceeds with valid rows; you can re-ingest the quarantined rows after correction.
Use cases
- Bank statement upload — daily upload of bank-provided CSV statements.
- Card processor export — upload card transaction exports from your processor.
- ERP export — upload SAP, Oracle, or NetSuite GL extract CSVs.
- Manual data entry — upload spreadsheets from teams without API access.
Code samples
TypeScript
import { FVI } from '@falconveritas/sdk';
import * as fs from 'fs';
const fvi = new FVI({
baseUrl: 'https://api.falconveritas.com',
bearer: process.env.FVI_JWT!,
tenantId: process.env.FVI_TENANT_ID!,
});
const initiate = await fvi.ingestion.csv.initiate({
sourceId: 'src-jpmorgan',
filename: 'jpmorgan-2026-08-22.csv',
sizeBytes: 52428800,
hasHeader: true,
idempotencyKey: `csv-init-fvi-${Date.now()}`,
});
const file = fs.readFileSync('jpmorgan-2026-08-22.csv');
const chunkSize = initiate.chunk_size_bytes;
for (let i = 0; i < file.length; i += chunkSize) {
const chunk = file.slice(i, i + chunkSize);
await fvi.ingestion.csv.uploadChunk(initiate.upload_id, chunk);
}
const completion = await fvi.ingestion.csv.complete(initiate.upload_id, {
expectedChunks: initiate.total_chunks,
triggerIngestion: true,
idempotencyKey: `csv-complete-fvi-${Date.now()}`,
});
console.log(`Ingested ${completion.rows_valid} rows, ${completion.rows_invalid} quarantined`);
Python
from falconveritas import FVI
client = FVI(
bearer=os.environ["FVI_JWT"],
tenant_id=os.environ["FVI_TENANT_ID"],
)
initiate = client.ingestion.csv.initiate(
source_id="src-jpmorgan",
filename="jpmorgan-2026-08-22.csv",
size_bytes=52428800,
has_header=True,
idempotency_key=f"csv-init-fvi-{int(time.time())}",
)
with open("jpmorgan-2026-08-22.csv", "rb") as f:
for i in range(initiate.total_chunks):
chunk = f.read(initiate.chunk_size_bytes)
if not chunk:
break
client.ingestion.csv.upload_chunk(initiate.upload_id, chunk)
completion = client.ingestion.csv.complete(
initiate.upload_id,
expected_chunks=initiate.total_chunks,
trigger_ingestion=True,
idempotency_key=f"csv-complete-fvi-{int(time.time())}",
)
print(f"Ingested {completion.rows_valid} rows, {completion.rows_invalid} quarantined")
Related articles
Last updated: 2026-08-22 · Page owner: Documentation · Source: src/lib/api/ingestion.ts.