208 Already Reported
Success response defined by RFC 5842 §7.1.
Last updated July 27, 2026
Common causes at a glance
- A Depth: infinity PROPFIND across a collection reachable through two bindings
- A bind loop where a collection is, transitively, a member of itself
- A bind-aware client advertising support with the DAV request header
- A server deduplicating repeated subtrees to keep the multistatus finite
Reproduce it
curl -s -X PROPFIND -H 'Depth: infinity' -H 'DAV: bind' https://dav.example.com/Coll/What 208 Already Reported means
WebDAV bindings let more than one URL map to the same collection, so a
Depth: infinity PROPFIND can walk the same subtree repeatedly,
or loop forever if the bindings form a cycle. 208 is the deduplication marker: the first
binding to a collection gets a normal 200 with all its descendants, and every subsequent
binding gets 208 with no descendant elements at all.
You will not see 208 on a status line. It appears only as the value of a
DAV:status inside a 207 document, which makes it one of
the least-encountered codes in the registry.
What the spec says
RFC 5842 §7.1 defines 208 as part of the WebDAV binding
extensions and is explicit about scope: it is used inside a DAV:propstat
response element, occurs only for Depth: infinity requests, and matters most
when multiple bindings create a bind loop (§2.1.1). It SHOULD NOT
be used unless the client signalled support with the DAV request header
(§8.2); otherwise the server reports 508 Loop Detected
(§7.2) instead. RFC 5842 is Experimental, not Standards Track.
Clients reconstructing the binding graph should request the
DAV:resource-id property (§3.1).
What actually causes it
Seeing 208 requires a server implementing RFC 5842 bindings, which is
rare, plus a client that opted in. Most WebDAV deployments never create multiple bindings
to one collection, so the situation never arises. When it does and the client did not
send DAV: bind, correct behaviour is 508 Loop Detected rather than 208, and
a client hitting 508 mid-multistatus usually reports a truncated response. The other snag
is client-side: code mapping a WebDAV tree onto a local filesystem sees a 208 entry with
no children and records an empty directory, when the contents were reported earlier under
a different href. Reconnecting the two needs DAV:resource-id.
How to debug it
Ask for the identity property alongside whatever else you need, and send the
DAV header so the server is permitted to use 208 at all:
curl -s -X PROPFIND -H 'Depth: infinity' -H 'DAV: bind' \
-H 'Content-Type: application/xml' \
--data '<?xml version="1.0"?><D:propfind xmlns:D="DAV:"><D:prop><D:resource-id/></D:prop></D:propfind>' \
https://dav.example.com/Coll/ | xmllint --format -
Find HTTP/1.1 208 Already Reported in the status elements, note the
href it belongs to, then match its DAV:resource-id against the
earlier 200 response carrying the same id. That is the entry whose children apply. A 508
instead means the server rejected your DAV header or detected a loop it
could not deduplicate. Repeated Depth: 1 enumeration is slower but loop-proof
for clients that cannot handle bindings.
Working examples
curl
# The DAV: bind request header is what permits the server to answer with 208.
curl -s -X PROPFIND \
-H 'Depth: infinity' \
-H 'DAV: bind' \
-H 'Content-Type: application/xml' \
--data '<?xml version="1.0"?><D:propfind xmlns:D="DAV:"><D:prop><D:displayname/><D:resource-id/></D:prop></D:propfind>' \
https://dav.example.com/Coll/ | xmllint --format -
# Count how many entries came back deduplicated:
curl -s -X PROPFIND -H 'Depth: infinity' -H 'DAV: bind' \
https://dav.example.com/Coll/ | grep -c '208 Already Reported'
Python (requests)
import xml.etree.ElementTree as ET
import requests
DAV = "{DAV:}"
BODY = (
'<?xml version="1.0"?><D:propfind xmlns:D="DAV:">'
"<D:prop><D:displayname/><D:resource-id/></D:prop></D:propfind>"
)
resp = requests.request(
"PROPFIND",
"https://dav.example.com/Coll/",
headers={"Depth": "infinity", "DAV": "bind", "Content-Type": "application/xml"},
data=BODY,
timeout=60,
)
print(resp.status_code) # 207; 208 lives inside the body
by_id = {}
for r in ET.fromstring(resp.content).findall(f"{DAV}response"):
href = r.findtext(f"{DAV}href")
rid = "".join(t for t in r.iter(f"{DAV}resource-id") for t in t.itertext())
for status in (s.text or "" for s in r.iter(f"{DAV}status")):
if "208" in status:
print(f"{href} already reported as {by_id.get(rid, 'unknown href')}")
elif "200" in status:
by_id.setdefault(rid, href)
Node (fetch)
const body =
'<?xml version="1.0"?><D:propfind xmlns:D="DAV:">' +
"<D:prop><D:displayname/><D:resource-id/></D:prop></D:propfind>";
const res = await fetch("https://dav.example.com/Coll/", {
method: "PROPFIND",
// Without the DAV: bind header the server should send 508, not 208.
headers: { Depth: "infinity", DAV: "bind", "Content-Type": "application/xml" },
body,
});
console.log(res.status); // 207
const doc = new DOMParser().parseFromString(await res.text(), "application/xml");
for (const r of doc.getElementsByTagNameNS("DAV:", "response")) {
const href = r.getElementsByTagNameNS("DAV:", "href")[0]?.textContent;
const status = r.getElementsByTagNameNS("DAV:", "status")[0]?.textContent ?? "";
if (status.includes("208")) console.log("deduplicated binding:", href);
}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.