Menu

API reference · CRM

Imports

crm · imports · 1 operation

POST/v1/crm/imports

Import up to 1,000 persons, companies, prospects or deals (background job)

crm.imports.create

WriteSince 2026-11-01

Imports up to 1000 rows per call into persons, companies, prospects or deals, with the same rules as the CSV import wizard: typed values (dates, numbers, select options by label or synonym, yes/no), duplicates found by email or phone (companies by domain or name) and handled by behavior.deduplication (merge: fill empty fields and add emails and phones, never touching do-not-contact; skip: leave the row out; create_activity or notify: use the existing record as it is), companies linked or created from the person (behavior.company_link_mode), rows without an existing person skipped when person_link_mode is skip_unmatched, deals placed by the pipeline and stage names of the row (or default_pipeline_id/default_stage_id) and kept as historical when won or lost. Each row is an object keyed by field id (person.email, person.first_name, person.last_name, person.phone, person.company_name, company.name, company.domain, deal.title, deal.value, deal.stage, labels, owner, cf:<custom field slug>, custom:<free key>…; the ids as the CRM writes them inside, like person.firstName, work too) or by your own column names with mappings [{column, crm_field}] or a saved template (template_id); common names like email, phone or nombre are recognized on their own, row by row (rows may use different keys; when two keys of a row go to the same field, the first with a value counts). Names typed all in capitals or all in lowercase are capitalized (JUAN PÉREZ → Juan Pérez); a name with its own capitals stays as written. For persons and prospects, by default (behavior.company_link_mode link_or_create) each person also gets a company: the row's company or, without one, one named after a business email domain (never gmail.com-like providers or your own domains); send company_link_mode none to skip it. Send dry_run: true first to get the plan (what is created, merged, skipped or fails, row by row) without writing anything; it is optional. Without dry_run it returns job_id at once: poll platform.jobs.get until done (result.summary has the counters, result.row_errors the rows that failed, result.created_ids the ids of what was created, up to 1,000 of each kind) or subscribe to job.completed. Prospects and deals are attributed to source_id or, without it, to the system source «API». A prospect without an owner column goes to the owner of the existing person it matched when they are a member of the organization, and otherwise is left unassigned. Imported records do not fire crm.person.created or crm.deal.created webhooks. Only the first 500 persons a job creates, in row order, start the sequences triggered by "person created"; the rest are imported without being enrolled. Requires the import_data permission of the CRM (owners, admins, CRM admins and sales managers). At most 5 jobs in progress per organization, imports started from the app's CSV wizard included (409 limit_reached, details.resource jobs).

Instructions for agents

Run it first with dry_run: true and show the person the plan (counts and warnings). Import only after they agree, then check the job until it is done and report the counters and any row errors.

Scope
crm:write
Permission
Edit in the module · CRM action: import_data
MCP tool
crm_imports_createOn the full surface; on the compact one, mb_execute_operation with id: "crm.imports.create".
Events it can emit
job.completed

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

Parameters

rowsarray<object>bodyrequired

Up to 1,000 rows, each an object keyed by field id (person.email, person.first_name…) or by your own column names (with mappings, a template, or names the import recognizes on its own, row by row: email, phone, nombre…).

destinationenumbodyoptional

What the rows become: person, company, prospect or deal (with template_id, the template's).

Values: prospect, deal, person, company

mappingsarray<object>bodyoptional

Your column → field, up to 80; without it, the template's or the keys of the rows.

template_idstringbodyoptional

A saved import template (crm.import_templates.list): its destination, mapping and behavior.

behaviorobjectbodyoptional

What to do with companies, unmatched persons and duplicates; what is left out takes the destination's default (the template's, with template_id).

source_idstringbodyoptional

Prospects and deals: the lead source they are attributed to (crm.lead_sources.list); default the system source «API».

default_pipeline_idstringbodyoptional

Deals: the pipeline of rows without deal.pipeline (crm.pipelines.list).

default_stage_idstringbodyoptional

Deals: the stage of rows without deal.stage (a stage id of that pipeline).

default_currencystringbodyoptional

ISO 4217 code of values without a currency column (default the template's, else USD).

file_namestringbodyoptional

The name the history of each deal or prospect shows («Imported from …»); default «API <date>».

Response

job_idstring
status"queued"
rowsnumber

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/imports" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "rows": []
}'

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/imports" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "rows": []
}'