Plur-e
Developers

Dynamics 365 CRM API

Read and write Dynamics 365 CRM (Dataverse) through the Plur-e public API — the 13 operations with curl examples, the required select, cursor pagination and how deletes and actions are scoped.

Quick answer

The CRM API exposes 13 operations under /crm: list and describe entities, query and get records, create, update, delete, change state, assign, associate, FetchXML, search and custom actions. Reads need crm:read, create/update operations crm:write, deletes crm:delete. Queries must pass select, results are paged with nextCursor, and every call runs as the application user of the CRM connection tied to the key, limited by the key's CRM roles.

Base URL: https://api.plur-e.com/api/public/v1. Names in the path are the Dataverse entity set names (plural, for example accounts, contacts). Choice columns are integers and lookups are read as _<column>_value. If no CRM is connected the call answers 409 with the code crm_not_connected.

The 13 operations

OperationMethod and pathScope
List entitiesGET /crm/entities?search=crm:read
Describe an entityGET /crm/entities/{entitySet}crm:read
Query recordsGET /crm/records/{entitySet}crm:read
Get a recordGET /crm/records/{entitySet}/{id}crm:read
Create a recordPOST /crm/records/{entitySet}crm:write
Update a recordPATCH /crm/records/{entitySet}/{id}crm:write
Delete a recordDELETE /crm/records/{entitySet}/{id}crm:delete
Set statePOST /crm/records/{entitySet}/{id}/statecrm:write
AssignPOST /crm/records/{entitySet}/{id}/assigncrm:write
Associate or disassociatePOST /crm/records/{entitySet}/{id}/associatecrm:write (crm:delete to disassociate)
FetchXMLPOST /crm/fetchxmlcrm:read
SearchGET /crm/search?text=&entities=&top=crm:read
Run an actionPOST /crm/actions/{action}crm:write (crm:delete for delete-type actions)

Each call is also checked against the key's CRM roles: the entity needs Read, New, Update or Delete there, matching the operation. A call the roles do not grant answers 403.

Read

# Which entities can this key see?
curl -s "https://api.plur-e.com/api/public/v1/crm/entities?search=account" \
  -H "Authorization: Bearer <YOUR_API_KEY>"

# Columns and relationships of one entity
curl -s "https://api.plur-e.com/api/public/v1/crm/entities/accounts" \
  -H "Authorization: Bearer <YOUR_API_KEY>"

Describe an entity before you query it: never guess column names.

Query records

curl -s -G "https://api.plur-e.com/api/public/v1/crm/records/accounts" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  --data-urlencode "select=name,accountnumber" \
  --data-urlencode "filter=statecode eq 0" \
  --data-urlencode "orderBy=name asc" \
  --data-urlencode "pageSize=50" \
  --data-urlencode "count=true"
Query parameterMeaning
selectRequired (unless you pass a cursor): comma-separated column names. Without it the call answers 400 with the code crm_invalid_query
filterOData $filter
orderByColumns with optional asc / desc
expandRelated records to include
pageSizeRows per page
cursorThe nextCursor of the previous page
counttrue adds the total of matching rows

Follow nextCursor until it comes back empty. Keep pages small.

curl -s -G "https://api.plur-e.com/api/public/v1/crm/records/accounts/<RECORD_ID>" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  --data-urlencode "select=name,accountnumber"
curl -s -X POST "https://api.plur-e.com/api/public/v1/crm/fetchxml" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "entitySet": "accounts", "fetchXml": "<fetch top=\"10\"><entity name=\"account\"><attribute name=\"name\"/></entity></fetch>", "cursor": null }'

curl -s -G "https://api.plur-e.com/api/public/v1/crm/search" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  --data-urlencode "text=contoso" \
  --data-urlencode "entities=accounts,contacts" \
  --data-urlencode "top=10"

Every table joined in a FetchXML query needs Read in the key's roles too.

Write

Create, update and delete return the object that the CRM operation returns. Create answers 201; the others answer 200.

# Create (values are columns; lookups bind a related record by id)
curl -s -X POST "https://api.plur-e.com/api/public/v1/crm/records/contacts" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "values": { "firstname": "Ada", "lastname": "Lovelace" }, "lookups": { "parentcustomerid_account": "<ACCOUNT_ID>" } }'

# Update (clearLookups removes lookups; etag protects against concurrent edits)
curl -s -X PATCH "https://api.plur-e.com/api/public/v1/crm/records/contacts/<RECORD_ID>" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "values": { "jobtitle": "Engineer" }, "lookups": { }, "clearLookups": [ ], "etag": null }'

# Delete (needs crm:delete)
curl -s -X DELETE "https://api.plur-e.com/api/public/v1/crm/records/contacts/<RECORD_ID>" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
OperationBody
Create{ "values": { }, "lookups": { } }
Update{ "values": { }, "lookups": { }, "clearLookups": [ ], "etag": "…" }
Set state{ "stateCode": 1, "statusCode": 2 }
Assign{ "ownerType": "systemuser" or "team", "ownerId": "…" }
Associate{ "navigation": "…", "relatedEntitySet": "…", "relatedId": "…", "remove": false }
Run an action{ "parameters": { }, "boundEntitySet": "…", "boundId": "…" } (bound fields only for a bound action)
curl -s -X POST "https://api.plur-e.com/api/public/v1/crm/records/accounts/<RECORD_ID>/state" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "stateCode": 1, "statusCode": 2 }'

What needs crm:delete

  • DELETE /crm/records/{entitySet}/{id}.
  • Disassociating: an associate call with "remove": true. Associating without remove only needs crm:write.
  • Actions with delete-type names: an action whose name contains delete or remove (in any letter case) needs crm:delete on top of crm:write, because custom actions can delete data even though they run with update rights.

Without the extra scope the call answers 403 with error plure_1321 and the message This API key does not have the scope 'crm:delete'.

Who the call runs as

CRM calls run as the application user of the connection, not as a person. What it can do is limited three ways: by the key's scopes, by the key's CRM roles, and by the security roles of the application user in Dynamics 365. A refusal by Dataverse itself answers 403 with the code crm_privilege.

CRM error codes

CodeHTTPMeaning
crm_invalid_query400A rule of the query is broken, for example a missing select
crm_not_found404The entity or record does not exist
crm_privilege403The application user lacks the privilege in Dynamics 365
crm_duplicate409Dataverse detected a duplicate
crm_not_connected409The tenant has no CRM connected

See Errors for the full list.

Was this page helpful?

On this page