> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CDP to ERP object sync: push segments into NetSuite, SAP, Workday, or QuickBooks as native objects

> Configure a segment-to-object mapping for NetSuite, SAP S/4HANA, Workday, or QuickBooks Online, map source traits onto ERP fields with an upsert key, run on-demand syncs, and read the sync-run history.

# CDP to ERP object sync

Object sync activates a CDP segment into your ERP or finance system as **native object writes** — NetSuite Customers or Contacts, SAP S/4HANA Business Partners or Customers, Workday Customers or Contacts, QuickBooks Customers. Records are upserted on an identifier field you choose, on the cadence you set.

This is the reverse-ETL path: the segment you build in the CDP becomes the source, and the ERP object becomes the destination. Object sync is the ERP sibling of [CDP to CRM object sync](/guides/cdp-crm-object-sync) — the same operator surface, aimed at finance and ERP master data instead of sales and martech objects.

## 1. Destinations and object types

Object sync targets four ERP and finance destinations, each with a closed list of native object types:

| Destination  | Object types                  | Default upsert identifier |
| ------------ | ----------------------------- | ------------------------- |
| `netsuite`   | `Customer`, `Contact`         | `email`                   |
| `sap_erp`    | `BusinessPartner`, `Customer` | `email`                   |
| `workday`    | `Customer`, `Contact`         | `email`                   |
| `quickbooks` | `Customer`                    | `PrimaryEmailAddr`        |

The `object_type` you set is validated against this list when you PATCH your config, so a target the destination cannot accept fails fast at config time, not at run time.

<Info>
  Distinguish the two sync directions this surface sits beside: the [per-event destinations](/guides/cdp-destinations) panel streams individual CDP events to connected apps, and [warehouse exports](/guides/cdp-reverse-etl-and-warehouse-exports) move tables in and out of your data warehouse. Object sync exists for the segment-level batch case — "this segment, into that ERP object" — with an upsert key so re-runs update in place instead of duplicating records.
</Info>

Connect the destination first from the [destinations](/guides/cdp-source-and-destinations) page — object sync can only dispatch against a destination your organization has connected. No credentials are accepted on the object-sync surface; the config carries only a connection identifier, and the upstream OAuth token stays inside your connected integration.

## 2. Configure a mapping

Read the config for every destination with `GET /api/v1/cdp/erp-sync/config`. It returns one entry per destination with its current mapping, the object types it accepts, the default upsert identifier, and the last sync outcome.

To create or update a mapping, PATCH the destination with only the keys you want to change — the merge is scoped to that destination, so sibling destinations and every other organization setting are preserved. The shape is `PATCH /api/v1/cdp/erp-sync/config/{destination}` with `{destination}` one of the four ids above:

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/cdp/erp-sync/config/netsuite" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "object_type": "Customer",
    "segment_id": "seg_active_accounts",
    "identifier_field": "email",
    "field_map": {
      "email": "email",
      "first_name": "FirstName",
      "last_name": "LastName",
      "lifecycle_stage": "Category"
    },
    "schedule_minutes": 60
  }'
```

| Field                 | Rule                                                                                                                                                                                                                 |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`             | Must be `true` before a run is accepted; a disabled destination returns `409 DESTINATION_NOT_ENABLED`.                                                                                                               |
| `object_type`         | Must be one of the destination's whitelisted object types (above).                                                                                                                                                   |
| `identifier_field`    | The upsert key the ERP system matches on; defaults to the destination's canonical field if you do not set one.                                                                                                       |
| `field_map`           | `{ "source_trait": "destination_field" }` pairs. Destination fields accept letters, digits, underscores, and dots — so SAP's nested `to_Customer.CustomerName` and QuickBooks's `PrimaryEmailAddr` shapes are valid. |
| `nango_connection_id` | Which connected ERP account to write into — an identifier only, never a credential.                                                                                                                                  |
| `segment_id`          | The segment this mapping draws from.                                                                                                                                                                                 |
| `schedule_minutes`    | Optional cadence, 15 to 1440 minutes, for scheduled runs.                                                                                                                                                            |

<Warning>
  The `identifier_field` must appear as one of the **values** in your `field_map`. The config is rejected otherwise (`400 VALIDATION_ERROR`), because without a mapped key every record would be blind-inserted as a duplicate in your ERP system. Two source traits writing to the same destination field are rejected for the same reason.
</Warning>

## 3. Run a sync

Trigger an on-demand run with `POST /api/v1/cdp/erp-sync/run/{destination}`:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/cdp/erp-sync/run/netsuite" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "seg_active_accounts",
    "profiles": [
      { "email": "ap@acme-finance.example", "first_name": "Ana", "last_name": "Lopez" },
      { "email": "ops@northwind.example", "first_name": "Ben", "last_name": "Ng" }
    ]
  }'
```

* `segment_id` (required, up to 200 chars) labels which segment the profiles belong to; it is recorded on the run.
* `profiles` is the batch of profiles to project through the stored field map (1 to 5000 per request).

The destination must be configured and enabled first — a run against an unconfigured destination returns `409` (`DESTINATION_NOT_ENABLED` until it is enabled, `FIELD_MAP_NOT_CONFIGURED` until a field map exists). The stored map and identifier are re-validated at run time as well.

The response is the recorded **run object**:

```json theme={null}
{
  "id": "9c3d...",
  "ts": "2026-09-27T10:04:11Z",
  "destination": "netsuite",
  "object_type": "Customer",
  "segment_id": "seg_active_accounts",
  "requested": 2,
  "matched": 2,
  "skipped": 0,
  "batches": 1,
  "dispatched": 1,
  "dispatch_skipped": 0,
  "status": "ok",
  "rows": { "attempted": 2, "delivered": 2, "rejected": 0, "failed": 0 },
  "dispatch_failures": []
}
```

Records are grouped into batches of up to 100 and upserted through your connected integration. The **skipped-without-identifier rule**: a profile whose mapped record has a missing or empty upsert-key value is skipped — never blind-inserted — and counted as `rejected` in the run's `rows` breakdown. `status` reads:

* `ok` — every batch upserted.
* `partial` — some batches upserted, some failed.
* `failed` — every batch failed.
* `skipped` — nothing to dispatch (no matched records, or no connected account wired yet).

<Info>
  A run against a destination with no connected account is still recorded — every batch reports as `dispatch_skipped` — so you can validate the mapping end to end before you wire the integration.
</Info>

## 4. Read the run history

`GET /api/v1/cdp/erp-sync/runs` returns the most recent runs, newest first. The history is capped at the last 50 runs per organization; older runs roll off as new ones land.

Use it the same way you use the [sync run log](/guides/cdp-sync-run-log): shape the run, check `rows.rejected` (profiles skipped for a missing upsert key) and `dispatch_failures` (per-batch provider errors, with record counts — never the profile data) to find the leg that needs a fix.

## 5. Where the config lives (no migration)

Object-sync config and run history live in your organization's settings as structured data — the same storage pattern the CRM object sync uses. There is no schema migration and no new table to manage:

* A PATCH merges only the keys you send, under the destination you named — sibling destinations and unrelated settings are never touched.
* Run history is a bounded, newest-first list; the cap keeps the record small and self-pruning.

## 6. Consent and audit

Object sync activates a segment you defined, through a connection you authorized. The decision to sync, and the cadence, are entirely your organization's controls:

* Config changes and runs are written to the audit log with the destination and the operation counts — never the profile records themselves.
* Runs record counts (matched, skipped, batches, dispatch outcomes), not profile payloads. Failed batches keep the provider error message and the batch's record count only.

If you operate under a consent regime, the segment's membership is the filter that decides whose data reaches the ERP system — keep the segment definition aligned with your subscribers' consent, as you would for any other activation surface ([erasure propagation](/guides/cdp-erasure-propagation) covers the delete path).

## Related

* [CDP to CRM object sync](/guides/cdp-crm-object-sync) — the sibling surface for Salesforce, HubSpot, Braze, Iterable, Customer.io, and Klaviyo
* [Reverse-ETL warehouse exports](/guides/cdp-reverse-etl-and-warehouse-exports) — the warehouse-shaped cousin of this flow
* [Build the source segment](/guides/cdp-segments)
* [CDP destinations (per-event dispatch)](/guides/cdp-destinations)
* [Read the sync run log](/guides/cdp-sync-run-log)
* [Decode CRM connection and dispatch errors](/troubleshooting/crm-integration-errors)
