Dataverse Web API errors explained

By Emil Björk · Microsoft business apps consultant, Gothenburg

Dataverse Web API errors by code — 0x80040217 does not exist, 0x80040220 privilege, 0x80040333 duplicate, 0x80040237 duplicate key, 0x80048d19 payload, 0x80072322 throttling, @odata.bind mistakes — with fixes.

Published

Dataverse returns errors as a JSON body with a code (a hex value like 0x80040217), a message, and often a link. The hex code is the stable identifier — messages get reworded, codes do not — and this reference is organised by it. It covers the errors integration developers, flow builders, and plug-in authors meet most. For the API itself, see Dataverse organization service vs Web API and batch operations in the Dataverse Web API.

Reading an error

{
  "error": {
    "code": "0x80040217",
    "message": "account With Id = 3a9f… Does Not Exist"
  }
}

HTTP status tells you the family (400 bad request, 401 unauthenticated, 403 forbidden, 404 not found, 412 precondition, 429 throttled, 500 server); the code tells you which specific rule fired. Log both plus the x-ms-service-request-id response header for support.

Authentication

401 — "Bearer token is missing or invalid" / "IDX10214: Audience validation failed"

Cause. No token, an expired token, or a token for the wrong audience. The scope must be https://<org>.crm<n>.dynamics.com/.default for the specific environment; a Graph token or a token for another environment fails.

Fix. Request the token for the environment URL; refresh before the one-hour expiry.

401/403 — "The user is not a member of the organization" / user disabled

Cause. The identity is valid in Entra but has no user row in this environment — a service principal that was never added as an application user, or a user without a licence or security role in this environment.

Fix. Add the app registration as an application user in the Power Platform admin centre and assign a security role; for people, check licence assignment and environment security group membership.

Prevention. Application users per environment are part of every environment's setup checklist — Power Platform environments.

403 — 0x80040220 "Principal user (Id=…) is missing prvXxx privilege" / "SecLib::AccessCheckEx failed"

Cause. The caller's security roles do not grant that privilege on that table at that scope — prvReadaccount, prvCreatenew_order, prvAppendToaccount (needed to set a lookup to it), prvAssign, prvShare. Append and AppendTo are the ones everyone forgets: setting a lookup needs Append on the child and AppendTo on the parent.

Fix. Add the privilege to the role, or run as a user who has it. Dataverse security model explains scopes.

Addressing and payload

404 — "Resource not found for the segment 'X'"

Cause. The entity set name in the URL is wrong — it is the plural logical collection name (accounts, contacts, new_orders), and custom tables use the publisher prefix. Also raised for a misspelt navigation property in $expand.

Fix. Check the entity set name in /api/data/v9.2/ (the service document lists them) or in the table's properties in the maker portal.

400 — "Could not find a property named 'X' on type 'Microsoft.Dynamics.CRM.account'"

Cause. A column name in $select, $filter, $orderby, or $expand that is not the lowercase logical name. Lookups in $select and $filter use the _name_value form; navigation properties in $expand use the schema name of the relationship.

Fix. Use logical names from the table's Columns view; for lookups, _parentcustomerid_value.

404 — 0x80040217 "X With Id = … Does Not Exist" / 0x80060891 "Entity 'X' With Id = … Does Not Exist"

Cause. The row was deleted, the GUID belongs to a different table, or the caller cannot see it — Dataverse returns not-found rather than forbidden for rows outside the user's read scope, which sends people looking for a data problem when it is a security one.

Fix. Confirm the GUID and table; then check the caller's read scope (user, business unit, organisation) on that table.

400 — 0x80048d19 "Error identified in Payload provided by the user for Entity: 'X'" / "An undeclared property 'X' which only has property annotations in the payload but no property value was found"

Cause. The JSON body contains a property Dataverse does not accept: a misspelt or wrong-case logical name, a display name, a read-only column (createdon, _x_value), or a lookup set without @odata.bind. The "undeclared property" variant is almost always a lookup written as new_customer@odata.bind when the navigation property is new_Customer@odata.bind (schema name, case-sensitive) — or the reverse.

Fix. Set lookups with the navigation property's exact schema name: "parentcustomerid_account@odata.bind": "/accounts(guid)". Remove read-only columns from the body. Check every property against $metadata.

Prevention. Generate request bodies from metadata rather than typing them; test in the browser's developer tools with Xrm.WebApi.createRecord where the errors are the same but faster to iterate.

400 — "CRM do not support direct update of Entity Reference properties, Use Navigation properties instead."

Cause. Setting a lookup by writing the GUID to _parentcustomerid_value or to the attribute name.

Fix. Use @odata.bind as above.

400 — "The date-time format for X is invalid" / "Cannot convert the literal 'X' to the expected type 'Edm.DateTimeOffset'"

Cause. Dates not in ISO 8601 (2026-09-03T00:00:00Z), or a date-only column given a time, or a locale-formatted string.

Fix. ISO 8601 with a Z or offset; date-only columns take 2026-09-03.

400 — "A validation error occurred. The value of 'X' on record of type 'Y' is outside the valid range." / choice value not valid

Cause. An integer for a choice column that is not one of its options, or a number outside the column's min/max.

Fix. Use the option's integer value from the column definition, not its label.

413 / "The request is too large" / "Maximum number of requests per batch exceeded"

Cause. A $batch with more than 1,000 operations, or a payload above the request size limit.

Fix. Chunk batches; keep change sets small so one failure does not roll back a thousand.

Business rules and keys

400 — 0x80040333 "A record was not created or updated because a duplicate of the current record already exists."

Cause. A duplicate detection rule matched an existing row (same email on contact, same name on account).

Fix. If the duplicate is genuine, merge or update the existing row. If the write is deliberate, send MSCRM.SuppressDuplicateDetection: true. Consider whether the integration should use an alternate key upsert instead.

400 — 0x80040237 "Cannot insert duplicate key."

Cause. An alternate key (unique index) already has a row with that value — an integration creating a row that exists, or two integrations racing.

Fix. Use the alternate key in the URL with PATCH (/accounts(new_externalid='ABC')), which upserts. Dataverse alternate keys covers the design.

400 — 0x80040265 "ISV code aborted the operation" / a custom message

Cause. A plug-in threw. The message is the plug-in's; the code is the envelope.

Fix. Dataverse plug-in exceptions.

400 — business rule or required column: "You must provide a value for required field 'X'" / "Attribute: X … is not a valid attribute"

Cause. Business-required columns and business rules apply to the API too.

Fix. Supply the column; disable or scope the business rule if the API should bypass it.

412 — "The version of the existing record doesn't match the RowVersion property provided"

Cause. An If-Match header with a stale etag.

Fix. Re-read and retry; omit If-Match for last-write-wins.

Throttling and limits

429 — 0x80072322 "Number of requests exceeded the limit of 6000 over time window of 300 seconds."

429 — 0x80072321 "Combined execution time of incoming requests exceeded limit of 1,200,000 milliseconds over time window of 300 seconds."

429 — 0x80072326 "Number of concurrent requests exceeded the limit of 52."

Cause. Service protection limits per user (or per application user) per five-minute window: requests, execution time, and concurrency. Loops that call the API per row, unbounded parallelism, and RetrieveMultiple without paging all get here.

Fix. Honour Retry-After with exponential backoff; reduce calls with $select, $expand, and batches; keep concurrency under the limit; spread heavy loads across application users only when the work is genuinely independent.

Prevention. Design integrations around the limits from day one; the polling vs push and change tracking guides cover reducing calls at the source.

"Sql error: Generic SQL error" — 0x80044150 / "Sql timeout expired" / deadlock

Cause. Lock contention on hot rows or tables, a query without selective filters on a large table, or a synchronous plug-in holding a transaction.

Fix. Retry once; add filters on indexed columns; shorten transactions.

"Maximum request length exceeded" / attachments

Cause. File and image uploads above the configured size; use the chunked file upload endpoints for large files.

Query errors

"The 'X' operator is not supported for the type" / "Function 'contains' is not supported on this property"

Cause. An OData operator on a column type that does not support it — contains on a lookup, arithmetic on a choice.

Fix. Filter on _x_value for lookups; use FetchXML for anything the OData translation cannot express — FetchXML vs OData.

Cause. Paging. The response includes @odata.nextLink when more rows exist; callers that ignore it get a partial dataset with no error.

Fix. Follow nextLink until absent; set Prefer: odata.maxpagesize to control page size.

When it is not the Web API's fault

Errors from a plug-in, a business rule, or a synchronous flow are surfaced through the API with their own messages inside the same envelope; the code above tells you which layer. And an error that only appears for some users and not others is a security-role problem until proven otherwise — Dataverse security model and field-level security are where to look.

Frequently asked questions

How do I set a lookup through the Dataverse Web API?

With the navigation property and @odata.bind, not the attribute name: "parentcustomerid_account@odata.bind": "/accounts(guid)". Sending the GUID in _parentcustomerid_value or the attribute directly produces 'CRM do not support direct update of Entity Reference properties' or a 0x80048d19 payload error.

What does 0x80048d19 'Error identified in Payload provided by the user' mean?

The JSON body has a property Dataverse does not recognise for that table — a misspelt logical name, wrong case, a display name, a lookup set without @odata.bind, or a property that only exists on another table. The message that follows usually names the undeclared property.

What is the difference between 0x80040333 and 0x80040237?

0x80040333 is a duplicate detection rule firing — a configured rule matched an existing row, and you can bypass it per request with the MSCRM.SuppressDuplicateDetection header if that is intended. 0x80040237 is a hard alternate-key violation — a row with that key already exists and the write is refused; use an upsert against the key instead.

Why do I get 429 or 0x80072322 under load?

Service protection limits per user: 6,000 requests, 20 minutes of execution time, and 52 concurrent requests per five-minute window. Batch, reduce calls, parallelise across users or app users where appropriate, and honour the Retry-After header with backoff.

Further reading

Related guides

Browse every guide in Customer Engagement or just Troubleshooting.

Spot something wrong or want a topic covered? Send a correction or a topic request — both are welcome.