400 Bad Request
Situations in the vault where SharePoint, SPFx, Microsoft Graph or the Azure services around them answer with HTTP 400. Each one explains why it happens and how to fix it.
- $filter on /groups/{id}/members needs $count=true, not just ConsistencyLevelgotchas / graphThe header is only half the recipe; a permanently failing call hid behind a best-effort catch as a "temporary outage"
- $filter on a multi-value person field can return 400 — filter those client-sidegotchas / rest-apiFall back to client filtering, but only on HTTP 400
- A # or % in a file or folder name — the classic …Url APIs cannot reach it, and the old writes quietly save %25gotchas / rest-apiThe classic …ServerRelativeUrl lookups, Files/add(url=), folders/add, moveto and copyto cannot reach it even encoded: 404/400, Exists: false for a folder that exists, and writes that answer 200 but save a literal %25…
- A $filter literal encoded twice finds nothing — with a 200gotchas / rest-apiIf a helper already percent-encodes the quoted literal, wrapping the whole $filter in encodeURIComponent encodes the value a second time (/ → %252F, @ → %2540); SharePoint then compares against the escaped text and…
- A column called MyField0 appears — a throttled existence check or two web parts provisioning at oncegotchas / rest-apiPOST /fields with a taken name succeeds with a numeric suffix; 429 read as "missing", a cached check or two concurrent web parts each make a twin — three-state check, single run per site, name check after the create
- A conditional spread hides a phantom column until SharePoint returns 400gotchas / spfx...(cond ? {…} : {}) bypasses the excess-property check, so a field the list doesn't have reaches the POST and 400s; two scripted sweeps find the rest
- A folder's Files collection rejects a two-level $expandgotchas / rest-apiListItemAllFields/Editor 400s there; take ModifiedBy/Author from the file, and list one level through the folder API rather than a FileDirRef filter
- A SharePoint group created by code hides its own membership — and Full Control does not get you ingotchas / securitysitegroups POST defaults to OnlyAllowMembersViewMembership: true, so only a member or the account that ran provisioning can read the members; Full Control does not help and identical permission masks prove it
- A Hyperlink column takes an object, not a stringgotchas / rest-apiIt reads back as { Description, Url } ([object Object], React #31) and a plain string fails the whole save with 400; one helper each way, 255-character address limit, scheme allowlist
- AddValidateUpdateItemUsingPath answers 200 when it creates nothing — while a write to a missing column is a loud 400gotchas / rest-apiField errors sit inside a 200 and no item is created; a write to a missing column is a 400 on every path, never a silent drop
- An unencoded # in a $filter value cuts the URL — SharePoint gets half a query and answers 400gotchas / rest-apiThe browser drops everything after # as a fragment, SharePoint gets an unterminated literal and answers 400; guest UPNs (#EXT#) always hit it; & cuts the expression and a raw + arrives as a space, so a valid literal…
- encodeURIComponent won't save you from apostrophes in OData literalsgotchas / rest-apiencodeURIComponent leaves ' alone; double it
- Choice fields accept any value over REST — validation is a form-only illusiongotchas / rest-apiREST skips choice validation entirely; enforce vocabulary yourself
- A copy job reports "finished" before the target shows every file – verify after the target settles, and read authors with $expandgotchas / rest-apiAfter CreateCopyJobs reports JobState 0, the target's /items can still miss hundreds of files for a while; re-read until two reads match, and compare authors via $expand on /items, not siteusers
- CreateCopyJobs: the library root is refused, progress logs are handed out once, and JobWarning is not a failuregotchas / rest-apiPassing a document library's root folder URL to CreateCopyJobs fails with HTTP 500 "Null value for source item" while a subfolder URL copies the folder with its content; several file names containing the hash sign…
- Creating a modern page via REST is a three-step dance, not one POSTgotchas / rest-apiCanvasContent1 won't stick on create; create → SavePageAsDraft → Publish, canvas is JSON; plain JSON is enough, Description needs its own write and a re-publish; the file name is random — rename with moveto
- DateTime fields — write full ISO with a time zone, derive the day locallygotchas / rest-apiNo-timezone writes 400; UTC reads shift the day; read a day in the zone it was written in — including noon-UTC dates at UTC+12 to +14; a bare date in $filter is UTC midnight, so le drops the last day of a range
- directoryObject collections reject $select of user fields — and $top is a separate, per-endpoint trapgotchas / graphUser fields on /members//transitiveMembers = 400; OData cast cures $select everywhere, $top stays per-endpoint (probe both)
- Drop __metadata from write bodies — odata=verbose needs OData v3, and SPHttpClient sends v4gotchas / rest-apiOld-tutorial payloads 400 as plain JSON, and switching to odata=verbose 400s again under SPHttpClient, which sends odata-version: 4.0; write plain JSON without the type hint, or blank odata-version where the verbose…
- Dropping a folder onto your upload zone does nothing (dataTransfer.files never sees it)gotchas / spfxdataTransfer.files cannot see a folder, items is neutered after the first await, and readEntries() returns the contents in batches of 100
- Enumerate every site from the browser console (no admin API, no tooling)gotchas / searchcontentclass:STS_Site OR STS_Web over Search REST lists every web the account can read, no admin role or tooling; plus the traps of a cross-site F12 sweep (Hidden filter, case-sensitive GetList, 400 vs 404, per-web…
- fields/getbyinternalnameortitle returns 400, not 404, for a missing fieldgotchas / rest-apiIt throws ArgumentException (HTTP 400), not 404; an existence-check that hard-fails on non-404 breaks the "field missing → create" path, and one that creates on any non-200 duplicates the column when throttled
- File size in a document library — File_x0020_Size 400s; use $expand=Filegotchas / rest-apiFile_x0020_Size 400s in $select; use File/Length
- Adding or removing a group member via Graph needs the object ID — a UPN returns 400 "Invalid object identifier"gotchas / graphPOST /groups/{id}/members/$ref and DELETE /groups/{id}/members/{id}/$ref accept only the user's object ID (GUID); a UPN from a people picker returns 400 Invalid object identifier — resolve it with GET…
- Which Entra ID roles are privileged? isPrivileged is beta-only, and a hand-written list gets it wronggotchas / graphRead isPrivileged live from beta roleDefinitions – v1.0 omits it and 400s on $select=isPrivileged; Exchange/SharePoint/Teams admins are not privileged
- Lookup and person fields — read with $expand, write with <Name>Idgotchas / rest-apiRead via $expand + projections, write via Id; ~12-lookup query limit
- A missing attachment returns 400, not 404 – an "is it there?" check by status code reads "not there" as a failuregotchas / rest-apiAttachmentFiles('name') on a missing attachment returns 400 ArgumentOutOfRangeException; check existence from the attachment list
- Looking up a field that does not exist returns 400, not 404gotchas / rest-apifields/GetByInternalNameOrTitle on a missing field answers 400; test existence with a $filter instead
- A library with NoCrawl returns nothing from Search — and silently blinds anything built on Searchgotchas / searchExcluded containers return 0 with HTTP 200 and no log, so Search-backed AI never sees them; provisioning that excludes data lists must not inherit that onto content libraries; reindex can't be forced over REST…
- POST /fields rejects a subtype property like RichText unless the body names the subtypegotchas / rest-api{ FieldTypeKind: 3, RichText: false } without @odata.type is read as SP.Field and fails with 400 – plain text columns in the same loop succeed, so the list ends up half-built; use CreateFieldAsXml or annotate the type
- POST /views takes an SP.View body — ViewTypeKind fails, and @odata.type passes only under OData v4gotchas / rest-apiViewTypeKind from the creation-information docs 400s, and an @odata.type annotation passes only with OData-Version: 4.0; measured recipe for view + scope + column order
- preAuthorizedApplications can't reference a scope created in the same PATCHgotchas / graphGraph checks delegatedPermissionIds against stored scopes; scope and its pre-authorization need two separate PATCH calls
- A new Choice value in your provisioning code never reaches already-deployed sitesgotchas / rest-apiCreate-if-missing never updates an existing field; a new Choice value in the manifest no-ops on deployed sites; reconcile with a post-hook SP.FieldChoice MERGE (plain JSON with @odata.type under SPHttpClient) that only…
- A REST write over 2 MB fails with "The request message is too big"gotchas / rest-apiSharePoint Online rejects any REST request whose body exceeds 2,097,152 bytes (after JSON escaping, in UTF-8). A multi-line text field in a list holds far more than that – the cap is the request, not the field – so…
- Retrying a write without the "new" columns on any 400 silently loses data — drop only the column the error namesgotchas / rest-apiWhen one new column is missing, a group-wide retry drops the ones that exist too; drop only the fields the error names as not existing, loop, and tell the user what was left out
- Microsoft To Do rejects $select on lists and tasks — 400 invalidRequest (RequestBroker--ParseUri)gotchas / graphSince Oct 2026 /me/todo/lists and /tasks return 400 for any $select except id; drop $select, keep $top/$filter, and measure 400s call by call before trusting a docs-based theory