Menu

API reference · CRM

Merges

crm · merge · 2 operations

POST/v1/crm/merge/preview

Preview a merge

crm.merge.preview

ReadIdempotentSince 2026-11-01

Dry run of crm.merge.execute for two deals, persons, companies or prospects: the fields where both have a different value (to choose per field, by the names choices takes: job_title, custom_fields.<slug>), how much history will move (activities, conversations, messages, notes), the related company/person duplicates worth merging too, and the full field comparison (each value as the app shows it and as stored). Nothing is written.

Scope
crm:read
Permission
View the module
MCP tool
crm_merge_previewOn the full surface; on the compact one, mb_execute_operation with id: "crm.merge.preview".

Declared cost: up to 14 reads and 0 writes per call (they count against the daily quota).

Parameters

kindenumbodyrequired

What is merged: two deals, persons, companies or prospects.

Values: deal, person, company, prospect

survivor_idstringbodyrequired

The record that remains (it keeps its id and, by default, its values).

loser_idstringbodyrequired

The duplicate absorbed into survivor_id (it stays as a tombstone that points to the survivor).

Response

kindenum

Values: deal, person, company, prospect

survivor_idstring
loser_idstring
conflictsarray<object>

The fields where both records have a different value: choose the side per field in choices of crm.merge.execute (the survivor's by default).

countsobject

How much of the loser's history moves to the survivor.

related_dupsarray<object>

Related records (the companies or persons of the two) that may be duplicates too: pass them in cascades to merge them in the same call.

comparisonarray<object>

Every field of the two records, side by side.

loser_is_won_dealboolean

Deals: the absorbed deal is won; its close stops counting (the survivor keeps its own status).

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/merge/preview" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "kind": "deal",
  "survivor_id": "SURVIVOR_ID",
  "loser_id": "LOSER_ID"
}'

Try it with your test API key

With a test API key the request always goes to your practice organization: example data you can reset.

curl -X POST "https://api.mb-suite.com/v1/crm/merge/preview" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "kind": "deal",
  "survivor_id": "SURVIVOR_ID",
  "loser_id": "LOSER_ID"
}'
POST/v1/crm/merge

Merge two records

crm.merge.execute

IrreversibleSince 2026-11-01

Merges loser_id into survivor_id (same kind: deal, person, company or prospect): the loser's history, links, lists and enrollments move to the survivor, the survivor keeps its values unless choices says otherwise per field (field name → survivor or loser, as crm.merge.preview names them: a field that cannot be chosen for that kind is merge_choice_unknown; arrays are united, empty fields filled), and the loser stays as a tombstone that points to the survivor. A full copy of the loser goes to the trash (not restorable automatically). cascades merges related records with default choices. Requires the delete permission of the kind. Outside the screen it requires a dry run and a confirm_token.

Needs confirmation: first dry_run: true (preview and confirm_token, valid 10 minutes and for one run), then the same body with confirm_token. In a few operations only with certain data: the description says when. Why and how: Dry run and confirm_token.

Scope
crm:write and “Allow deletions” (crm:delete in OAuth)
Permission
Edit in the module
MCP tool
crm_merge_executeOn the full surface; on the compact one, mb_execute_operation with id: "crm.merge.execute".

Declared cost: up to 60 reads and 60 writes per call (they count against the daily quota).

Parameters

kindenumbodyrequired

What is merged: two deals, persons, companies or prospects.

Values: deal, person, company, prospect

survivor_idstringbodyrequired

The record that remains (it keeps its id and, by default, its values).

loser_idstringbodyrequired

The duplicate absorbed into survivor_id (it stays as a tombstone that points to the survivor).

choicesobjectbodyoptional

Per field in conflict, which side wins: survivor (the default) or loser. Keys are the field names crm.merge.preview returns in conflicts (title, first_name, job_title…; snake_case or camelCase) and custom_fields.<slug> for a custom field. A key that is not a field of that kind, or a custom field neither record has, is merge_choice_unknown.

cascadesarray<object>bodyoptional

Related merges to run in the same call with the default choices (the relatedDups of crm.merge.preview: the two companies or persons behind two deals), up to 5.

Response

survivor_idstring
loser_idstring
reanchoredobject
trash_item_idstring

The trash item this deletion left: the item_id of platform.trash.restore (POST /trash/{item_id}/restore). Missing if nothing went to the trash.

trash_item_idsarray<string>

Only when the deletion left more than one copy: all of them, the first being trash_item_id.

trash_expires_atnumber

When the copy leaves the trash for good (epoch ms): 30 days after the deletion.

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/merge" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kind": "deal",
  "survivor_id": "SURVIVOR_ID",
  "loser_id": "LOSER_ID",
  "dry_run": true
}'

Try it with your test API key

With a test API key the request always goes to your practice organization: example data you can reset.

curl -X POST "https://api.mb-suite.com/v1/crm/merge" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kind": "deal",
  "survivor_id": "SURVIVOR_ID",
  "loser_id": "LOSER_ID",
  "dry_run": true
}'