207 Multi-Status
Success response defined by RFC 4918 §11.1.
Last updated July 27, 2026
Common causes at a glance
- A PROPFIND enumerating a collection with per-resource property results
- A PROPPATCH where some property updates succeeded and others failed
- A COPY or MOVE across a tree where individual members were locked or missing
- A CalDAV or CardDAV client synchronising a calendar or address book collection
Reproduce it
curl -i -X PROPFIND -H 'Depth: 1' -H 'Content-Type: application/xml' https://dav.example.com/coll/What 207 Multi-Status means
A WebDAV method like PROPFIND, PROPPATCH,
COPY, or MOVE can act on a whole collection tree. One HTTP
status cannot describe “twelve succeeded, one is locked, one does not exist”, so WebDAV
returns 207 with a DAV:multistatus document in the body carrying a
DAV:response element per resource, each with its own status line.
A client that only checks the HTTP status therefore treats a request where every single operation failed as a success. Parsing the XML is mandatory, and per-resource failures have to be surfaced by the client.
What the spec says
RFC 4918 §11.1 defines the code and points at
§13 for the response format: application/xml or
text/xml containing a DAV:multistatus root. Each
DAV:response carries either a single DAV:status or, for
PROPFIND, one or more DAV:propstat elements grouping properties
by the status that applies to them (§9.1.2).
§13.2 lets a server omit descendants whose status matches an
ancestor’s, so a missing resource in the document does not mean it was skipped.
Extensions add codes inside the document: 208 from
RFC 5842, and 424 Failed Dependency
(§11.4) after a failed PROPPATCH.
What actually causes it
Almost every 207 problem is a parsing problem. Clients hardcode the
D: prefix instead of resolving the DAV: namespace URI, then
break the day a server emits a different prefix or a default namespace. Others read only
the first DAV:response, or ignore DAV:propstat grouping and
treat a 404 propstat block as a present-but-empty property. Servers contribute non-UTF-8
XML, text/html error pages inside what claims to be a multistatus, and
collection hrefs missing the trailing slash so client path matching fails. Nginx’s
DAV module does not implement
PROPFIND at all, so a WebDAV client hitting an nginx-only deployment gets
405 rather than 207 and the confusion starts there.
How to debug it
Get the raw XML out first, then look at the per-resource statuses rather than the 207:
curl -s -X PROPFIND -H 'Depth: 1' -H 'Content-Type: application/xml' \
--data '<?xml version="1.0"?><propfind xmlns="DAV:"><allprop/></propfind>' \
https://dav.example.com/coll/ | xmllint --format -
Extract every status line in the document; that is where the failures are. If the count
of DAV:response elements is lower than the number of members you expect,
check Depth: the default when the header is absent is infinity, and many
servers refuse infinite depth outright. Confirm the server speaks WebDAV with an
OPTIONS request and look for the DAV response header
(§10.1); if it is missing, no amount of XML will help.
Working examples
curl
# Enumerate one level and pretty-print the multistatus document.
curl -s -X PROPFIND \
-H 'Depth: 1' \
-H 'Content-Type: application/xml' \
--data '<?xml version="1.0"?><propfind xmlns="DAV:"><allprop/></propfind>' \
https://dav.example.com/coll/ | xmllint --format -
# The statuses that matter are inside the body, not on the response:
curl -s -X PROPFIND -H 'Depth: 1' https://dav.example.com/coll/ \
| grep -oE '<[A-Za-z]+:?status>[^<]*' | sort | uniq -c
# Confirm the server speaks WebDAV at all:
curl -sI -X OPTIONS https://dav.example.com/coll/ | grep -i '^dav:'
Python (requests)
import xml.etree.ElementTree as ET
import requests
DAV = "{DAV:}" # resolve the namespace URI; never trust the prefix
resp = requests.request(
"PROPFIND",
"https://dav.example.com/coll/",
headers={"Depth": "1", "Content-Type": "application/xml"},
data='<?xml version="1.0"?><propfind xmlns="DAV:"><allprop/></propfind>',
timeout=30,
)
print(resp.status_code) # 207 - says nothing about the individual results
root = ET.fromstring(resp.content)
for r in root.findall(f"{DAV}response"):
href = r.findtext(f"{DAV}href")
statuses = [s.text for s in r.iter(f"{DAV}status")]
print(href, statuses)
if any("200" not in (s or "") for s in statuses):
print(" -> partial failure on this resource")
Node (fetch)
// fetch() is fine here; the work is parsing the XML body.
const res = await fetch("https://dav.example.com/coll/", {
method: "PROPFIND",
headers: { Depth: "1", "Content-Type": "application/xml" },
body: '<?xml version="1.0"?><propfind xmlns="DAV:"><allprop/></propfind>',
});
console.log(res.status); // 207
const xml = await res.text();
const doc = new DOMParser().parseFromString(xml, "application/xml");
// getElementsByTagNameNS avoids depending on the D: prefix the server chose.
for (const r of doc.getElementsByTagNameNS("DAV:", "response")) {
const href = r.getElementsByTagNameNS("DAV:", "href")[0]?.textContent;
const statuses = [...r.getElementsByTagNameNS("DAV:", "status")]
.map((s) => s.textContent);
console.log(href, statuses);
}Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.