# Push an order to a CRM

> POST /v1/orders/{id}/pushes. Push an order's records to a connection, keyed on mydatahub_record_id with the MyDataHub property group.

Source: https://emailmovers.com/mydatahub/docs/reference/rest/create-push

`POST /v1/orders/{id}/pushes`

Push an order's records to a connection, keyed on mydatahub_record_id with the MyDataHub property group. dry_run defaults to true and reports what would change. A live push needs approval: a person's confirmation_token (or MCP elicitation), or an agent key whose allowed_tools include push_to_crm and whose allowed_connection_ids include the connection; otherwise 403 confirmation-required. Records only: nothing is ever sent to the contacts. Free.

| Property | Value |
| --- | --- |
| Endpoint | `POST https://mydatahub.emailmovers.com/v1/orders/{id}/pushes` |
| Operation id | `createPush` |
| Cost | Free (0 credits) |
| Access | An API key or an OAuth 2.1 token with the scope `pushes:write`. |
| Idempotency-Key | Optional. Makes a retry safe. |
| MCP tool | [`push_to_crm`](https://emailmovers.com/mydatahub/docs/reference/mcp/push_to_crm.md) |
| Tag | CRM push |

> **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 page describes the contract the API is tested against.

## Request

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `MyDataHub-Version` | header | string | no | The dated API version, pinned per key when left out. Non-breaking changes ship as new dates; the path major changes only for breaking changes, with a 12 month deprecation window. |
| `Idempotency-Key` | header | string | no | Makes the request safe to retry: the same key returns the stored response. Keys are kept for the life of the organisation. |

### Body

| Member | Type | Required | Description |
| --- | --- | --- | --- |
| `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 request

```bash
curl -X POST "https://mydatahub.emailmovers.com/v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/pushes" \
  -H "Authorization: Bearer $MYDATAHUB_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G"
}'
```

## Responses

### 202: The push, or its dry run.

Headers: `RateLimit`, `RateLimit-Policy`, `Location`.

| 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 |  |

#### Example response

```json
{
  "id": "psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5",
  "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
  "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G",
  "dry_run": true,
  "status": "completed",
  "summary": {
    "to_create": 4102,
    "to_update": 0,
    "already_present": 0,
    "skipped": 0,
    "failed": 0
  },
  "property_group": [
    "mydatahub_record_id",
    "licence_expires_at",
    "lawful_basis",
    "article14_status",
    "tps_ctps_screened_at",
    "subscriber_class",
    "suppression_status",
    "compliance_receipt_url"
  ],
  "sample": {
    "mydatahub_record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
    "licence_expires_at": "2027-09-26",
    "lawful_basis": "legitimate_interests_art6_1_f",
    "article14_status": "notified",
    "tps_ctps_screened_at": "2026-09-12",
    "subscriber_class": "ltd",
    "suppression_status": "none",
    "compliance_receipt_url": "https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R"
  },
  "errors": [],
  "created_at": "2026-09-26T10:20:00Z",
  "completed_at": "2026-09-26T10:20:03Z"
}
```

## Errors

Every error is a problem object with a `code` and a `next_step`. Every response carries the `RateLimit` and `RateLimit-Policy` headers; see [Rate limits](https://emailmovers.com/mydatahub/docs/rate-limits.md).

| Status | Problem codes | When |
| --- | --- | --- |
| 400 | [`invalid-request`](https://emailmovers.com/mydatahub/docs/errors/invalid-request.md), [`idempotency-key-required`](https://emailmovers.com/mydatahub/docs/errors/idempotency-key-required.md), [`unsupported-version`](https://emailmovers.com/mydatahub/docs/errors/unsupported-version.md) | The request could not be read, or an Idempotency-Key is missing. |
| 401 | [`authentication-required`](https://emailmovers.com/mydatahub/docs/errors/authentication-required.md), [`account-required`](https://emailmovers.com/mydatahub/docs/errors/account-required.md) | Sign in (OAuth) or send an API key (authentication-required), or create a free account for what the anonymous tier does not cover (account-required). |
| 403 | [`insufficient-scope`](https://emailmovers.com/mydatahub/docs/errors/insufficient-scope.md), [`confirmation-required`](https://emailmovers.com/mydatahub/docs/errors/confirmation-required.md), [`business-details-required`](https://emailmovers.com/mydatahub/docs/errors/business-details-required.md), [`licence-acceptance-required`](https://emailmovers.com/mydatahub/docs/errors/licence-acceptance-required.md), [`dpa-acceptance-required`](https://emailmovers.com/mydatahub/docs/errors/dpa-acceptance-required.md), [`agent-budget-exceeded`](https://emailmovers.com/mydatahub/docs/errors/agent-budget-exceeded.md) | The token lacks a scope (insufficient-scope, with required_scope and a WWW-Authenticate step-up challenge); no person confirmed the spend and no agent key covers it (confirmation-required, with an approval_url); an agent key's per-order cap or monthly budget does not cover it (agent-budget-exceeded); an account made by self-serve sign-up has not yet confirmed its business name and that it is a UK business, which its first order needs (business-details-required); the order needs the Data Licence and Sharing Agreement accepted at the version in force (licence-acceptance-required); or a suppression upload comes before the organisation has accepted the Article 28 data processing agreement in force (dpa-acceptance-required). Nothing is charged. |
| 404 | [`not-found`](https://emailmovers.com/mydatahub/docs/errors/not-found.md) | No such object for this organisation. |
| 409 | [`idempotency-key-reused`](https://emailmovers.com/mydatahub/docs/errors/idempotency-key-reused.md), [`quote-expired`](https://emailmovers.com/mydatahub/docs/errors/quote-expired.md), [`order-not-ready`](https://emailmovers.com/mydatahub/docs/errors/order-not-ready.md), [`order-not-cancellable`](https://emailmovers.com/mydatahub/docs/errors/order-not-cancellable.md) | An Idempotency-Key reused with different parameters, an expired machine quote, or an object in the wrong state. |
| 422 | [`validation-failed`](https://emailmovers.com/mydatahub/docs/errors/validation-failed.md), [`quote-required`](https://emailmovers.com/mydatahub/docs/errors/quote-required.md), [`dataset-unavailable`](https://emailmovers.com/mydatahub/docs/errors/dataset-unavailable.md) | The request is not valid. errors lists every problem with a JSON pointer and did_you_mean. |
| 429 | [`rate-limited`](https://emailmovers.com/mydatahub/docs/errors/rate-limited.md) | Too many requests. Wait for Retry-After. |
| Any other failure | [`internal-error`](https://emailmovers.com/mydatahub/docs/errors/internal-error.md), [`service-unavailable`](https://emailmovers.com/mydatahub/docs/errors/service-unavailable.md), [`tps-screen-expired`](https://emailmovers.com/mydatahub/docs/errors/tps-screen-expired.md) | Something failed on our side (500 internal-error) or the service is briefly unavailable (503 service-unavailable, with Retry-After). Nothing was charged; retry with the same Idempotency-Key. A count, sample, quote or order with telephone in play is also refused with 503 tps-screen-expired, with no Retry-After, while the dataset version's TPS and CTPS screen is more than 28 days old. |
