Load Fictix data into your app
For a product that already calls a provider API or SDK, use Connect your software and its provider-specific request directory. This page covers loading canonical data and exports.
This guide covers ingestion design for products that want Fictix data inside their own database.
Start by creating or selecting the company in the Fictix
Console, then copy its server-side runtime setup
from Connect to your builder. This guide assumes you have a project slug and
project X-API-Key; it does not require a shared admin key.
Choose JSON or CSV
Use JSON when:
- you control the loader
- nested lines/applications matter
- you want full record fidelity
- you need lineage fields
Use CSV when:
- your product already has CSV importers
- a user needs spreadsheet review
- a batch pipeline expects files
JSON loader pattern
- Get record counts from runtime info or job status.
- Determine load order.
- Page each entity.
- Upsert into your database.
- Store Fictix lineage.
- Reconcile counts.
Pseudocode:
/api/v1/orgs/{slug}/data/{entity}import requests
def load_entity(origin: str, slug: str, api_key: str, entity: str):
offset = 0
limit = 500
total = None
rows = []
seen_ids = set()
while total is None or offset < total:
response = requests.get(
f"{origin}/api/v1/orgs/{slug}/data/{entity}",
headers={"X-API-Key": api_key},
params={"limit": limit, "offset": offset},
timeout=30,
)
response.raise_for_status()
page = response.json()
data = page.get("data")
current_total = page.get("count")
if not isinstance(data, list) or type(current_total) is not int or current_total < 0:
raise ValueError("Expected an entity page with data and a non-negative count")
if "offset" in page and page["offset"] != offset:
raise ValueError(f"Requested offset {offset}, received {page['offset']}")
if len(data) > limit:
raise ValueError(f"Received {len(data)} rows for limit {limit}")
if total is not None and current_total != total:
raise ValueError(f"Entity count changed during pagination: {total} to {current_total}")
total = current_total
if len(rows) + len(data) > total:
raise ValueError(f"Fetched more than the reported entity count {total}")
for row in data:
record_id = row.get("id") if isinstance(row, dict) else None
if record_id is None or not str(record_id).strip():
raise ValueError(f"Record at offset {offset} has no id")
record_id = str(record_id)
if record_id in seen_ids:
raise ValueError(f"Duplicate record id across pages: {record_id}")
seen_ids.add(record_id)
rows.append(row)
if not data and offset < total:
raise ValueError(f"Pagination stopped at {offset} before count {total}")
for row in data:
upsert_row(entity, row, lineage=page.get("lineage"))
offset += len(data)
if len(rows) != total:
raise ValueError(f"Fetched {len(rows)} rows, but count is {total}")
return len(rows)
CSV loader pattern
Export one entity:
/api/v1/orgs/{slug}/export/{entity}?format=csvcurl "$FICTIX_API_ORIGIN/api/v1/orgs/$FICTIX_PROJECT_SLUG/export/customers?format=csv" \
-H "X-API-Key: $FICTIX_PROJECT_API_KEY" \
-o customers.csv
Then run your import:
your-importer customers.csv --entity customers
For accounting workflows, prefer normalized line files when using CSV bundles from runtime export packs.
Mapping notes
Customers
Map:
idto external idnameordisplay_nameto customer display nameemailto primary email- address fields to billing address
- payment terms to customer settings
Invoices
Map:
- invoice header fields first
- line items second
- customer references after customers are loaded
- item references after items are loaded
Load credit_memos after invoices. Each applied credit memo links to an invoice through invoice_id. Confirm each memo's customer_id and currency match the invoice. Sum amount_applied from credit memos by invoice_id; keep the invoice's amount_paid as cash receipts only.
Validate in integer cents:
applied_credit_memos = sum(amount_applied for credit_memos with invoice_id == invoice.id)
invoice.total - invoice.amount_paid - applied_credit_memos - invoice.balance = 0
Do not fold applied credits into invoice.amount_paid.
Credit memos
The credit_memos entity contains applied accounts receivable credits linked to invoices. Read invoice_id and amount_applied from each memo and group the applied amount by invoice. Load invoices first so the linked invoice is available when importing a memo.
Bills
Load vendors before bills. Validate:
total - amount_paid - balance = 0
Payments
Use canonical names:
customer_payments, notpaymentsvendor_payments, notbill_payments
This prevents downstream double-counting.
Bank transactions
Load bank accounts first. Every bank_account_id should resolve to a bank
account or bank-designated account record.
Journal entries
Validate that each entry balances:
sum(debits) = sum(credits)
Reconciliation after load
After loading, compare:
- Fictix source count
- fetched count
- imported count
- rejected count
- duplicate count
Example reconciliation record:
{
"entity": "invoices",
"fictix_count": 200,
"fetched_count": 200,
"imported_count": 200,
"rejected_count": 0
}
Handling partial loads
Make loaders resumable by storing:
- project slug or job id
- entity
- last offset
- page size
- import checkpoint id
- error detail
Do not restart from scratch unless your upsert semantics are idempotent.
Avoiding synthetic data leakage
Mark imported records:
- source:
fictix - synthetic:
true - demo tenant id
- generated timestamp
Do not send Fictix synthetic emails to real email workflows unless your product has an explicit demo-mode mail sink.
Approved control-plane integrations
/api/v1/demo-jobs
If your backend has approved /api/v1/demo-jobs access, it can create jobs and
use normalized entity pages/exports directly.
Keep that credentialed flow on your server; the Console remains the customer-facing source for organizations, projects, key rotation, billing, and support context.
