Skip to main content
ReconLayer’s persistence layer (packages/db/prisma/schema.prisma) is PostgreSQL via Prisma. packages/db owns the schema exclusively — HTTP handlers (apps/api) and the pure matching logic (packages/reconciliation-engine) never execute SQL directly. This page documents the actual tables, enums, and relations as defined in schema.prisma.
Five design rules are baked into this schema (from the schema header comments):
  1. raw_records.payload is jsonb, never edited, never deleted.
  2. match_links.match_reason is human-readable text, not a code.
  3. reconciliation_cases keeps expected, actual, fee, FX, rounding, and unexplained_delta as separate columns — nothing is collapsed into one number.
  4. Every state change is logged in audit_events. Append-only.
  5. Amounts use Decimal(38, 18) — wide enough for 18-decimal stablecoin values without losing fractional cents.

Entity relationship overview

payment_intents (PaymentIntent)

What the customer asks ReconLayer to reconcile.
ColumnTypeNotes
idString @id @default(cuid())
organizationIdStringTenant scope.
externalReferenceString?Client’s business reference.
sourceCurrency / sourceAmountString / Decimal(38,18)
destinationCurrency / destinationAmountString / Decimal(38,18)
paymentTypePaymentType @default(stablecoin)
paymentSubtypeString?
directionPaymentDirection @default(debit)
effectiveDateDateTime?
stablecoin / chainString?
beneficiaryAccount / beneficiaryNameString?
statusPaymentIntentStatus @default(initiated)
references / metadataJson?
createdAt / updatedAtDateTime
Constraints & indexes: @@unique([organizationId, externalReference]) — the idempotency key for the intake endpoint (nullable externalReference values don’t conflict with each other). Indexed on organizationId, (organizationId, status), externalReference, and createdAt. Relations: legs: FlowLeg[], reconciliationCase: ReconciliationCase? (1:1), rawRecords: RawRecord[], auditEvents: AuditEvent[], intentReferences: PaymentIntentReference[].

payment_intent_references (PaymentIntentReference)

Typed identifiers attached to an expectation, used by the matcher to find candidates by provider_transfer_id or tx_hash.
ColumnType
id, organizationId, paymentIntentIdString
type, valueString
createdAtDateTime
Unique on (organizationId, type, value) and (paymentIntentId, type, value). Indexed on organizationId, paymentIntentId, and (organizationId, type, value).

raw_records (RawRecord)

Every payload ingested, untouched, forever.
ColumnTypeNotes
id, organizationIdString
paymentIntentId / importBatchIdString?Set once normalized/matched.
sourceRawSourcefile | api | webhook | manual
sourceTypeImportSourceType?
provider / integrationKeyString?
sourceRefStringprovider transfer id, tx hash, csv row id, etc.
payloadJsonraw blob, never edited
rowNumberInt?
normalizedType / normalizedIdString?
validationStatusRecordValidationStatus?pending | valid | warning | failed
validationErrorsJson?
signature / signatureValidString? / Boolean?HMAC and verification result
receivedAtDateTime @default(now())
r2Url / archivedAtString? / DateTime?R2 archive pointer, set by a background job
Constraints & indexes: @@unique([organizationId, source, sourceRef]) — tenant-scoped idempotent ingestion. Indexed on organizationId, paymentIntentId, importBatchId, receivedAt, (source, receivedAt), sourceType, (organizationId, provider), (organizationId, integrationKey). A GIN index on payload is added via migration SQL for ad-hoc JSONB querying. Relations: paymentIntent: PaymentIntent?, importBatch: ImportBatch?, flowLegs: FlowLeg[] (relation name LegFromRawRecord), matchLinks: MatchLink[], auditEvents: AuditEvent[].

flow_legs (FlowLeg)

Normalized legs where value actually moved.
ColumnTypeNotes
id, organizationIdString
paymentIntentId / rawRecordId / routeGroupIdString?
typeLegTypeprovider_transfer | onchain_transfer | bank_transfer
phaseLegPhase?source | intermediary_in | transfer | intermediary_out | destination
statusLegStatus @default(pending)pending | confirmed | failed | reversed | missing
sequenceInt?
reconciliationScopeLegReconciliationScope @default(required)required | optional | ignored
providerTransferId / txHashString?First-class — commonly queried
chainIdInt?
fromAddress / toAddress / tokenAddressString?
provider / integrationKeyString?
currency / amountString? / Decimal(38,18)?
occurredAtDateTime?
receivedAtDateTime @default(now())
metadataJson?
Indexes: organizationId, paymentIntentId, routeGroupId, providerTransferId, txHash, (paymentIntentId, sequence), (organizationId, provider), (organizationId, integrationKey), (type, status), receivedAt. Relations: paymentIntent: PaymentIntent?, rawRecord: RawRecord? (relation LegFromRawRecord), matchLinks: MatchLink[], legReferences: FlowLegReference[].

flow_leg_references (FlowLegReference)

ColumnType
id, organizationId, flowLegIdString
type, valueString
createdAtDateTime
Unique on (flowLegId, type, value). Indexed on organizationId, flowLegId, and (organizationId, type, value).

reconciliation_cases (ReconciliationCase)

One per PaymentIntent — the truth row.
ColumnTypeNotes
id, organizationIdString
paymentIntentIdString? @uniqueEnforces 1:1 with PaymentIntent.
statusCaseStatus @default(open)open | resolved | archived
reconciliationStatusReconciliationStatus @default(unreconciled)unreconciled | tentatively_reconciled | reconciled
expectedAmount / actualAmountDecimal(38,18)?Kept separate, never collapsed
providerFee, networkFee, developerFee, fxSpread, roundingDeltaDecimal(38,18)?Individual delta components
unexplainedDeltaDecimal(38,18)?The column ops cares about — indexed
exceptionTypeExceptionType?
notesString?
reconciledAt / lastRunAtDateTime?
createdAt / updatedAtDateTime
Indexes: organizationId, (organizationId, status), (organizationId, reconciliationStatus), (organizationId, exceptionType), unexplainedDelta. Relations: paymentIntent: PaymentIntent?, matchLinks: MatchLink[], auditEvents: AuditEvent[].

ExceptionType enum

missing_expected_record   missing_evidence       missing_callback
asset_mismatch             amount_mismatch         fee_variance
fx_variance                unexplained_variance    state_mismatch
timing_delay               settlement_reversed     invalid_signature
duplicate_record           chain_reorg
Explainable links between raw records, flow legs, and cases.
ColumnTypeNotes
id, organizationId, caseId, legId, rawRecordIdString
matchTypeMatchTypeprovider_id | tx_hash | reference_exact | amount_and_time_window | manual_override
matchReasonStringHuman-readable, e.g. “Bridge transfer ID X matched on-chain tx Y via destination_tx_hash”
confidenceMatchConfidence @default(deterministic)deterministic | heuristic | manual
matchedAtDateTime @default(now())
Constraints: @@unique([caseId, legId, rawRecordId]) — idempotent linking (the matcher upserts on this key). Indexed on organizationId, caseId, legId.

reconciliation_rules (ReconciliationRule)

Per-organization matching tolerances and behavior — see Reconciliation Rules for how each field is used.
ColumnType
id, organizationId, nameString
paymentTypePaymentType?
sourceTypeImportSourceType?
isActiveBoolean @default(true)
amountToleranceDecimal(38,18)?
timeWindowMinutes, expectedCompletionMinutes, delayedSettlementThresholdMinutesInt?
sourceSlaMinutes, intermediaryInSlaMinutes, transferSlaMinutes, intermediaryOutSlaMinutes, destinationSlaMinutesInt?
allowProviderIdMatch, allowTxHashMatch, allowReferenceExactMatch, allowAmountAndTimeWindowMatchBoolean @default(true)
requireAllRequiredLegsBoolean @default(true)
metadataJson?
createdAt / updatedAtDateTime
Indexed on organizationId, (organizationId, isActive), (organizationId, paymentType), (organizationId, sourceType).

audit_events (AuditEvent)

Append-only log of everything the system did.
ColumnType
id, organizationIdString
paymentIntentId, caseId, rawRecordId, importBatchIdString?
eventTypeString — e.g. ingest.bridge.received, match.deterministic.linked, case.resolved
actorString — e.g. system:bridge_adapter, system:matcher, user:<id>
payloadJson?
messageString?
occurredAtDateTime @default(now())
Indexed on organizationId, paymentIntentId, caseId, rawRecordId, importBatchId, eventType, occurredAt. Relations to PaymentIntent?, ReconciliationCase?, RawRecord?, ImportBatch?.

Import pipeline tables

import_profiles (ImportProfile)

Saved mapping/parsing rule sets for file onboarding.
ColumnType
id, organizationId, nameString
sourceTypeImportSourceType
provider / integrationKeyString?
fieldMappingsJson
valueMappings / parsingRulesJson?
isActiveBoolean @default(true)
createdAt / updatedAtDateTime
Indexed on organizationId, (organizationId, sourceType), (organizationId, provider), (organizationId, integrationKey). Relation: importBatches: ImportBatch[].

import_batches (ImportBatch)

One uploaded file or import job.
ColumnType
id, organizationIdString
importProfileIdString?
sourceTypeImportSourceType
provider / integrationKeyString?
originalFileName / fileStorageKey / fileHashString
statusImportBatchStatus @default(uploaded)
totalRows, validRows, warningRows, failedRowsInt @default(0)
createdByUserIdString? (Clerk user id)
createdAtDateTime
completedAtDateTime?
@@unique([organizationId, fileHash]). Indexed on organizationId, (organizationId, status), importProfileId, createdByUserId, (organizationId, provider), (organizationId, integrationKey). Relations: importProfile: ImportProfile?, rawRecords: RawRecord[], auditEvents: AuditEvent[]. ImportBatchStatus: uploaded, mapping_required, parsing, validating, processing, completed, failed. ImportSourceType (also used as RawRecord.sourceType and ReconciliationRule.sourceType): client_transfer_report, client_internal_ledger, bank_statement, onchain_report, psp_report, manual.

Operational and platform tables

These tables support API access, integrations, outbound webhooks, org settings, and UI preferences. They are not part of the reconciliation data model but share the same database.
TableModelPurpose
api_keysApiKeyMachine credentials for headless org integrations. Unique prefix and hashedKey.
integrationsIntegrationConfigured provider/bank/chain-indexer/system-of-record connections. kind: provider | bank | chain_indexer | system_of_record. status: disabled | active | deleted.
outbound_webhook_endpointsOutboundWebhookEndpointCustomer-configured webhook destinations with subscribedEvents[] and a signingSecret.
outbound_webhook_eventsOutboundWebhookEventQueued events (pending | delivered | failed | skipped), unique on (organizationId, sourceKey).
outbound_webhook_deliveriesOutboundWebhookDeliveryPer-endpoint delivery attempts with retry state (pending | retry_scheduled | delivered | failed).
organization_settingsOrganizationSettingsPer-workspace defaults: name, region, currency, slaProfile, retention.
organization_roles / organization_member_accessOrganizationRole / OrganizationMemberAccessRBAC roles and member invitations, keyed to Clerk users.
user_preferencesUserPreferencePer-user UI/accessibility defaults (fontSize, density, reducedMotion, highContrast).
idempotency_requestsIdempotencyRequestRequest-level idempotency for client retries, unique on (organizationId, operation, idempotencyKey).

Schema migrations

pnpm db:generate  # Generate the Prisma Client
pnpm db:migrate   # Apply pending migrations
Because core-model (canonical shape types and Zod schemas) is separate from db (Prisma persistence types), the reconciliation engine and most service-layer logic can be tested without a database.

Core Concepts

What each table represents in the reconciliation lifecycle.

Canonical Field Registry

Field-by-field mapping targets for import profiles and adapters.