CRM permissions reference
Each CRM provider gates API access a different way, and a missing grant usually fails silently rather than with an error you'd notice — the symptom is an empty list picker, a field that never shows up to map, or a sync that quietly imports nothing, not a red banner telling you what's wrong. If something in your CRM connection looks incomplete, this is the first place to check.
This page lists what we actually call, grouped by provider, so you (or whoever administers your CRM) can grant exactly what's needed — no more, no less.
HubSpot
HubSpot connects with a Private App access token. You create the private app in HubSpot yourself (Settings → Integrations → Private Apps), tick the scopes below by hand, and paste the token into our Connect dialog. There's no prompt or consent screen on our side — whatever scopes you didn't tick, we simply can't use, and the failure is silent (see the table below for which of these fail loudly and which don't).
| What we access | Direction | Scope |
|---|---|---|
| Contacts | Read + write | crm.objects.contacts.read, crm.objects.contacts.write |
| Contact properties (field discovery, and the mapping screen) | Read | crm.objects.contacts.read (also satisfied by crm.schemas.contacts.read) |
| Deals — read-only mirror, never written | Read | crm.objects.deals.read |
| Deal pipelines & stages (stage labels) | Read | crm.objects.deals.read |
| Deal ↔ contact associations | Read | Covered by crm.objects.deals.read + crm.objects.contacts.read — there's no separate associations scope |
| Owners (to show a display name on an owner field) | Read | crm.objects.owners.read |
| Marketing lists + list memberships | Read | crm.lists.read |
| Account info (portal ID, used to build links back to HubSpot) | Read | Not independently verified — see note below |
Notes:
crm.lists.readis not what powers "Limit inbound sync." HubSpot's search API can't filter by list membership at all, so the inbound scope feature uses a boolean contact property instead (see CRM sync scope). This scope is for Connected segments — building a segment from a HubSpot list — a separate feature. Miss it and that list picker is simply empty, with nothing telling you why.- We couldn't pin down from our own code exactly which scope (if any)
private apps need for the account-info lookup that resolves your portal
ID — some HubSpot documentation lists it under the base
oauthscope, which private apps may not expose as a checkbox at all. If your portal ID isn't resolving, check your private app's granted scopes in HubSpot directly.
Dynamics 365 (Dataverse)
Dynamics authenticates with OAuth client-credentials against
{resource}/.default — that grants an access token for the whole
organization, not a specific set of API scopes. What the connection can
actually do is entirely controlled by the Dataverse security role
assigned to the Application User created for this connection (Power
Platform admin center → your environment → Application users). Give that
role too little and the same thing happens as with HubSpot: no error, just
an empty picker or a field that never resolves.
| Table (entity) | Direction | Privilege needed |
|---|---|---|
| Contact | Read + write | Read, Write |
| Lead | Read + write | Read, Write |
| Opportunity — read-only mirror, never written | Read | Read |
| List (marketing lists) | Read | Read |
List Member (listmembers) | Read | Read |
Entity/attribute metadata (EntityDefinitions) | Read | Read access to entity metadata — this isn't a record-level privilege in the same sense as the rows above; if your security role can query the API at all it typically already has this |
Also read, only to enrich the read-only deal mirror (currency code and contact associations on an opportunity) — missing these degrades that one field gracefully (logged, left blank) rather than failing the sync:
- Connections — read (resolves opportunity stakeholders/contacts)
- Account — read (resolves an opportunity's primary contact when the deal's customer is an account rather than a contact directly)
- Transaction Currency — read (resolves the ISO currency code on a deal)
Dynamic marketing lists are excluded on purpose, not by a missing
privilege. Even with full Read on List, a dynamic list's membership is
a saved query with no member rows — there's nothing for our filter to
traverse — so it shows in the scope picker greyed out rather than being
usable. See CRM sync scope for what this looks like.
Pipedrive
Pipedrive is different again: the connection uses a personal API token, and a token carries exactly the permissions of the Pipedrive user it belongs to — there's no separate scope list to grant. Whatever that user can see and do in the Pipedrive UI, the connection can see and do through the API. Nothing more.
To make sure the connection works, the user whose token you use needs:
| What we access | Direction | What the token owner needs in Pipedrive |
|---|---|---|
| Persons (contacts) | Read + write | View and edit access to Contacts |
| Leads | Read + write | View and edit access to Leads |
| Deals — read-only mirror, never written | Read | View access to Deals |
| Deal participants (contact associations on a deal) | Read | View access to Deals (same permission as above) |
| Deal stages | Read | View access to Deals |
| Filters (marketing lists — Pipedrive's filters stand in for lists) | Read | View access to Filters/saved views for People and Leads |
| Person / lead field definitions (field discovery, mapping screen) | Read | View access to Contacts and Leads (same permission as above) |
users/me (connection health check) | Read | Any authenticated user — no extra permission |
And separately, at the account level: API access must be enabled for that user's permission set (Settings → Company settings → Permission sets → your set → "Use API"). Pipedrive treats this as an on/off switch independent of the object-level permissions above — a user can have full access to everything in the app and still be unable to generate an API token if this isn't turned on.