# Push an order to a CRM

> push_to_crm: Pushes an order's records to a connected CRM such as HubSpot or Salesforce, keyed on mydatahub_record_id, as record operations only: nothing is ever sent to the contacts.

Source: https://emailmovers.com/mydatahub/docs/reference/mcp/push_to_crm

Pushes an order's records to a connected CRM such as HubSpot or Salesforce, keyed on mydatahub_record_id, as record operations only: nothing is ever sent to the contacts. dry_run defaults to true and reports what would be created, updated or skipped; a live push needs the user's confirmation or an agent key allowed push_to_crm on that connection, or it returns confirmation-required. Free; it needs pushes:write and a connection made in the app. Show the dry run to the user before a live push. Lawful basis is UK GDPR Article 6(1)(f) legitimate interests; email audiences include corporate subscribers only by default (PECR), so sole traders and ordinary partnerships are left out unless the toggle is changed.

| Property | Value |
| --- | --- |
| Tool name | `push_to_crm` |
| Position in tools/list | 17 of 22 |
| Cost | Free |
| Scope | `pushes:write` |
| Annotations | readOnlyHint false, destructiveHint false, idempotentHint true, openWorldHint true |
| Kind | Write; idempotent, not destructive, open world |
| REST equivalent | [`POST /v1/orders/{id}/pushes`](https://emailmovers.com/mydatahub/docs/reference/rest/create-push.md) |

> **Not yet live.** MyDataHub v2 is built and tested locally and is not yet live. The v2 hosts open at launch, date to confirm. This is what `tools/list` returns from https://mydatahub.emailmovers.com/mcp once v2 launches.

## Input

The schema sets `additionalProperties` to false, so a member that is not listed is refused.

| Member | Type | Required | Description |
| --- | --- | --- | --- |
| `order_id` | string | yes | The order whose records to push. |
| `connection_id` | string | yes | Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G. |
| `dry_run` | boolean | no | Default true: report what would change without writing. A live push needs approval: a person's confirmation, or an agent key allowed push_to_crm on this connection. Default `true`. |
| `update_existing` | boolean | no | Update records already in the CRM, matched on mydatahub_record_id. Default true. Default `true`. |
| `confirmation_token` | string | no | The token the approval page on mydatahub.emailmovers.com returns once the signed-in person approves. It is bound to that person (their sub), this spend (the machine quote_id, or the enrichment or push) and an expiry no later than the quote's, and it is redeemable once. |

## Example call

The `params` of a `tools/call` request:

```json
{
  "name": "push_to_crm",
  "arguments": {
    "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G",
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E"
  }
}
```

## Output

The result carries `structuredContent` that matches this schema, and one line of text that leads with the number.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5. |
| `order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `connection_id` | string | yes | Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G. |
| `dry_run` | boolean | yes |  |
| `status` | enum: `queued`, `running`, `completed`, `partial`, `failed` | yes |  |
| `summary` | object | yes |  |
| `summary.to_create` | integer (0 or more) | yes |  |
| `summary.to_update` | integer (0 or more) | yes |  |
| `summary.already_present` | integer (0 or more) | yes |  |
| `summary.skipped` | integer (0 or more) | yes |  |
| `summary.failed` | integer (0 or more) | yes |  |
| `property_group` | array of enum: `mydatahub_record_id`, `licence_expires_at`, `lawful_basis`, `article14_status`, `tps_ctps_screened_at`, `subscriber_class`, `suppression_status`, `compliance_receipt_url` | yes | The MyDataHub property group written on every record (CrmPropertyGroup). |
| `sample` | `CrmPropertyGroup` or null | yes | On a dry run: the property group the first record would carry. |
| `sample.mydatahub_record_id` | string | yes | The record id: a Salesforce External ID, a HubSpot unique property, a Pipedrive custom field. |
| `sample.licence_expires_at` | string (date) or null | yes |  |
| `sample.lawful_basis` | enum: `legitimate_interests_art6_1_f` | yes |  |
| `sample.article14_status` | enum: `notified`, `publicly_notified_disproportionate_effort`, `pending_within_window` | yes |  |
| `sample.tps_ctps_screened_at` | string (date) or null | yes |  |
| `sample.subscriber_class` | enum: `ltd`, `plc`, `llp`, `scottish_partnership`, `public_body`, `charity_incorporated`, `sole_trader`, `ordinary_partnership`, and 1 more | yes |  |
| `sample.suppression_status` | enum: `none`, `objected_to_emailmovers`, `objected_to_licensee`, `deceased`, `left_company` | yes | Kept current: an objection reaching the record updates it in the CRM. |
| `sample.compliance_receipt_url` | string (uri) | yes |  |
| `errors` | array of object | yes |  |
| `errors[].record_id` | string | yes |  |
| `errors[].message` | string | yes |  |
| `created_at` | string (date-time) | yes |  |
| `completed_at` | string (date-time) or null | yes |  |

## Errors

A failed call returns `isError` true. The text content leads with the sentence a person acts on, then the whole problem as JSON, with `code`, `detail`, `next_step` and `did_you_mean`. A 401 and a step-up 403 are answered at HTTP level so the client can start authorisation. See [Errors](https://emailmovers.com/mydatahub/docs/errors.md).
