---
title: "Unified CRM SDK with 29 connector specifications"
description: "Unified CRM SDK La Boétie keeps private: 29 connector specifications, Node and Python runtimes, nine connectors active on a live sandbox, no hosted service."
canonical: "https://laboetie.io/products/crm"
language: en
---
# CRM SDK: unified CRM API for Node and Python

A unified CRM SDK is a library with one API across many CRMs. The La Boétie CRM SDK runs inside the application in Node and Python, covers contacts, companies, deals and leads, and keeps tokens and data in stores the application owns. It holds 29 connector specifications, and nine connectors are active on a live sandbox. The SDK stays private.

Status: Private. Access: In house. 29 connector specifications with tokens and data in your stores.

## Capabilities

- JavaScript and Python runtimes interpret the connector specification.
- Sync, webhooks and field mapping are built in.
- Both runtimes share replayed conformance fixtures.
- The connect UI ships as a web component.
- Agents connect through an MCP server.

## Where CRM tokens and data are stored

Tokens and CRM data stay in stores the application owns. No hosted service sits in between. The Node runtime includes stores for memory, Postgres and Mongo. Every token is encrypted.

## CRMs and objects covered

The 29 connector specifications include HubSpot, Pipedrive, Dynamics 365 Sales and Odoo. One API reads, syncs and writes contacts, companies, deals and leads across them. A capability matrix lists what each connector declares, because object coverage differs from one CRM to the next.

## Sync, webhooks and field mapping

Incremental sync commits each page with its checkpoint, so a crash resumes at the last committed page. Only one worker at a time can sync a given connection and model. Webhooks exist on three connectors: HubSpot, Pipedrive and GoHighLevel. Field mapping and custom fields run in the same runtime.

## Connect UI and MCP server

End users connect a CRM through a web component that has a React wrapper and a headless core. AI agents reach the SDK through an MCP (Model Context Protocol) server that runs in docs mode or app mode.

## Conformance fixtures shared by both runtimes

Both runtimes replay 476 recorded conformance fixture files and must return identical results. The repository holds 982 JavaScript and 1,144 Python test declarations. Nine connectors are active on a live sandbox.

## How La Boétie uses the CRM SDK

La Boétie develops the SDK in house and keeps every package private and unpublished. The studio offers no signup or public download for it. A team that needs CRM integrations inside its product can describe them in a brief.


## What a unified CRM SDK is

A **unified CRM SDK** is a library that gives your application one set of calls for many CRM systems. CRM stands for customer relationship management: the software where a sales team keeps its contacts, companies and deals.

Each CRM has its own API, its own field names, its own login flow and its own limits. A product that integrates with five CRMs would otherwise carry five versions of the same code. A unified layer replaces that with one model of a contact, one model of a deal and one way to read changes.

## Hosted unified API or SDK in your app

Two designs exist, and the difference decides where customer data travels.

| Question | Hosted unified API | SDK in your app |
|---|---|---|
| Where do requests go? | Through the vendor's servers | From your app straight to the CRM |
| Who stores OAuth tokens? | Usually the vendor | Your database |
| Who holds synced records? | Often the vendor | Your database |
| What you run | A client library | The sync workers and stores |
| Pricing logic | Often per connection or per call | No per call meter from a third party |
| Time to first connection | Short, with a hosted login page | You register the OAuth apps yourself |

A hosted API suits a team that wants the integration handled for it. An SDK suits a team that handles sensitive customer data, or one that wants no third party in the middle. This SDK takes the second design.

## Connectors and what each one covers

The repository holds 29 connector specifications. Coverage varies by CRM, because each provider exposes different objects, so a capability matrix states what each connector declares.

| Connector | Authentication | Objects |
|---|---|---|
| HubSpot | OAuth 2 | Contact, company, deal, lead, user, pipeline |
| Salesforce | OAuth 2 | Contact, company, deal, lead, user |
| Pipedrive | OAuth 2 | Contact, company, deal, lead, user, pipeline |
| Dynamics 365 Sales | OAuth 2 | Contact, company, deal, lead, user, task |
| Zoho CRM | OAuth 2 | Contact, company, deal, lead, user |
| Odoo CRM | API key | Contact, company, deal, lead, user, pipeline |
| Sellsy | OAuth 2 | Contact, company, deal, user |
| Attio | OAuth 2 | Contact, company, deal, user |
| Twenty | API key | Contact, company, deal, user |
| Teamleader Focus | OAuth 2 | Contact, company, deal, task, activity, user, pipeline |

A specification differs from a tested integration. Nine connectors are active on a live sandbox, and the others wait for a sandbox account. Self hosted Odoo, EspoCRM, SuiteCRM and Twenty accept a customer host, and the local sandboxes cover Odoo 16 to 20.

## Connect a CRM in Express

The server creates one object and mounts its routes.

```js
import express from 'express';
import { createCrm, createMemoryStores, toNodeListener } from '@ilanoh/crm';
import { authorize, sessionActor } from './crm-policy.js';

export const crm = await createCrm({
  ...createMemoryStores(),
  keys: { k1: process.env.CRM_KEY },
  active_key_id: 'k1',
  redirect_base_url: `${process.env.APP_URL}/crm`,
  return_to_allowlist: [`${process.env.APP_URL}/settings/`],
  base_path: '/crm',
  connectors: ['hubspot'],
  oauth: {
    hubspot: {
      client_id: process.env.HUBSPOT_CLIENT_ID,
      client_secret: process.env.HUBSPOT_CLIENT_SECRET,
    },
  },
  authenticateRequest: sessionActor,
  authorize,
});

const app = express();
app.use(toNodeListener(crm.handlers));
```

Memory stores suit a test. Production uses the Postgres or Mongo stores, which sit in your own database. Your two functions, `authenticateRequest` and `authorize`, plug the SDK into your existing login and permissions. Quickstarts cover Express, Fastify, Django, React and plain HTML.

In the browser, your user clicks a connect button, a web component.

```html
<crm-connect base-path="/crm" lang="en" status-model="contact"></crm-connect>
```

The component ships with a React wrapper and a headless core, so it can adopt your design.

## Read, write and fill empty fields

Reading uses one call for any connector.

```js
const client = crm.connection(connectionId, { principal: job });
const page = await client.list('contact', { limit: 100 });
```

A page holds `items` and a `next_page_token`. The token is signed, bound to its connection and expires after 24 hours at most. A filter the provider cannot apply raises `Unsupported` before any request, so your code never receives silently unfiltered data.

Writing follows patch rules that hold across every connector.

| You send | Result |
|---|---|
| A key is absent | The field stays unchanged |
| `null` | The field is cleared |
| An empty string or array | Sent as written |
| `skipIfUnchanged: true` | The SDK reads first and sends nothing when the values match |

`fillEmpty` suits enrichment. It writes only the fields that are empty, so a value entered by a salesperson stays in place, and the result lists what it wrote and what it kept.

A write retries only when the retry cannot apply it twice. When the outcome is unknown, the SDK says so, which keeps duplicates out of a customer's CRM.

## Sync without losing records

A sync has to survive crashes, parallel workers and provider limits. The SDK handles that loop: only one worker syncs a given connection at a time, and a crash resumes where the last committed page ended.

Providers offer very different ways to read changes, and your code calls the same function for all of them. Check the capability matrix before you promise a sync frequency, because some CRMs need a full read while others offer a change feed.

## Deleted records

A system that copies CRM data must remove a record when the CRM deletes it. For personal data, this is a legal concern as well as a technical one. The SDK reports a deletion only on evidence, and a record missing from one response never counts. Each deletion reaches your store with its reason and date.

## Custom fields

Every sales team adds its own fields, so a unified model alone falls short. The capability matrix states custom field support per object, and records carry these values under `custom_fields.<key>`. Writes filter them key by key, so `fillEmpty` protects a custom value as it protects a standard one. A companion skill helps a coding agent map custom fields for a new integration.

## Security and token storage

The SDK holds customer CRM credentials inside your application, so its protections matter. Tokens are encrypted in your database, a wrong key stops startup before any bad write, and keys can be rotated. Requests to private addresses and redirects to other hosts are refused. Your application remains responsible for its own session security.

## Errors in one shape

Every error has one JSON shape in both runtimes. It holds a code, a message free of secrets, a `retryable` flag and the provider's status. `retryable` is true for `RateLimited` and `ProviderUnavailable`. A queue can carry an error from one process to another and rebuild it with its type intact.

## Node and Python parity

Two runtimes usually drift apart. Here, both read the same connector specifications and must return identical results on the same recorded fixtures, so a Python worker and a Node API can share one table. Django has its own integration.

## AI agents and MCP

An MCP server exposes the SDK to AI agents. MCP, the Model Context Protocol, is a standard way for an agent to call tools. Docs mode lets a coding agent read the specifications and add a connector, and app mode lets an agent act on a connected CRM under the permissions of its principal.

## When to use this SDK

| Your situation | Recommendation |
|---|---|
| One CRM, your own account | Call its API directly |
| A product that connects to your customers' CRMs | An SDK or a hosted API |
| Customer data must stay in your database | An SDK |
| You need many connectors live next week | A hosted API, which has them tested today |
| Custom logic per CRM | An SDK with a raw request escape hatch |

## Next step

[Send a brief](https://laboetie.io/brief) with the CRMs your customers use and the objects you need, and the studio will reply with what is ready. The rest of the catalogue sits on the [products page](https://laboetie.io/products).


## Frequently asked questions

### What is a unified CRM SDK?

A unified CRM SDK is a library with one API across many CRMs for use inside an application. The La Boétie CRM SDK runs in the application process, in Node and Python, and both runtimes interpret the same connector specification. Each of the 29 CRM specifications maps one provider onto common models for contacts, companies, deals and leads.

### Which CRMs does the SDK connect to?

The 29 CRM connector specifications in the repository include HubSpot, Pipedrive, Dynamics 365 Sales, Odoo, Salesforce and Zoho CRM. Nine connectors are active on a live sandbox today, and seventeen wait for a sandbox account. The same runtime reads calls and transcripts from Ringover, Aircall and Microsoft Teams through their own connectors.

### Where does the SDK store OAuth tokens?

OAuth tokens stay in a store the application owns. Tokens are encrypted under a key from the application secret store, and keys can be rotated.

### Which languages and frameworks does the SDK support?

The Node and Python runtimes of the SDK replay the same conformance fixtures. Quickstarts cover Express, Fastify, Django, React and plain HTML. In each quickstart, about fifteen lines of code mount the handlers under one route and connect HubSpot. The Python runtime includes a Django integration, and the connect UI ships as a web component.

### Does the SDK support custom CRM fields?

Yes, on the connectors and models that declare them. The capability matrix has a custom fields column for every connector and model. The runtime includes read and write mapping code for those fields. Check that column before relying on a field, because coverage differs between CRMs and between objects inside one CRM.

### Can AI agents use the CRM SDK?

Yes. The SDK includes an MCP (Model Context Protocol) server with a docs mode and an app mode. A plugin bundles that server with four skills, which cover integrating the SDK, adding a connector, debugging a connection and mapping custom fields. Like the other packages, the server stays private and appears on no public registry.

### Can I install the CRM SDK from npm or PyPI?

No. Every package is marked private and absent from npm and PyPI. La Boétie develops the SDK in house and offers no download or signup. Teams that need CRM integrations in their own product can send a brief from this page that lists the CRMs involved and the objects to sync, and the studio will reply from there.
