Skip to main content

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 tenantId and bearer JWT.
  • The user must have the tenant_admin or tenant_data_engineer role.
  • 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:

  1. Header match — first row must match the configured source's expected headers. FVI supports header auto-detection if exact match fails.
  2. Required columns — all required columns must be present and non-null.
  3. Type validation — numeric columns are validated against the configured regex; date columns are parsed against the configured date format.
  4. Reference data — counterparty codes, currencies, and GL accounts are validated against the FVI reference data.
  5. 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")

Last updated: 2026-08-22 · Page owner: Documentation · Source: src/lib/api/ingestion.ts.