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
| Operation | Method and path | Scope |
|---|---|---|
| List entities | GET /crm/entities?search= | crm:read |
| Describe an entity | GET /crm/entities/{entitySet} | crm:read |
| Query records | GET /crm/records/{entitySet} | crm:read |
| Get a record | GET /crm/records/{entitySet}/{id} | crm:read |
| Create a record | POST /crm/records/{entitySet} | crm:write |
| Update a record | PATCH /crm/records/{entitySet}/{id} | crm:write |
| Delete a record | DELETE /crm/records/{entitySet}/{id} | crm:delete |
| Set state | POST /crm/records/{entitySet}/{id}/state | crm:write |
| Assign | POST /crm/records/{entitySet}/{id}/assign | crm:write |
| Associate or disassociate | POST /crm/records/{entitySet}/{id}/associate | crm:write (crm:delete to disassociate) |
| FetchXML | POST /crm/fetchxml | crm:read |
| Search | GET /crm/search?text=&entities=&top= | crm:read |
| Run an action | POST /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 parameter | Meaning |
|---|---|
select | Required (unless you pass a cursor): comma-separated column names. Without it the call answers 400 with the code crm_invalid_query |
filter | OData $filter |
orderBy | Columns with optional asc / desc |
expand | Related records to include |
pageSize | Rows per page |
cursor | The nextCursor of the previous page |
count | true 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"FetchXML and search
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>"| Operation | Body |
|---|---|
| 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
associatecall with"remove": true. Associating withoutremoveonly needscrm:write. - Actions with delete-type names: an action whose name contains
deleteorremove(in any letter case) needscrm:deleteon top ofcrm: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
| Code | HTTP | Meaning |
|---|---|---|
crm_invalid_query | 400 | A rule of the query is broken, for example a missing select |
crm_not_found | 404 | The entity or record does not exist |
crm_privilege | 403 | The application user lacks the privilege in Dynamics 365 |
crm_duplicate | 409 | Dataverse detected a duplicate |
crm_not_connected | 409 | The tenant has no CRM connected |
See Errors for the full list.
Business Central API
Call Business Central through the Plur-e public API — list and run catalogued methods, read-only OData queries, the scope each call needs and the deletions the API always refuses.
Plur-e panel API
Read your Plur-e mobile users, devices, recorded locations, activity, roles and licensed modules through the public API — the eight read-only routes, their scopes, pagination and response fields.