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

  1. Get record counts from runtime info or job status.
  2. Determine load order.
  3. Page each entity.
  4. Upsert into your database.
  5. Store Fictix lineage.
  6. Reconcile counts.

Pseudocode:

GET/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:

GET/api/v1/orgs/{slug}/export/{entity}?format=csv
curl "$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:

  • id to external id
  • name or display_name to customer display name
  • email to 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, not payments
  • vendor_payments, not bill_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

POST/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.