BadRequest (Microsoft Graph): the URL or body was rejected outright
Short answer: BadRequest, without the Request_ prefix, is what Microsoft Graph returns when the request fails before the directory ever sees it: a path segment that is not a Graph resource, or a body that is not valid JSON. The prefix is the tell. Request_BadRequest comes from the directory; a bare BadRequest comes from the layer in front of it.
Fix: Check the URL path against Graph's resource names, segment by segment, and check the request body parses as JSON with a content-type of application/json. If you build the URL by concatenation, look for an empty variable or a double slash. Full steps
| Error code | BadRequest |
|---|---|
| HTTP status | 400 |
| In Microsoft's Graph error documentation | No (checked ). The wording on this page is what Graph returned in the lab. |
| Category | Graph request shape |
| Attack signal | Low: usually configuration or user error |
What does BadRequest mean, and why has it no prefix?
Section titled “What does BadRequest mean, and why has it no prefix?”Most Graph errors about a request carry a Request_ prefix: Request_BadRequest, Request_UnsupportedQuery, Request_ResourceNotFound. A bare BadRequest is a different thing, and the difference is useful.
Our reading, from the two cases measured below: the prefixed codes come from the directory service behind Graph, which has seen your request and formed an opinion about its content. The bare BadRequest comes from the layer in front — the OData and request-parsing layer — which rejected the request before any directory service was asked. One of those two failures is about what you asked for; the other is about the request not being well-formed enough to ask anything.
So the prefix tells you roughly where to look. A prefixed code means the URL and body were structurally fine and the problem is in the content. A bare BadRequest means the URL path or the body is wrong, and your payload has not been looked at.
Microsoft’s current Graph error reference lists HTTP status codes only, with no named-code table; the string badRequest appears on it once, as a code value in a sample JSON body, with no definition. The retired Azure AD Graph catalogue lists Request_BadRequest and never the bare form. The wording on this page is what Graph v1.0 returned in a lab tenant on 31 October 2026.
Reproduced in the lab
Section titled “Reproduced in the lab”| Request | HTTP | Code | Message |
|---|---|---|---|
GET /me with an app-only token |
400 | BadRequest |
/me request is only valid with delegated authentication flow. |
GET /me/messages with an app-only token |
400 | BadRequest |
/me request is only valid with delegated authentication flow. |
GET /notAResource |
400 | BadRequest |
Resource not found for the segment 'notAResource'. |
POST /users with body {"displayName": |
400 | BadRequest |
Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format. |
/me with an app-only token: the most common way to hit this
Section titled “/me with an app-only token: the most common way to hit this”/me is a shortcut for “the signed-in user”. An app-only token has no signed-in user in it, so there is nobody for /me to mean, and Graph says so plainly:
/merequest is only valid with delegated authentication flow.
This is worth its own heading because it is the error people hit when they move working code from a delegated flow to a daemon or background service. The code compiles, the token is acquired successfully, the permissions are granted and consented, and every /me call returns 400. Nothing about the token is wrong.
The fix is to name the user explicitly: /users/<id or userPrincipalName> instead of /me, with the corresponding application permission (User.Read.All rather than the delegated User.Read). The lab ran that substitution with the same token and it returned 200. The sub-resources behave the same way — GET /me/messages returned the identical message — so /me/<anything> becomes /users/<id>/<anything>.
One consequence that catches people out: there is no app-only equivalent of “the current user” at all. If your code genuinely needs to know who is calling, it needs a delegated token, which means an interactive or on-behalf-of flow rather than client credentials.
A third row from the same run makes the boundary clear. GET /users/does/not/exist returned Request_BadRequest with the message Unexpected segment DynamicPathSegment. Expected property/$value. — so an unknown segment under a real resource is a prefixed error, while an unknown segment at the top level is a bare one. Our reading is that the first reached the users service and the second did not reach any service, which is consistent with the prefix but is not something the responses prove.
Note also what “Resource not found for the segment” does not mean. Despite the words, this is HTTP 400 and not 404: Graph is saying the path is not a valid Graph URL, not that an object is missing. A missing object gives you 404 Request_ResourceNotFound.
Is it an attack signal?
Section titled “Is it an attack signal?”Low, with one qualification.
A single occurrence is a wrong URL or a broken serialiser. Volume from a single identity, across many different top-level paths, is path enumeration — someone discovering which Graph resources exist by asking for them. It is a poor way to learn anything, because Graph’s resource list is public documentation, so this is not a signal worth building an alert on. It is worth noticing if it accompanies something else.
The more practical consequence is operational: these failures are invisible in Entra sign-in logs, like all Graph call failures, so a client that is failing every request still looks healthy from the identity side. The token acquisition succeeded; only the calls are broken. MicrosoftGraphActivityLogs is where they show up: KQL for failures by app.
How do I fix it?
Section titled “How do I fix it?”Resource not found for the segment 'X'. The path is not a Graph URL. Check the segment named in the message against Graph’s resource names — they are camelCase and plural for collections (users, servicePrincipals, directoryRoles, groups). Three things to rule out:
- A leading or doubled slash, or a path segment built from an empty variable. Neither was tested in the lab; they are worth ruling out first because they are the cheapest to check.
- A missing version segment. The host alone is not a valid base; every request needs
/v1.0/or/beta/. - A resource that exists only on
beta. If the documentation page you are working from has a beta selector, check which one it is set to.
Unable to read JSON request payload... The body did not parse. The message names both likely causes, so check them in this order:
- The
Content-Typeheader. It must beapplication/json. Several HTTP clients default toapplication/x-www-form-urlencodedor send nothing at all for aPOSTwith a string body. - The body itself. Serialise with a JSON library rather than by building a string. The lab case was a truncated object, which is what template interpolation produces when a value is missing.
- Double encoding. A JSON string that has itself been JSON-encoded is a valid string but not a valid object. If your body starts with
"{rather than{, that is the problem.
/me request is only valid with delegated authentication flow. Replace /me with /users/<id or userPrincipalName>, and use the application permission rather than its delegated counterpart. If the code needs the caller’s own identity, it needs a delegated token; client credentials cannot provide one.
If you cannot tell which layer rejected the request, send the same URL with no body and no query parameters. If the bare request succeeds, the path is fine and the problem is the body or the query. This takes one call and saves a lot of guessing.
Related
Section titled “Related”Request_BadRequest— the prefixed form, returned when the directory rejects the contentRequest_ResourceNotFound— a valid URL for an object that does not exist- All Graph error codes