KasarKasar Docs
API Reference

Duplicates

Detect and merge duplicate records. Scan for matches by email, phone, LinkedIn, or name and consolidate them into a single master record.

The duplicates endpoint provides two operations: detecting potential duplicate records and merging confirmed duplicates. Both operations require admin privileges.

Both actions on this endpoint are restricted to admin users. Non-admin tokens receive a 400 response with code PERMISSION_DENIED.

Detect Duplicates

POST /api/v1/duplicates

Scans records for potential duplicates based on matching criteria.

Request Body

{
  "action": "detect",
  "object_name": "contacts",
  "fields": ["email", "phone"],
  "limit": 100
}
FieldTypeRequiredDefaultDescription
actionstringNodetectAny value other than "merge" performs a detect.
object_namestringYesThe object to scan (e.g. contacts, companies)
fieldsstring[]NoinferredField names to compare for matching (e.g. email, phone, name). When omitted, the engine infers candidate fields from the object's metadata.
limitintegerNo50Maximum number of duplicate groups to return. Capped at 200.

Matching Rules

The detection engine loads up to 5000 records and compares them pairwise on the chosen fields. Each comparison is an exact, case-insensitive string match on the trimmed field value (empty/null values are skipped).

When fields is omitted, candidate fields are inferred from the object's metadata:

Inferred WhenField Considered
Field type is EMAILthe email field
Field type is PHONEthe phone field
Field type is URL and the field name contains linkedinthe LinkedIn URL field
Field type is TEXT and the field name is name, full_name, or company_namethe name field

If no candidates are inferred, the engine falls back to comparing the name field.

Example Request

curl -X POST "https://kasar.app/api/v1/duplicates" \
  -H "Authorization: Bearer ksr_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "detect",
    "object_name": "contacts",
    "fields": ["email", "name"],
    "limit": 50
  }'

Response

{
  "object_name": "contacts",
  "duplicate_groups": [
    {
      "records": [
        {
          "id": "550e8400-...",
          "first_name": "Alice",
          "last_name": "Martin",
          "email": "alice@example.com",
          "created_at": "2025-01-15T10:30:00Z"
        },
        {
          "id": "660f9511-...",
          "first_name": "Alice",
          "last_name": "Martin",
          "email": "alice@example.com",
          "created_at": "2025-02-20T14:00:00Z"
        }
      ],
      "match_reason": "email: \"alice@example.com\""
    }
  ],
  "total_groups": 1,
  "records_scanned": 3420
}
FieldTypeDescription
object_namestringThe object that was scanned
duplicate_groupsobject[]Array of groups, each containing matching records
duplicate_groups[].recordsobject[]The records that match each other
duplicate_groups[].match_reasonstringThe field(s) and value(s) that triggered the match, formatted as field: "value". Multiple reasons are comma-separated.
total_groupsintegerTotal number of duplicate groups found
records_scannedintegerNumber of records that were loaded and scanned

Merge Duplicates

POST /api/v1/duplicates

Merges one or more duplicate records into a master record. All relations (BELONGS_TO_ONE foreign keys and MANY_TO_MANY junction entries) are re-linked to the master record before the duplicates are deleted.

Request Body

{
  "action": "merge",
  "object_name": "contacts",
  "master_id": "550e8400-...",
  "duplicate_ids": ["660f9511-..."],
  "merge_strategy": "fill_blanks"
}
FieldTypeRequiredDefaultDescription
actionstringYesMust be "merge"
object_namestringYesThe object containing the records
master_idUUIDYesThe record to keep as the canonical version
duplicate_idsUUID[]YesRecords to merge into the master and delete
merge_strategystringNokeep_masterHow to handle conflicting field values

Merge Strategies

StrategyDescription
keep_masterKeep all scalar field values from the master record. Duplicate values are discarded. This is the default.
fill_blanksFor each scalar field, if the master has a blank/null value, fill it with the value from a duplicate (the first duplicate that has a non-empty value wins). Non-blank master fields are preserved. Relations, junctions, and compound fields (EMAILS/PHONES) are never copied by this strategy — they are transferred by the merge engine in both strategies.

Example Request

curl -X POST "https://kasar.app/api/v1/duplicates" \
  -H "Authorization: Bearer ksr_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "merge",
    "object_name": "contacts",
    "master_id": "550e8400-e29b-41d4-a716-446655440000",
    "duplicate_ids": ["660f9511-f3ac-52e5-b827-557766551111"],
    "merge_strategy": "fill_blanks"
  }'

Response

{
  "master_id": "550e8400-e29b-41d4-a716-446655440000",
  "duplicates_deleted": 1,
  "updated_relations": 4,
  "strategy": "fill_blanks"
}
FieldTypeDescription
master_idUUIDThe ID of the surviving master record
duplicates_deletedintegerNumber of duplicate records that were deleted
updated_relationsintegerNumber of relation/junction links that were re-pointed to the master
strategystringThe merge strategy that was applied (keep_master by default)

What Happens During a Merge

  1. Field values are resolved according to the chosen strategy.
  2. BELONGS_TO_ONE relations: Any records pointing to a duplicate via a foreign key are updated to point to the master instead.
  3. MANY_TO_MANY junctions: Junction table entries referencing duplicates are re-linked to the master. If a junction already exists for the master, the duplicate entry is removed to avoid conflicts.
  4. Duplicate records are permanently deleted.

The merge operation is atomic. If any step fails, the entire operation is rolled back and no data is modified.

Error Responses

All handler errors are returned with HTTP status 400. A missing or invalid Bearer token is rejected earlier with 401 UNAUTHORIZED.

StatusCodeDescription
401UNAUTHORIZEDInvalid or missing API token
400PERMISSION_DENIEDToken is not an admin, or lacks delete permission on a duplicate record
400INVALID_OBJECTThe object name does not exist
400MISSING_REQUIRED_FIELDmaster_id or duplicate_ids is missing (merge)
400RECORD_NOT_FOUNDThe master record was not found during a fill_blanks merge
400MERGE_FAILEDThe merge operation failed
400WRITE_FAILEDAn unexpected error occurred while writing

On this page