Free live webinar

Same team, more pipeline: AI-powered marketing for Dynamics 365 · Wed 11 Nov · 1:00pm ET · with Microsoft MVP Mark SmithSame team, more pipeline · 11 Nov

Save my seat

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 accessDirectionScope
ContactsRead + writecrm.objects.contacts.read, crm.objects.contacts.write
Contact properties (field discovery, and the mapping screen)Readcrm.objects.contacts.read (also satisfied by crm.schemas.contacts.read)
Deals — read-only mirror, never writtenReadcrm.objects.deals.read
Deal pipelines & stages (stage labels)Readcrm.objects.deals.read
Deal ↔ contact associationsReadCovered 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)Readcrm.objects.owners.read
Marketing lists + list membershipsReadcrm.lists.read
Account info (portal ID, used to build links back to HubSpot)ReadNot independently verified — see note below

Notes:

  • crm.lists.read is 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 oauth scope, 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)DirectionPrivilege needed
ContactRead + writeRead, Write
LeadRead + writeRead, Write
Opportunity — read-only mirror, never writtenReadRead
List (marketing lists)ReadRead
List Member (listmembers)ReadRead
Entity/attribute metadata (EntityDefinitions)ReadRead 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)

If the connection fails

The error names which of the four fields to look at. The two halves mean different things:

  • "Azure rejected…" — Azure AD would not issue a token, so Dynamics was never contacted. The message says which field: the client secret (copy the secret's Value, not its Secret ID — and check it hasn't expired), the Application (client) ID (not the Object ID), the Azure tenant ID, or an Organization URL that must be https://yourorg.crm.dynamics.com with no path. Each message carries an AADSTS… code your Azure administrator can look up.
  • "Dynamics rejected the sign-in" — your Azure credentials are fine and the token was issued, but Dataverse refused it. Almost always this means the environment has no Application User for that Client ID, or it has one with no security role. Add it as described above.

A wrong Organization URL does not usually produce the first error: Azure issues a token for any *.crm.dynamics.com address, so a typo there surfaces later as a failure to reach the environment.

Data push (optional — only if you enable it)

Pushing engagement data into your environment needs two things beyond the table above. See Pushing engagement data into Dynamics 365 for the full story.

WhatWhenPrivilege
Creating our publisher, solution and tablesOne-off, when you press ProvisionCustomisation rights — the System Customizer role is the simple way. You can remove it again afterwards: once provisioning succeeds we only ever write rows, never schema
Writing rows into those tablesOngoingCreate + Write on the tables we created (covered by the role your connection already uses for Contact/Lead)
Your users seeing those rowsOngoingOrganization-level Read on the created tables, in whichever role your users have. The tables are user-owned and the rows are owned by the Application User, so without this the data is present but invisible — an empty grid, no error. This is the most common way data push appears broken while working correctly

We never delete rows in your environment, never modify a table we did not create, and never add anything to a Microsoft solution.

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 connects by authorising our app — you click Connect with Pipedrive, sign in to Pipedrive in the window that opens, and approve the access listed on Pipedrive's own consent screen. There is no API token to generate and nothing to paste. We never ask for, see, or store your Pipedrive password or an API token.

Two things follow from that, and they're the ones worth understanding:

  • The connection acts as the person who authorised it. Whoever clicks through the consent screen is the Pipedrive user the connection behaves as. The permissions below are the ceiling; that user's own access in Pipedrive is the floor, and the lower of the two wins. Connecting as a user with restricted visibility gives you a connection with restricted visibility. Prefer an account that can see everything you want synced, and ideally not one that's about to leave — if their Pipedrive access is removed, the connection stops working.
  • It's all or nothing. Pipedrive shows the whole list on one screen and you either approve it or cancel — there are no individual tick boxes, so there's no way to end up half-granted. If you cancel, nothing is connected and nothing is stored.

What we ask for, and why

What we accessDirectionPermission requested
Persons (contacts)Read + writecontacts:full
Person field definitions (field discovery, and the mapping screen)Readcontacts:full
Finding a person by email or phone (matching an outbound change to the right record)Readsearch:read
LeadsRead + writeleads:full
Deals — read-only mirror, never writtenReaddeals:read
Deal participants (contact associations on a deal)Readdeals:read
Deal stages (stage labels)Readdeals:read
Saved filters (Pipedrive's filters stand in for marketing lists)ReadCovered by the person and lead permissions above
Account basics and the connection health check (users/me)Readbase
Owner names (turning the owner id on a record into a person's name)Readusers:read
Change notifications for persons, so we don't have to keep askingRead + writewebhooks:full

Notes:

  • Nothing here writes to your Pipedrive settings or schema. We don't ask for admin access. When a mapped dropdown gains a value we haven't seen before, the new option is created on our side, never in your Pipedrive field definitions.
  • Deals are a one-way mirror. We read them; we never create, edit or delete a deal, which is why the deal permission is read-only.
  • Webhooks are a quota measure, not extra reach. Pipedrive meters API usage against a daily budget, and having your account tell us what changed costs far less of it than asking repeatedly. It doesn't give us access to anything the read permissions above don't already cover.
  • users:read is only so an owner has a name. A change notification identifies a record's owner as a bare numeric id and carries no name anywhere, so without the user directory the owner on a contact would show as a raw Pipedrive user id rather than a person. base covers users/me — the account you connected as — and nothing else, so the directory is a separate permission. We read it once per batch of changes, and only to turn ids into names.
  • Notifications cover persons only. A person edited in Pipedrive reaches us within seconds, and we then re-check persons on a slower schedule purely to catch anything a dropped notification missed. Leads and deals are not notified — a lead notification carries no contact details and a deal notification carries neither its stage nor who it's linked to, so acting on either would create duplicates — and they keep being checked on the regular sync schedule instead. Nothing you change is missed either way; leads and deals simply arrive on that schedule rather than immediately.

If something looks incomplete

  • The list picker under "Limit inbound sync" is empty even though your Pipedrive account has saved filters: this isn't a permission you can grant separately. Disconnect and connect again; if it's still empty, that's worth reporting to us rather than changing anything in Pipedrive.
  • Nothing syncs at all, or the connection reports an error immediately: check that the person who authorised the connection still has access to Pipedrive, and that API access is enabled for their permission set (Settings → Company settings → Permission sets → your set → "Use API"). Pipedrive treats that as an on/off switch independent of the object-level permissions a user has in the app.
  • You want to hand the connection to someone else, or the authorising user is leaving: disconnect and reconnect as the new person. There's no way to re-point an existing connection at a different Pipedrive user.

Removing our access

Disconnecting the CRM in Settings → Integrations revokes our access at Pipedrive and uninstalls the app from your Pipedrive account — you don't need to do anything on the Pipedrive side as well. Contacts already synced into the app stay; segments built from Pipedrive saved filters stop working until you reconnect.