Repair API
Send messy data. Get production-ready data back. In a single call, Repair inspects your records, infers a schema, reconciles equivalent field names, normalizes and safely repairs values, detects and (at high confidence) merges duplicates, flags anomalies, and returns clean records with a complete audit trail of everything it changed.
Endpoint & cost
POST /api/v1/repair
Metering is based on input records: a request with 10 records draws down 10 records from your monthly allowance, no matter how many internal operations run. The synchronous maximum is 500 records per request. Requests that fail validation are never charged.
What one call does
Every request runs this deterministic pipeline (each stage is toggleable):
- Inspect the incoming records and detect their likely structure.
- Detect equivalent field names and map them to canonical fields.
- Infer a typed schema (email, phone, currency, date, and more).
- Normalize and safely repair values (trim, lowercase, parse numbers/dates).
- Validate values and separate unfixable issues from automatic repairs.
- Detect duplicates and merge only the high-confidence ones.
- Detect anomalies and flag records that need a human.
- Return repaired data plus a detailed, per-change audit trail.
Request fields
| Field | Type | Description |
|---|---|---|
| records* | object[] | Messy records to repair (1-500). Field names, casing, and value formats may vary across records. Values must be primitives (no nested objects/arrays). |
| options.normalize | boolean | Normalize and safely repair values. Default true. |
| options.dedupe | boolean | Detect duplicate records. Default true. |
| options.merge_duplicates | boolean | Merge high-confidence duplicates. Low-confidence matches are never merged. Default true. |
| options.detect_anomalies | boolean | Flag unusual or inconsistent records. Default true. |
| options.infer_schema | boolean | Infer a typed schema for the canonical fields. Default true. |
| options.strict | boolean | Only apply high-confidence fixes; send ambiguous cases to review_required. Default false. |
| options.dry_run | boolean | Return proposed changes without modifying the returned dataset. Default false. |
Response
| Field | Type | Description |
|---|---|---|
| data | object[] | The repaired records. In dry-run mode this is the original dataset, unchanged. |
| schema | object | Inferred type, nullability, and confidence for each canonical field. |
| field_mappings | object[] | Which input field names were reconciled into each canonical field, with confidence. |
| changes | object[] | The audit trail: every value change with from, to, action, a specific reason, and confidence. |
| duplicates | object[] | Duplicate groups with action merge (applied) or review (left for a human). |
| anomalies | object[] | Unusual records, flagged (never deleted) with evidence. |
| review_required | object[] | Everything that needs a human: validation failures, low-confidence duplicates, ambiguous fields. |
| summary | object | Counts: records_received/returned, issues_found/fixed, duplicates_found/merged, anomalies_found, review_required. |
Dry run
Set options.dry_run: true to preview a repair without changing the returned dataset. The data array comes back exactly as you sent it, while changes, duplicates, anomalies, and schema describe everything Repair would do. Ideal for building trust and diffing before you commit.
Strict mode
Set options.strict: true to only apply high-confidence fixes. Fields whose type can’t be inferred confidently are left untouched and surfaced in review_required instead of being coerced, and duplicate merging requires a higher confidence threshold. Use strict mode when correctness matters more than automation.
Usage & metering
Repair draws down the same account-wide monthly record allowance as every other capability, at 1 unit per input record. Internal normalize, dedupe, and anomaly work is not billed separately — you pay once for the repair. Usage is shared across all API keys on your account. See Rate limits.
Errors & limits
Repair uses the same structured error format as the rest of the platform (error.code, error.message, request_id). Common cases:
400 invalid_request— empty records, a non-object record, or a nested/non-primitive value.413 payload_too_large— the body exceeds the size cap or more than 500 records.401 unauthorized/invalid_api_key— missing or invalid key.429 usage_limit_exceeded— the request would exceed your monthly record allowance.429 rate_limit_exceeded— too many requests in the burst window.
Internal server errors return a sanitized 500 internal_error and never leak record contents or stack traces. See Errors.
Try it live
Pick a scenario (repair, dry run, or strict) or edit the JSON, then send it against the live engine. No API key required.
POST /api/v1/repair// Send the request to see the live response.
Live demo endpoint · no API key needed · rate-limited · not stored · runs the production engine
Example request
POST /api/v1/repair
Authorization: Bearer zap_live_...
Content-Type: application/json
{
"records": [
{ "Company Name": "ACME, INC.", "Email": " JOHN@ACME.COM ", "Revenue": "$1.2M", "signup": "3/7/26" },
{ "company": "Acme Inc", "email_address": "john@acme.com", "revenue": "1200000", "created": "2026-03-07" }
],
"options": {
"normalize": true,
"dedupe": true,
"merge_duplicates": true,
"detect_anomalies": true,
"infer_schema": true,
"strict": false,
"dry_run": false
}
}Example response
{
"request_id": "req_xxx",
"success": true,
"data": [
{ "company": "ACME, INC.", "email": "john@acme.com", "revenue": 1200000, "signup_date": "2026-03-07" }
],
"schema": {
"company": { "type": "company", "nullable": false, "confidence": 0.9 },
"email": { "type": "email", "nullable": false, "confidence": 1 },
"revenue": { "type": "currency", "nullable": false, "confidence": 1 },
"signup_date": { "type": "date", "nullable": false, "confidence": 1, "format": "YYYY-MM-DD" }
},
"field_mappings": [
{ "inputs": ["Company Name", "company"], "canonical": "company", "confidence": 0.95, "concept": "company" },
{ "inputs": ["Email", "email_address"], "canonical": "email", "confidence": 0.98, "concept": "email" }
],
"changes": [
{ "record": 0, "field": "email", "from": " JOHN@ACME.COM ", "to": "john@acme.com", "action": "normalized", "reason": "email trimmed and lowercased", "confidence": 0.99 },
{ "record": 0, "field": "revenue", "from": "$1.2M", "to": 1200000, "action": "normalized", "reason": "currency value parsed to a numeric amount", "confidence": 0.97 }
],
"duplicates": [
{ "source_records": [0, 1], "result_record": 0, "confidence": 0.97, "reason": "company matches; email matches", "action": "merge" }
],
"anomalies": [],
"review_required": [],
"summary": {
"records_received": 2,
"records_returned": 1,
"issues_found": 6,
"issues_fixed": 6,
"duplicates_found": 1,
"duplicates_merged": 1,
"anomalies_found": 0,
"review_required": 0,
"dry_run": false,
"strict": false
},
"usage": { "records_processed": 2, "credits_used": 2, "credits_remaining": 998 }
}Code examples
curl -X POST https://zapinner.com/api/v1/repair \
-H "Authorization: Bearer $ZAPINNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{ "Company Name": "ACME, INC.", "Email": " JOHN@ACME.COM ", "Revenue": "$1.2M" },
{ "company": "Acme Inc", "email_address": "john@acme.com", "revenue": "1200000" }
]
}'