Skip to content
Control Plane Labs

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.

Other 2xx codes