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

# CRM integration

> Keep your CRM and Spirii in step on every account — who the customer is, what they run or charge with, and what their charging is worth.

By the end of this playbook your CRM and Spirii will share one view of each customer account: its identity and structure, maintained from your CRM, and the locations, charge keys, drivers, and charging activity behind it, pulled back from Spirii.

## Before you start

This playbook is for operators and e-mobility providers who run their customer relationships in a CRM and want it to reflect what happens on the Spirii platform. You'll need an [API key](/developers/authentication) and a working understanding of [customers](/components/organisation/customers), which are the accounts everything in this playbook hangs off.

The app user steps apply only if you have a branded app. Without one, skip them.

## How the pieces fit

In Spirii, a **customer** is the account: the organisation you do business with. What hangs off it depends on which side of your business the account sits on, and most partners have accounts on both.

* **On your charging network**, a customer is a site owner. Its **locations** and the chargers on them belong to it, and its charging activity is the revenue earned on those locations.
* **For your drivers**, a customer is a fleet. Its **charge keys** belong to it, and its charging activity is what those charge keys consumed, on your network and on others. In the API, charge keys are `/v2/tokens`.
* **App users** are the drivers signed up to your branded app. A driver can hold charge keys and be associated with a customer.

```mermaid theme={null}
flowchart TD
    C("Customer") --> CH("Child customers")
    C --> L("Locations")
    C --> K("Charge keys")
    L -->|"CDRs in cpo mode"| R("Charging activity")
    K -->|"CDRs in emp mode"| R
    AU("App users") --> K
```

<Warning>
  Fields named `crmId` and `crmCustomerId` in the Spirii API hold **Spirii's** customer ID, not your CRM's. Store your CRM's account ID in the customer's `externalId`, and treat any `crm…` field as a Spirii reference.
</Warning>

### Decide which system owns each record

A two-way sync works when each record has one system of record. If both systems can edit the same field, a change in one gets overwritten by the next sync from the other, and neither team can tell which value is current.

| Record                                                                | System of record                           | Sync direction                 | Endpoints                                                                                                                                                                                                                                   |
| --------------------------------------------------------------------- | ------------------------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Customer identity: name, type, country, contact, parent, `externalId` | Your CRM                                   | CRM → Spirii, and changes back | [Create](https://docs.spirii.com/api-reference/customer-v2/create-a-new-customer), [update](https://docs.spirii.com/api-reference/customer-v2/update-a-customer), [list](https://docs.spirii.com/api-reference/customer-v2/get-v2customers) |
| Locations and chargers                                                | Spirii                                     | Spirii → CRM                   | [Locations](https://docs.spirii.com/api-reference/locations-v2/fetches-a-list-of-locations)                                                                                                                                                 |
| Charge keys                                                           | Spirii                                     | Spirii → CRM                   | [Charge keys](https://docs.spirii.com/api-reference/tokens/get-a-list-of-tokens)                                                                                                                                                            |
| App users                                                             | Spirii — drivers create them by signing up | Spirii → CRM                   | [App users](https://docs.spirii.com/api-reference/app-users/get-a-list-of-app-users)                                                                                                                                                        |
| Charging activity                                                     | Spirii                                     | Spirii → CRM, aggregated       | [CDRs](https://docs.spirii.com/api-reference/charge-records-v2/get-a-list-of-cdrs)                                                                                                                                                          |

Leave billing and payout details (bank details, billing settings, financial setup) out of the sync. They drive Spirii's invoicing and payouts, and belong to your finance process rather than your CRM.

## Set up the sync

A CPO running charging operations for 100 site owners is a typical case: each site owner is an account in the CRM and a customer in Spirii, and account managers want each account's sites, chargers, and monthly revenue next to the contract.

<Steps>
  <Step title="Link the accounts you already have">
    For each existing customer, find it in Spirii and write your CRM's account ID into `externalId`. The `search` parameter matches customer names and IDs, including `externalId`.

    ```bash theme={null}
    curl "https://api.spirii.com/v2/customers?search=Lindenhof" \
      -H "Authorization: Bearer <SPIRII_API_KEY>"

    curl -X PUT https://api.spirii.com/v2/customers/123 \
      -H "Authorization: Bearer <SPIRII_API_KEY>" \
      -H "Content-Type: application/json" \
      -d '{ "externalId": "ACC-100482" }'
    ```

    Once every customer carries an `externalId`, you can match records in both directions without relying on names.
  </Step>

  <Step title="Create new accounts from your CRM">
    When an account is won in the CRM, create the customer in Spirii. `name`, `type`, `country`, `contactDetails`, and `ownerId` are required. `ownerId` places the customer in the hierarchy: your own customer ID for a direct account, or a parent customer's ID for a subsidiary.

    ```bash theme={null}
    curl -X POST https://api.spirii.com/v2/customers \
      -H "Authorization: Bearer <SPIRII_API_KEY>" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Lindenhof Housing",
        "type": "Business",
        "ownerId": 234,
        "externalId": "ACC-100482",
        "country": "DE",
        "contactDetails": { "name": "Facilities Team", "email": "facilities@example.com", "phone": "+4930123456" }
      }'
    ```

    Store the returned `id` on the CRM account. It's the ID you'll filter locations, charge keys, and CDRs by. See [Customer management](/capabilities/account/customer-management) for how the hierarchy is structured.
  </Step>

  <Step title="Push changes as they happen">
    When a field your CRM owns changes, send it with an update. The update accepts any subset of the customer's fields, so there's no need to resend the whole record.

    ```bash theme={null}
    curl -X PUT https://api.spirii.com/v2/customers/123 \
      -H "Authorization: Bearer <SPIRII_API_KEY>" \
      -H "Content-Type: application/json" \
      -d '{ "contactDetails": { "name": "Anna Keller", "email": "a.keller@example.com" } }'
    ```
  </Step>

  <Step title="Pull changes made in Spirii">
    Customers can also be edited in Spirii Connect. Fetch the ones updated since your last sync and reconcile them against the CRM. Setting `updatedAtFrom` sorts results by `updatedAt`, oldest first.

    ```bash theme={null}
    curl "https://api.spirii.com/v2/customers?updatedAtFrom=2026-09-27T00:00:00.000Z&limit=50&offset=0" \
      -H "Authorization: Bearer <SPIRII_API_KEY>"
    ```

    This endpoint pages by `offset`. Increase it by `limit` until a page comes back short.
  </Step>

  <Step title="Attach locations and charge keys">
    Pull each account's operational footprint onto the CRM record: its locations and their EVSEs on the network side, and its active charge keys on the driver side.

    ```bash theme={null}
    curl "https://api.spirii.com/v2/locations?customerIds=123&limit=100" \
      -H "Authorization: Bearer <SPIRII_API_KEY>"

    curl "https://api.spirii.com/v2/tokens?customerId=123&isActive=true" \
      -H "Authorization: Bearer <SPIRII_API_KEY>"
    ```

    See the [Locations](/components/charging/locations) and [Charge keys](/components/access/charge-keys) components for what each record holds.
  </Step>

  <Step title="Sync app users">
    If you have a branded app, list its app users to keep driver contacts in the CRM. Each record carries the driver's name, email, phone number, the customer they're associated with, and their charge keys.

    `newsletter` records whether the driver opted in to marketing email when they signed up. Use it as the consent flag for marketing to that driver from your CRM.

    See the [App users](/components/access/app-users) component for who can see an app user and how deletion works.
  </Step>

  <Step title="Add charging activity">
    Summarise each account's charging from its CDRs. Which mode and filter you use depends on the side of the business the account sits on:

    | Account    | `mode` | Filter CDRs on             | What you get                                           |
    | ---------- | ------ | -------------------------- | ------------------------------------------------------ |
    | Site owner | `cpo`  | `location.crmCustomerId`   | Sessions on the customer's locations                   |
    | Fleet      | `emp`  | `auth.idTag.crmCustomerId` | Sessions on the customer's charge keys, on any network |

    ```bash theme={null}
    curl -G "https://api.spirii.com/v2/charge-records" \
      -H "Authorization: Bearer <SPIRII_API_KEY>" \
      --data-urlencode 'mode=cpo' \
      --data-urlencode 'filter={"location.crmCustomerId":{"$eq":123}}' \
      --data-urlencode 'updatedAtFrom=2026-09-01T00:00:00.000Z' \
      --data-urlencode 'limit=5000'
    ```

    Add `includeChildCompanyRecords=1` to include sessions belonging to the customer's direct children. Sum `consumed` and `price.amountExVat` per account and period for the CRM record. The [Reporting and dashboards](/developers/reporting-and-dashboards) playbook covers storing and aggregating CDRs.
  </Step>
</Steps>

## Verify

<Check>
  Create a test account in your CRM and let the sync run. The customer appears under **Customer management** in Spirii Connect with your CRM's account ID as its external ID. On the next pull it comes back matched to the same CRM account, with no duplicate created.
</Check>

## Schedule the sync

Not every resource can be fetched by what changed, so the schedule differs per resource.

| Resource    | Fetch what changed?                     | How to keep it current                                                                   |
| ----------- | --------------------------------------- | ---------------------------------------------------------------------------------------- |
| Customers   | Yes, `updatedAtFrom` / `updatedAtTo`    | Incremental pull                                                                         |
| CDRs        | Yes, `updatedAtFrom` / `updatedAtTo`    | Incremental pull                                                                         |
| Charge keys | Sortable by `updatedAt`, not filterable | Sort by `updatedAt` descending and stop once you reach records older than your last sync |
| Locations   | No time filter                          | Full refresh, scoped with `customerIds`                                                  |
| App users   | Filterable by `createdAt` only          | Full refresh; treat an app user missing from the result as removed                       |

Full refreshes count against the [rate limit](/developers/rate-limits) of 120 requests per minute. Size their schedule to the number of accounts: a network with thousands of locations can't refresh them every few minutes.

## Best practices

* **Match on IDs, never names.** Join on Spirii's customer `id` and your `externalId`. Names change and aren't unique. See [Identifiers](/developers/identifiers).
* **Page each endpoint the way it pages.** Customers, charge keys, and app users use `offset`; locations and CDRs use cursors. Default page sizes also differ, so set `limit` explicitly. See [Pagination](/developers/pagination).
* **Request only what the CRM needs.** Some driver fields on CDRs are personal data and are returned only with sufficient access. Use `fields` on CDR requests to keep payloads small.
* **Retry transient errors safely**, and don't retry a rejected request unchanged. See [Errors](/developers/errors).

## Next steps

<CardGroup cols={2}>
  <Card title="ERP integration" icon="receipt" href="/developers/erp-integration">
    Turn the same accounts' CDRs into invoice lines and ledger entries.
  </Card>

  <Card title="Reporting and dashboards" icon="chart-column" href="/developers/reporting-and-dashboards">
    The retrieval patterns for storing and aggregating CDRs.
  </Card>
</CardGroup>
