Menu

API reference · CRM

Deals

crm · deals · 20 operations

GET/v1/crm/deals

List deals

crm.deals.list

ReadIdempotentSince 2026-11-01

Lists deals newest-moved first, paginated by cursor (limit up to 200). Filter by pipeline_id, status (open by default; won, lost, or all) and owner_id, or get the deals of one person (person_id: as their primary person or among their persons) or one company (company_id); with person_id or company_id the other filters are applied to the page read (pass status all to see every deal of that person or company). Each deal carries what it already stores about its activity: last_activity_at, last_inbound_at, last_outbound_at, next_booking_at and action_state (whose move it is). Archived deals are left out unless include_archived is true; merged-away duplicates never appear. A seller with restricted scope only gets the deals they can see. Those filters apply to the page that was read, so a page can bring fewer than limit (even none) while has_more is true: keep following next_cursor until has_more is false. Use crm.deals.get for a single deal and crm.pipelines.list for the stage names. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their records or fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:read
Permission
View the module
MCP tool
crm_deals_listOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.list".

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

Parameters

limitintegerqueryoptional

How many items this page brings at most (default 20, up to 200).

Default: 20

cursorstringqueryoptional

The next_cursor of the previous page, exactly as it came (only for the same list and filters); without it, the first page.

pipeline_idstringqueryoptional

Only the deals of this pipeline (crm.pipelines.list).

statusenumqueryoptional

open (default), won, lost or all.

Values: open, won, lost, all

owner_idstringqueryoptional

Only the deals of this owner: a member's user id or an AI position's ai-… id.

person_idstringqueryoptional

Only the deals of this person: as their primary person or among their persons.

company_idstringqueryoptional

Only the deals of this company.

include_archivedbooleanqueryoptional

Include archived records (left out by default; merged-away duplicates never come).

Default: false

Response

dataarray<object>
has_moreboolean
next_cursorstring

Errors

Example

curl "https://api.mb-suite.com/v1/crm/deals" \
  -H "Authorization: Bearer $MB_API_KEY"

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 "https://api.mb-suite.com/v1/crm/deals" \
  -H "Authorization: Bearer $MB_TEST_KEY"
GET/v1/crm/deals/{deal_id}

Get one deal

crm.deals.get

ReadIdempotentSince 2026-11-01

Returns a deal with its denormalized company and primary person, stage, status, value, labels and custom fields, and its activity: last_activity_at, last_inbound_at (the lead's last message, email or missed call), last_outbound_at, next_booking_at (the next confirmed meeting) and action_state (whose move it is, since action_since). Not found if the id does not exist or the seller's scope hides it. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:read
Permission
View the module
MCP tool
crm_deals_getOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.get".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

Response

idstring
org_idstring
titlestring
valuenumber

The deal amount, in currency.

currencystring

ISO 4217 code of value.

pipeline_idstring
stage_idstring
statusenum

Values: open, won, lost

created_atnumber
updated_atnumber
stage_moved_atnumber

When the deal last changed stage, epoch ms.

stage_entered_atobject

When the deal first entered each stage it went through: stage id → epoch ms.

probabilitynumber

Win probability 0-100: the stage's, unless probability_overridden.

probability_overriddenboolean

true when the probability was set by hand (closing a deal sets 100 or 0 this way); false when it follows the stage.

expected_close_datenumber | null

Expected close date, epoch ms.

owner_idstring | null

The owner: a member's user id (members.members.list), or an AI position's ai-… id.

owner_namestring | null

The name of the owner, resolved when the record is read.

company_idstring | null
company_namestring | null
countrystring | null

ISO-2 country of the deal (its company's, otherwise its main contact's).

person_idsarray<string>

The deal's persons; the main contact is one of them.

primary_person_idstring | null
primary_person_namestring | null
primary_person_emailstring | null
primary_person_phonestring | null
label_idsarray<string>
custom_fieldsobject
won_atnumber | null
lost_atnumber | null
closed_atnumber | null
status_changed_atnumber | null

When the status last changed (won, lost or reopened), server time, epoch ms. Unlike won_at (the close date chosen by whoever marked it, which can be edited later), nobody edits it. Absent on deals whose status changed before 2026-10-13.

lost_reason_idstring | null

The lost reason (one of the pipeline's lostReasons).

lost_reason_notestring | null
rotting_stateenum | null

fresh, warning (two thirds of the stage's rotting days without moving) or rotting.

Values: fresh, warning, rotting

open_activity_countnumber | null

Pending tasks, calls and meetings of the deal.

overdue_activity_countnumber | null

Of those, the ones already past their due date.

source_channelstring | null

Where the deal came from: meta, google, tiktok, linkedin, whatsapp, web, manual, csv…

source_idstring | null

The lead source (crm.lead_sources.list).

external_lead_idstring | null

The lead id of the ads platform (Meta leadgen id, TikTok lead id).

submitted_atnumber | null

When the person submitted the lead form (platform time), epoch ms.

attributionobject | null
ai_scorenumber | null

The AI score 0-100 (ai_scoring.score).

ai_scoringobject | null
mrrnumber | null

Monthly recurring revenue from the quote's monthly lines, in currency.

line_itemsarray<object> | null

The quote's lines (crm.deals.update_quote), in currency.

total_discountnumber | null
total_discount_typeenum | null

Values: percent, amount

is_starredboolean
is_archivedboolean
archived_atnumber | null
merged_into_idstring | null

Set on a duplicate absorbed by a merge: the deal that kept it (lists never return these).

last_activity_atnumber | null

Last activity on the deal: an activity logged on it (note, task, call, meeting) or a message or email exchanged, epoch ms.

last_inbound_atnumber | null

Last contact FROM the lead (WhatsApp, a direct message, an email or a missed inbound call), epoch ms.

last_outbound_atnumber | null

Last contact TO the lead (a message, an email, a call or a sequence step), epoch ms.

next_booking_atnumber | null

The nearest confirmed meeting ahead, epoch ms; null or absent when there is none.

action_stateenum | null

Whose move it is: waiting_us (the lead wrote last), act_new (never contacted), overdue_task (a task or follow-up is overdue), scheduled (a meeting ahead), waiting_them (we wrote last) or none (closed or no signal). Recomputed on every message, booking, activity and call, and once a day.

Values: waiting_us, act_new, overdue_task, scheduled, waiting_them, none

action_sincenumber | null

Since when the deal is in its action_state, epoch ms; for scheduled, the time of the meeting (it can be in the future).

from_prospect_idstring | null

The inbox prospect this deal is the conversion of (crm.prospects.convert, or crm.deals.create with from_prospect_id): its history was linked to the deal.

linked_from_deal_idstring | null

The previous deal this one follows (a recurring or returning opportunity): set by «Create new deal» on a deal that came back, or by crm.deals.create. Traceability only: nothing was moved or copied from it.

linked_from_prospect_idstring | null

The previous prospect this deal follows (a returning lead), like linked_from_deal_id. Traceability only: unlike from_prospect_id, the deal is not that prospect converted.

created_by_idstring | null

Who created the deal: a member's user id (members.members.list), or an AI position's ai-… id.

created_by_namestring | null

The name of who created the deal, resolved when the record is read.

Errors

Example

curl "https://api.mb-suite.com/v1/crm/deals/{deal_id}" \
  -H "Authorization: Bearer $MB_API_KEY"

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 "https://api.mb-suite.com/v1/crm/deals/{deal_id}" \
  -H "Authorization: Bearer $MB_TEST_KEY"
GET/v1/crm/deals/{deal_id}/events

List the history of a deal

crm.deals.events.list

ReadIdempotentSince 2026-11-01

Returns the changelog of a deal (stage and pipeline changes, value, owner, won/lost/reopened, labels, persons, custom fields), newest first, paginated by cursor. field names the deal field that changed as it is published (stage_id, owner_id, custom_fields.<slug>). metadata.reason carries the reason given when persons were added or removed (crm.deals.persons.add or remove) and the handoff reason of an AI position. A seller with restricted scope gets an empty list for a deal that is not theirs (as in the app). Use it to understand what happened before acting on a deal. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:read
Permission
View the module
MCP tool
crm_deals_events_listOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.events.list".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

limitintegerqueryoptional

How many items this page brings at most (see the maximum and the default in the schema).

Default: 100

cursorstringqueryoptional

The next_cursor of the previous page, exactly as it came (only for the same list and filters); without it, the first page.

Response

dataarray<object>
has_moreboolean
next_cursorstring

Errors

Example

curl "https://api.mb-suite.com/v1/crm/deals/{deal_id}/events" \
  -H "Authorization: Bearer $MB_API_KEY"

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 "https://api.mb-suite.com/v1/crm/deals/{deal_id}/events" \
  -H "Authorization: Bearer $MB_TEST_KEY"
POST/v1/crm/deals

Create a deal

crm.deals.create

WriteSince 2026-11-01

Creates a deal in a pipeline stage (the stage must belong to that pipeline: stage_not_in_pipeline) with an owner (owner_id, a member; without it the deal belongs to the caller), optional company, primary person and other persons, value (0 by default) and currency (the pipeline's by default), labels and custom fields. The primary person is always one of the deal's persons (without primary_person_id, the first of person_ids becomes it). Labels must be deal labels of the organization (label_not_found) and each custom field value must match its field (custom_field_unknown, custom_field_invalid). The probability is the stage's unless you send one (then it is kept as set by hand). A new deal starts with action_state act_new and is recomputed from its persons' history right after. Attribution is inherited from the primary person when they came from paid media. After responding, the activities, conversations and messages already linked to its persons and company are linked to the new deal too; link_existing_activity none skips that. For traceability, linked_from_deal_id or linked_from_prospect_id name the previous deal or prospect this one follows (saved on the deal and on its created event; nothing is copied). from_prospect_id means the deal is that inbox prospect converted (its history comes along and the event says converted): prefer crm.prospects.convert for that, and never send it together with linked_from_prospect_id (prospect_origin_conflict). Every origin must exist in the organization (deal_not_found, prospect_not_found). Requires create_deal. A seller with restricted scope creates only their own deals (owner_id omitted or their own uid), in any pipeline, as on the screen: a deal for someone else is denied with crm_action_denied. They can only link persons and a company they can already see (their own, unassigned, or of a deal of theirs), and only name origins they can see; any other is not found (person_not_found, company_not_found, deal_not_found, prospect_not_found). With from_prospect_id, as in crm.prospects.convert, they cannot convert a prospect that is not theirs when its person or company belongs to someone else and is outside what they can see: 403 prospect_contact_not_visible and no deal is created.

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_createOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.create".
Events it can emit
crm.deal.created

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

Parameters

titlestringbodyrequired

The deal's name, as the board shows it.

pipeline_idstringbodyrequired

The pipeline, by id (crm.pipelines.list).

stage_idstringbodyrequired

A stage of that pipeline, by id (crm.pipelines.get lists them); another pipeline's stage is stage_not_in_pipeline.

valuenumberbodyoptional

The deal amount, in currency (0 by default).

currencystringbodyoptional

ISO 4217 code of value (USD, ARS…); by default the pipeline's currency (crm.pipelines.get).

owner_idstringbodyoptional

The owner, a member's user id (members.members.list or crm.team.list); without it the deal belongs to the caller.

company_idstringbodyoptional

The company, by id (crm.companies.list or crm.search).

primary_person_idstringbodyoptional

The deal's main contact, by id (crm.persons.list, crm.persons.lookup or crm.search). It is always one of the deal's persons; without it, the first of person_ids becomes the main contact.

person_idsarray<string>bodyoptional

The deal's persons, by id; the main contact is added if missing.

probabilitynumberbodyoptional

Win probability 0-100, set by hand; without it, the stage's probability (and it follows the stage when the deal moves).

expected_close_dateintegerbodyoptional

Expected close date, epoch ms.

label_idsarray<string>bodyoptional

Deal labels, by id (crm.labels.list with entity deal).

custom_fieldsobjectbodyoptional

Custom field values by key (the slug of crm.custom_fields.list with entity deal).

source_channelstringbodyoptional

Where the deal came from (meta, google, tiktok, linkedin, whatsapp, web, manual…); inherited from the main contact when they came from paid media.

source_idstringbodyoptional

The lead source, by id (crm.lead_sources.list).

from_prospect_idstringbodyoptional

The inbox prospect this deal is the conversion of: the deal's created event says converted and, with link_existing_activity all, the prospect's history is linked to the deal too. It must exist in this organization and, for a seller with restricted scope, be one she can see (her own, unassigned or an AI position's); otherwise prospect_not_found. Prefer crm.prospects.convert, which also marks the prospect converted.

linked_from_deal_idstringbodyoptional

The previous deal this one follows (a recurring or returning opportunity), for traceability: saved on the new deal and on its created event. Nothing is moved or copied from it. It must exist in this organization and, for a seller with restricted scope, be one she can see; otherwise deal_not_found.

linked_from_prospect_idstringbodyoptional

The previous prospect this deal follows (a returning lead), for traceability, like linked_from_deal_id; otherwise prospect_not_found. Not from_prospect_id, which means the deal IS that prospect converted: send one or the other (prospect_origin_conflict).

link_existing_activityenumbodyoptional

all (default): after responding, the activities, conversations and messages already linked to the deal's persons and company (and to from_prospect_id) are linked to the new deal too, so its timeline shows that history. none: the new deal starts with an empty timeline and nothing else is touched.

Values: all, none

Response

deal_idstring

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "title": "TITLE",
  "pipeline_id": "PIPELINE_ID",
  "stage_id": "STAGE_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/deals" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "title": "TITLE",
  "pipeline_id": "PIPELINE_ID",
  "stage_id": "STAGE_ID"
}'
PATCH/v1/crm/deals/{deal_id}

Update a deal

crm.deals.update

WriteSince 2026-11-01

Updates one or more fields of a deal: title, value, currency, probability, expected close date, owner (member or AI position), company, primary person, persons, labels, custom fields, stage, pipeline or kanban position. person_ids and custom_fields replace the whole list or map: to add or remove persons use crm.deals.persons.add / remove, and to change some custom fields use merge_custom_fields (only the keys sent; null deletes one). Changing the stage or pipeline also requires move_stage / change_pipeline, and the resulting stage must belong to the resulting pipeline (stage_not_in_pipeline; moving to another pipeline needs one of its stages). A probability sent is kept as set by hand (probability_overridden becomes true); without one, a deal that changes stage takes the new stage's probability unless it was set by hand, and probability_overridden false puts it back to its stage's. The primary person is always one of the deal's persons: a person_ids list without it keeps it (change it with primary_person_id, remove it with crm.deals.persons.remove). Labels added must be deal labels of the organization (label_not_found) and custom field values that change must match their field (custom_field_unknown, custom_field_invalid). Editing won_at requires mark_won and a won deal. A stage, pipeline or kanban position equal to the current one is not a change: if nothing else changes, nothing is written or audited and changed comes back false. A seller with restricted scope can only add persons or a company they can already see (their own, unassigned, or of a deal of theirs); any other is not found (person_not_found, company_not_found). Making an AI position (ai-…) the owner starts its first turn right away (it may write to the lead; not when the deal's primary person is marked «Do not contact» or unsubscribed: the AI never writes first to them and waits, with next_run_reason do_not_contact, until they write) and counts as one AI call: outside the app that change needs a dry run and its confirm_token (any other update runs directly), and in the practice organization of test keys it fails with 409 sandbox_external_unavailable and nothing changes. Without AI credits for that first turn in the organization's pool (or with the subscription that pays them blocked), the turn does not call the AI and writes to no one: the deal goes at once to the position's escalation person (or to whoever the lead source's assignment rule picks), and the deal returned already shows that person as its owner. Returns the deal as saved. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

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. In this one, only when: the owner changes to an AI position (owner_id ai-…): its first turn runs right away and it may write to the lead.

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_updateOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.update".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

updatesobjectbodyrequired

The fields to change; the rest stay as they are.

Response

dealobject
changedboolean

false when nothing changed (e.g. the deal was already in that stage): nothing was written and nothing was audited.

Errors

Example

curl -X PATCH "https://api.mb-suite.com/v1/crm/deals/{deal_id}" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "updates": {},
  "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 PATCH "https://api.mb-suite.com/v1/crm/deals/{deal_id}" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "updates": {},
  "dry_run": true
}'
POST/v1/crm/deals/{deal_id}/move

Move a deal to another stage

crm.deals.move

WriteSince 2026-11-01

Moves a deal to a stage (and optionally to another pipeline). Stage ids belong to a pipeline: when moving across pipelines pass both; a stage of another pipeline, or one that does not exist, is stage_not_in_pipeline (400, with the stage ids of the pipeline in details). The deal takes the new stage's probability unless it was set by hand (probability_overridden). This is the same path as dragging the card in the kanban and triggers conversions and sequence triggers. Moving a deal to the stage it is already in changes nothing (changed false: the time in stage is not reset, no history entry, no audit); with a new kanban_order it only reorders the card within its column. To mark a deal won or lost use crm.deals.mark_won / crm.deals.mark_lost instead. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_moveOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.move".
Events it can emit
crm.deal.stage_changed

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

stage_idstringbodyrequired

The stage to move it to: one of the pipeline's stages (crm.pipelines.get).

pipeline_idstringbodyoptional

Another pipeline, to move it there (stage_id must be one of its stages).

kanban_ordernumberbodyoptional

Its position inside the stage column of the board (lower first).

Response

dealobject
changedboolean

false when nothing changed (e.g. the deal was already in that stage): nothing was written and nothing was audited.

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/move" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "stage_id": "STAGE_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/deals/{deal_id}/move" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "stage_id": "STAGE_ID"
}'
POST/v1/crm/deals/{deal_id}/persons

Add persons to a deal

crm.deals.persons.add

WriteIdempotentSince 2026-11-01

Adds 1 to 20 persons to a deal without sending the whole list (crm.deals.update with person_ids replaces it, and two concurrent changes overwrite each other). Persons the deal already has are left as they are (already_linked); a person absorbed by a merge is followed to the record that kept it. If the deal had no primary person, the first one added becomes its primary person, with their name, email and phone, as on the deal page. reason (one line) is saved on each person_added event of the deal's history (metadata.reason) and in the audit. Idempotent: adding persons the deal already has writes nothing. Requires edit_deal. A person that does not exist is person_not_found; a seller with restricted scope can only add persons they can already see (their own, unassigned, or of a deal of theirs), any other is person_not_found too. A deal absorbed by a merge is deal_merged (409). Emits crm.deal.updated. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_persons_addOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.persons.add".
Events it can emit
crm.deal.updated

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

person_idsarray<string>bodyrequired

The persons, by id (1 to 20). A person absorbed by a merge is followed to the record that kept it.

reasonstringbodyoptional

Why, in one line (up to 500 characters): who introduced the person or asked to write to them. Saved on the deal's history events (metadata.reason) and in the audit.

Response

dealobject
addedarray<string>

The persons added (the record that kept a merged-away person).

already_linkedarray<string>

The persons the deal already had (in its list or as its primary person): left as they were.

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/persons" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "person_ids": [
    "PERSON_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/deals/{deal_id}/persons" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "person_ids": [
    "PERSON_ID"
  ]
}'
POST/v1/crm/deals/{deal_id}/persons/remove

Remove persons from a deal

crm.deals.persons.remove

WriteIdempotentSince 2026-11-01

Removes 1 to 20 persons from a deal without sending the whole list. Persons the deal does not have come back in not_linked; a merged-away person is followed to the record that kept it, and an id that is not a person of the organization is person_not_found (an id the deal still lists is removed even if the person no longer exists). For a seller with restricted scope, a person the deal does not have and they cannot see is person_not_found too. Removing the primary person makes the next person in the list the primary one (or leaves the deal without one), as on the deal page. reason (one line) is saved on each person_removed event (metadata.reason; every person that leaves gets one, also a primary person the deal did not list among its persons), on the primary_person_change it causes and in the audit. Idempotent: removing persons the deal does not have writes nothing. Requires edit_deal. A deal absorbed by a merge is deal_merged (409). Emits crm.deal.updated. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_persons_removeOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.persons.remove".
Events it can emit
crm.deal.updated

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

person_idsarray<string>bodyrequired

The persons, by id (1 to 20). A person absorbed by a merge is followed to the record that kept it.

reasonstringbodyoptional

Why, in one line (up to 500 characters): who introduced the person or asked to write to them. Saved on the deal's history events (metadata.reason) and in the audit.

Response

dealobject
removedarray<string>

The persons removed from the deal.

not_linkedarray<string>

The persons the deal did not have: nothing to remove.

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/persons/remove" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "person_ids": [
    "PERSON_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/deals/{deal_id}/persons/remove" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "person_ids": [
    "PERSON_ID"
  ]
}'
POST/v1/crm/deals/{deal_id}/external-lead-id

Set or clear the platform lead id of a deal

crm.deals.set_external_lead_id

WriteSince 2026-11-01

Sets, replaces or clears (null) the external_lead_id: the lead id assigned by the ads platform (Meta leadgen id, TikTok lead id…) used to send offline conversions back and as the dedup key of lead imports. Change it deliberately. Requires edit_deal.

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_set_external_lead_idOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.set_external_lead_id".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

external_lead_idstring | nullbodyrequired

The lead id of the ads platform (Meta leadgen id, TikTok lead id); null clears it.

Response

oktrue

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/external-lead-id" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "external_lead_id": "EXTERNAL_LEAD_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/deals/{deal_id}/external-lead-id" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "external_lead_id": "EXTERNAL_LEAD_ID"
}'
POST/v1/crm/deals/{deal_id}/won

Mark a deal as won

crm.deals.mark_won

WriteSince 2026-11-01

Marks a deal won: status, probability 100, won_at (now, or a past date passed in won_at), action_state none, and optionally moves it to the pipeline's won stage (won_stage_id must be the stage marked as won in its pipeline, from crm.pipelines.get: any other is won_stage_invalid). Triggers conversions and sequence exits. Requires mark_won. Returns the deal as saved. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_mark_wonOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.mark_won".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

won_stage_idstringbodyoptional

The pipeline's won stage (the one with isWon); won_stage_invalid otherwise. Without it, the deal moves to the won stage if the pipeline has one.

won_atintegerbodyoptional

The real close date, epoch ms (never in the future); without it, now.

Response

dealobject

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/won" \
  -H "Authorization: Bearer $MB_API_KEY"

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/deals/{deal_id}/won" \
  -H "Authorization: Bearer $MB_TEST_KEY"
POST/v1/crm/deals/{deal_id}/lost

Mark a deal as lost

crm.deals.mark_lost

WriteSince 2026-11-01

Marks a deal lost with an optional lost reason and note. lost_reason_id must be an active lost reason of the deal's pipeline (crm.pipelines.get lists them): any other is lost_reason_not_found. The deal keeps its stage (where it fell is a reporting fact) and its action_state becomes none. Triggers conversions and sequence exits. Requires mark_lost. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_mark_lostOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.mark_lost".
Events it can emit
crm.deal.lost

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

lost_reason_idstringbodyoptional

One of the active lost reasons of the deal's pipeline (crm.pipelines.get): lost_reason_not_found otherwise.

lost_reason_notestringbodyoptional

Why, in your words (up to 1,000 characters).

Response

dealobject

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/lost" \
  -H "Authorization: Bearer $MB_API_KEY"

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/deals/{deal_id}/lost" \
  -H "Authorization: Bearer $MB_TEST_KEY"
POST/v1/crm/deals/{deal_id}/reopen

Reopen a won or lost deal

crm.deals.reopen

WriteSince 2026-11-01

Reopens a won or lost deal into a stage of its pipeline (any other stage is stage_not_in_pipeline; an open deal is deal_not_closed, 409), clearing won_at, lost_at, closed_at and the lost reason. The deal goes back to the stage's probability (probability_overridden false: closing had set 100 or 0) and its action_state is recomputed from its history right after. Requires reopen_deal. Returns the deal as saved. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_reopenOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.reopen".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

stage_idstringbodyrequired

The stage it reopens in: one of its pipeline's stages.

Response

dealobject

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/reopen" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "stage_id": "STAGE_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/deals/{deal_id}/reopen" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "stage_id": "STAGE_ID"
}'
POST/v1/crm/deals/{deal_id}/archive

Archive or unarchive a deal

crm.deals.archive

WriteIdempotentSince 2026-11-01

Archives (archived: true) or restores (false) a deal. An archived deal keeps its status and stage but leaves every list, forecast and report; this is NOT the trash (see crm.deals.delete). Sequences set to exit when the deal is archived take its enrollments out (off by default). Idempotent: nothing changes if the deal is already in that state. Requires edit_deal. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_archiveOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.archive".
Events it can emit
crm.deal.archived

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

archivedbooleanbodyrequired

true archives it; false brings it back.

Response

dealobject
changedboolean

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/archive" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "archived": 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/deals/{deal_id}/archive" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "archived": true
}'
POST/v1/crm/deals/{deal_id}/star

Star or unstar a deal

crm.deals.set_starred

WriteIdempotentSince 2026-11-01

Marks a deal as starred (a visual flag that enables the 'only starred' filter) or removes the star. Idempotent. Requires edit_deal. With Google Ads, LinkedIn or TikTok, their terms limit it through the API and the MCP: what they do not allow answers 403 platform_terms_restricted, and their fields are left out of the response (the permissions guide lists what each platform allows).

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_set_starredOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.set_starred".

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

starredbooleanbodyrequired

true stars it; false removes the star.

Response

dealobject
changedboolean

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/{deal_id}/star" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "starred": 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/deals/{deal_id}/star" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "starred": true
}'
POST/v1/crm/deals/{deal_id}/quote

Set the quote (line items) of a deal

crm.deals.update_quote

WriteSince 2026-11-01

Replaces the deal's quote: line items (product, unit price, quantity, discount, frequency one_time or monthly, tax) and a global discount. The server recomputes value and mrr from the lines; an empty line_items array clears the quote and keeps the manual value. Requires edit_deal.

Scope
crm:write
Permission
Edit in the module
MCP tool
crm_deals_update_quoteOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.update_quote".
Events it can emit
crm.deal.updated

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

line_itemsarray<object>bodyrequired

The quote's lines (up to 200), in the deal's currency: they replace the ones it had.

total_discountnumberbodyoptional

A discount on the whole quote.

total_discount_typeenumbodyoptional

percent or amount (in the deal's currency).

Values: percent, amount

Response

valuenumber
mrrnumber

Errors

Example

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

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/deals/{deal_id}/quote" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "line_items": []
}'
DELETE/v1/crm/deals/{deal_id}

Delete a deal

crm.deals.delete

IrreversibleSince 2026-11-01

Deletes a deal. It goes to the 30-day trash (owners and admins can restore it from CRM settings → Trash); its activities and conversations are kept and unlinked. Prefer crm.deals.archive when the deal should just leave the lists. Requires delete_deal (a seller with scope only deletes their own).

With an API key, this DELETE runs directly: the record stays 30 days in the trash and the response carries its trash_item_id. With an OAuth token, and through the MCP, it needs dry_run → confirm_token.

Scope
crm:write and “Allow deletions” (crm:delete in OAuth)
Permission
Edit in the module
MCP tool
crm_deals_deleteOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.delete".
Events it can emit
crm.deal.deleted

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

Parameters

deal_idstringpathrequired

The deal, by id (crm.deals.list or crm.search).

Response

oktrue
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 DELETE "https://api.mb-suite.com/v1/crm/deals/{deal_id}" \
  -H "Authorization: Bearer $MB_API_KEY"

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 DELETE "https://api.mb-suite.com/v1/crm/deals/{deal_id}" \
  -H "Authorization: Bearer $MB_TEST_KEY"
POST/v1/crm/deals/bulk/labels

Add or remove labels on many deals

crm.deals.bulk.update_labels

WriteBulk · up to 500Since 2026-11-01

Adds and/or removes labels (ids from crm.labels.list with entity deal) on up to 500 deals at once. A label that is not a deal label of the organization is label_not_found (with details.unknown_ids). Deals that already have exactly those labels are left as they are (unchanged) and ids that do not exist are skipped (not_found). The dry run reads the same deals and names those that would change. Manager-tier only (owner, admin, crm_admin or sales_manager). Bulk: 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
Permission
Edit in the module
MCP tool
crm_deals_bulk_update_labelsOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.bulk.update_labels".

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

Parameters

deal_idsarray<string>bodyrequired

The deals (up to 500), by id (crm.deals.list).

to_addarray<string>bodyoptional

Label ids to add (crm.labels.list with entity deal), up to 20; send to_add, to_remove or both.

to_removearray<string>bodyoptional

Label ids to remove (crm.labels.list with entity deal), up to 20; send to_add, to_remove or both.

Response

updatednumber

How many deals changed.

unchangednumber

How many deals already were that way: not written.

not_foundarray<string>

The ids that do not exist in the organization (or are merged-away duplicates): skipped.

countnumber

Same as updated (kept for older clients).

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/bulk/labels" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_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/deals/bulk/labels" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_ID"
  ],
  "dry_run": true
}'
POST/v1/crm/deals/bulk/reassign

Reassign many deals to one owner

crm.deals.bulk.reassign

WriteBulk · up to 500Since 2026-11-01

Sets the owner of up to 500 deals to a member (new_owner_id, from members.members.list or crm.team.list) and notifies them once. Deals that already belong to that owner are left as they are (unchanged); ids that do not exist in the organization are skipped and come back in not_found; updated counts the reassigned ones. The dry run reads the same deals and names those that would change. Manager-tier only. Bulk: 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
Permission
Edit in the module
MCP tool
crm_deals_bulk_reassignOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.bulk.reassign".

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

Parameters

deal_idsarray<string>bodyrequired

The deals (up to 500), by id (crm.deals.list).

new_owner_idstringbodyrequired

The new owner: a member's user id (members.members.list or crm.team.list).

Response

updatednumber

How many deals changed.

unchangednumber

How many deals already were that way: not written.

not_foundarray<string>

The ids that do not exist in the organization (or are merged-away duplicates): skipped.

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/bulk/reassign" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_ID"
  ],
  "new_owner_id": "NEW_OWNER_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/deals/bulk/reassign" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_ID"
  ],
  "new_owner_id": "NEW_OWNER_ID",
  "dry_run": true
}'
POST/v1/crm/deals/bulk/move

Move many deals to a stage

crm.deals.bulk.move

WriteBulk · up to 500Since 2026-11-01

Moves up to 500 deals to a stage of a pipeline (both ids required; the stage must belong to that pipeline, otherwise stage_not_in_pipeline) with the same effects as moving them one by one (history, conversions, sequence triggers, run in series after responding). Each moved deal takes the stage's probability unless it was set by hand (probability_overridden). Deals already in that stage of that pipeline are left as they are (their time in stage is not reset, no history entry) and counted in unchanged; ids that do not exist are skipped (not_found). The dry run reads the same deals and names those that would move. Manager-tier only. Bulk: 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
Permission
Edit in the module
MCP tool
crm_deals_bulk_moveOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.bulk.move".
Events it can emit
crm.deal.stage_changed

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

Parameters

deal_idsarray<string>bodyrequired

The deals (up to 500), by id (crm.deals.list).

pipeline_idstringbodyrequired

The pipeline they move to (crm.pipelines.list).

stage_idstringbodyrequired

A stage of that pipeline: stage_not_in_pipeline otherwise.

Response

updatednumber

How many deals changed.

unchangednumber

How many deals already were that way: not written.

not_foundarray<string>

The ids that do not exist in the organization (or are merged-away duplicates): skipped.

Errors

Example

curl -X POST "https://api.mb-suite.com/v1/crm/deals/bulk/move" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_ID"
  ],
  "pipeline_id": "PIPELINE_ID",
  "stage_id": "STAGE_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/deals/bulk/move" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_ID"
  ],
  "pipeline_id": "PIPELINE_ID",
  "stage_id": "STAGE_ID",
  "dry_run": true
}'
POST/v1/crm/deals/bulk/delete

Delete many deals

crm.deals.bulk.delete

IrreversibleBulk · up to 500Since 2026-11-01

Deletes up to 500 deals; each one goes to the 30-day trash and can be restored individually (platform.trash.list). deleted counts the deals that existed; the other ids come back in not_found. Manager-tier only, and the matrix must allow delete_deal. 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 · CRM action: delete_deal
MCP tool
crm_deals_bulk_deleteOn the full surface; on the compact one, mb_execute_operation with id: "crm.deals.bulk.delete".
Events it can emit
crm.deal.deleted

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

Parameters

deal_idsarray<string>bodyrequired

The deals (up to 500), by id (crm.deals.list).

Response

deletednumber

How many deals were deleted (each one goes to the 30-day trash).

not_foundarray<string>

The ids that do not exist in the organization: nothing to delete.

trash_item_idsarray<string>

The trash items this deletion left, one per record: the item_id of platform.trash.restore (POST /trash/{item_id}/restore).

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/deals/bulk/delete" \
  -H "Authorization: Bearer $MB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_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/deals/bulk/delete" \
  -H "Authorization: Bearer $MB_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "deal_ids": [
    "DEAL_ID"
  ],
  "dry_run": true
}'