Reconcile Records
data.reconcile_records
Overview
You send two tables, the columns that identify a row, and the rules that decide whether two rows agree. We index both sides on the key, compare only the pairs that share one, and hand back three lists: pairs where every rule passed, pairs where at least one rule failed with both disagreeing values so you can act without re-fetching the rows, and rows with no counterpart. Seven comparison rules are offered — exact, case-folded, normalised text, absolute and relative numeric tolerance, calendar-day equality and null equivalence — and none of them guesses: there is no similarity score here, because a match you cannot explain is not a reconciliation. Keys compare as text with no type coercion, so 1, 1.0 and "1" are three different keys, the same rule data.join_tables publishes.
Knowing two rows met is not the same as knowing they agree, and the difference is where the money is.
Reliability
The same two tables, keys and rules always give the same three lists, on any machine and under any locale, time zone or default charset. Normalised text lower-cases with the root locale and calendar-day equality reads the offset as written, so neither moves with the host. Numeric comparison is exact decimal arithmetic rounded half-up at decimalScale, the same semantics data.validate_totals publishes, so no answer here depends on binary floating point. Conflicts are the one place values come back, because 1234.50 against 1234.05 is a typo and 1234.50 against 9910.00 is a different invoice, and a caller told only that a field differs has to re-fetch both rows; each echoed value stops at 128 code points and says so. Matched and unmatched rows carry indexes only. Work is projected from the bucket histogram, never from left times right: the implementation indexes on the key and never forms a Cartesian product, so refusing on work that will not happen would push you into batching around a limit that does not exist. Fifteen refusals, none retryable unchanged: RECONCILE_ROWS_INVALID and RECONCILE_ROW_LIMIT_EXCEEDED for a malformed table or one above 50000 rows; RECONCILE_KEYS_INVALID for a malformed key list and RECONCILE_KEY_LIMIT_EXCEEDED above eight key mappings, so remove extra blocking columns before retrying; RECONCILE_KEY_NOT_UNIQUE when duplicateKeyPolicy is REFUSE and a bucket holds several rows on one side; RECONCILE_RULES_INVALID, RECONCILE_RULE_UNSUPPORTED for an unknown comparison rule and RECONCILE_RULE_LIMIT_EXCEEDED above 64 rules, so split the comparison or remove unnecessary rules; RECONCILE_VALUE_LENGTH_INVALID when maxValueLength is outside 1 to 512 or a value is wider than the rule declared; RECONCILE_VALUE_NOT_NUMERIC and RECONCILE_VALUE_NOT_DATE when a typed rule meets a value it cannot read, which is a refusal rather than a silent conflict because a parse failure and a real disagreement are different findings; RECONCILE_TOLERANCE_INVALID for a negative tolerance or one sent to a rule that does not read it; RECONCILE_POLICY_UNSUPPORTED for a policy outside its members; RECONCILE_WORK_REFUSED above 200000000 comparison bytes, with both numbers; and RECONCILE_OUTPUT_TOO_LARGE when the report would not fit the inline response ceiling.
Context footprint
Returns indexes for matches and unmatched rows, and for conflicts the failing rule with two values capped at 128 code points each. A successful call costs 3 execution credits.
When to use it
When two systems hold what should be the same records and you need the disagreements, row by row and field by field.
When the two sides share no reliable identifier — there is nothing here to block on.
Scope
Non-goals
- Does not fuzzy-match keys or values
- Does not coerce types when comparing keys
- Does not merge, correct or choose between two disagreeing rows
- Does not score how similar two rows are
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| left | array | Yes | Bounded array of 1 through 50000 left-side row objects keyed by column name. |
| right | array | Yes | Bounded array of 1 through 50000 right-side row objects keyed by column name. |
| keys | array | Yes | Bounded array of 1 through 8 closed blocking-key pairs compared as text with no type coercion. |
| rules | array | Yes | Bounded array of 1 through 64 closed comparison rules with left, right, kind and optional tolerance. |
| duplicateKeyPolicy | string | No | How crowded key buckets are handled; omit it for PAIR_BY_INPUT_ORDER. |
| unmatchedPolicy | string | No | Which unmatched rows are listed; omit it for REPORT. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| matches | array | Yes | Bounded array of 0 through 50000 closed matching pairs with leftIndex, rightIndex and the blocking key. |
| conflicts | array | Yes | Bounded array of 0 through 50000 closed conflicting pairs with leftIndex, rightIndex and the failing rule. |
| unmatched | array | Yes | Bounded array of 0 through 50000 closed unmatched rows naming the side and the original index. |
| summary | object | Yes | Closed complete counts even when unmatchedPolicy narrows the listed rows. |
| work | object | Yes | Closed admission decision in comparison bytes with projected size and the budget ceiling. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"data.reconcile_records","input":{"left":[{"id":"A1","amount":"1234.50"}],"right":[{"id":"A1","amount":"1234.05"}],"keys":[{"left":"id"}],"rules":[{"left":"amount","rule":"NUMERIC_TOLERANCE","tolerance":0.01}]}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("data.reconcile_records", {"right":[{"id":"A1","amount":"1234.05"}],"keys":[{"left":"id"}],"left":[{"id":"A1","amount":"1234.50"}],"rules":[{"tolerance":0.01,"left":"amount","rule":"NUMERIC_TOLERANCE"}]});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| RECONCILE_ROWS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_ROW_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| RECONCILE_KEYS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_KEY_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| RECONCILE_KEY_NOT_UNIQUE | 412 | No | The input is valid but cannot be processed as-is. |
| RECONCILE_RULES_INVALID | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_RULE_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| RECONCILE_RULE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| RECONCILE_VALUE_LENGTH_INVALID | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_VALUE_NOT_NUMERIC | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_VALUE_NOT_DATE | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_TOLERANCE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| RECONCILE_POLICY_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| RECONCILE_WORK_REFUSED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| RECONCILE_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not fuzzy-match keys or values
- Does not coerce types when comparing keys
- Does not merge, correct or choose between two disagreeing rows
- Does not score how similar two rows are
- Matching records that have no shared identifier
- Deduplicating one table — data.detect_duplicates does that
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "data.reconcile_records",
"input": {
"right": [
{
"id": "A1",
"amount": "1234.05"
}
],
"keys": [
{
"left": "id"
}
],
"left": [
{
"id": "A1",
"amount": "1234.50"
}
],
"rules": [
{
"tolerance": 0.01,
"left": "amount",
"rule": "NUMERIC_TOLERANCE"
}
]
}
}Examples
Two ledgers where one amount disagrees
{
"right": [
{
"id": "A1",
"amount": "1234.05"
}
],
"keys": [
{
"left": "id"
}
],
"left": [
{
"id": "A1",
"amount": "1234.50"
}
],
"rules": [
{
"tolerance": 0.01,
"left": "amount",
"rule": "NUMERIC_TOLERANCE"
}
]
}{
"work": {
"projected": 1026,
"budget": 200000000
},
"conflicts": [
{
"rightIndex": 0,
"key": [
"A1"
],
"failures": [
{
"rightField": "amount",
"leftValue": "1234.50",
"delta": "0.45",
"truncated": false,
"leftField": "amount",
"rightValue": "1234.05",
"rule": "NUMERIC_TOLERANCE"
}
],
"leftIndex": 0
}
],
"unmatched": [],
"matches": [],
"summary": {
"ambiguousKeys": 0,
"truncated": false,
"conflictCount": 1,
"unmatchedLeft": 0,
"unmatchedRight": 0,
"leftCount": 1,
"matchCount": 0,
"rightCount": 1,
"candidatePairs": 1
}
}A matched status on both systems
{
"right": [
{
"status": "paid",
"id": "A1"
}
],
"keys": [
{
"left": "id"
}
],
"left": [
{
"status": "paid",
"id": "A1"
}
],
"rules": [
{
"left": "status",
"rule": "EXACT"
}
]
}{
"work": {
"projected": 1026,
"budget": 200000000
},
"conflicts": [],
"unmatched": [],
"matches": [
{
"leftIndex": 0,
"key": [
"A1"
],
"rightIndex": 0
}
],
"summary": {
"ambiguousKeys": 0,
"truncated": false,
"conflictCount": 0,
"unmatchedLeft": 0,
"unmatchedRight": 0,
"leftCount": 1,
"matchCount": 1,
"rightCount": 1,
"candidatePairs": 1
}
}Recommended use cases
- Comparing a bank statement against a ledger export
- Checking a migrated table against its source
- Finding which invoice amounts disagree between two systems
Avoid for
- Matching records that have no shared identifier
- Deduplicating one table — data.detect_duplicates does that
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.