207 Multi-Status
A single HTTP response that carries multiple independent status codes for a batch of operations.
Quick Reference
| Category | 2xx Success |
|---|---|
| RFC | RFC 4918 (WebDAV), Section 11.1 |
| Cacheable | No — multi-status responses vary per request body |
| Retryable | Per-resource — retry only the failed sub-operations |
| Typical use | WebDAV batch operations; custom bulk REST APIs |
What 207 Multi-Status Means
HTTP 207 Multi-Status is defined in RFC 4918 as part of the WebDAV extension. It solves a specific problem: when a client asks a server to perform several operations in one HTTP request, those operations may succeed or fail independently. A plain 200 cannot express mixed outcomes, and returning 207 signals that the response body contains a separate status for every sub-operation.
The response body is an XML document conforming to the WebDAV namespace (DAV:). Each resource or operation gets its own <response> element with a <href> identifying the target and a <status> containing a full HTTP status line such as HTTP/1.1 200 OK or HTTP/1.1 403 Forbidden.
Outside WebDAV, REST APIs sometimes borrow 207 for bulk endpoints. The XML requirement of the WebDAV spec is rarely followed in those cases — JSON is far more common — but the semantic intent is identical: one envelope response, multiple inner statuses.
WebDAV Operations That Produce 207
The WebDAV protocol adds collection (folder) management to HTTP. Operations on a collection must report outcomes for every member resource. The methods most likely to return 207 are:
- PROPFIND — retrieves properties for a resource and, with
Depth: 1orDepth: infinity, every child resource. Each resource gets a<propstat>element with a status for each requested property. - PROPPATCH — sets or removes properties. Some properties may be protected (like
DAV:getcontentlength) while others succeed; 207 reports each outcome. - COPY and MOVE with
Depth: infinity— when moving a directory tree, individual children may encounter lock conflicts or permission errors. - DELETE on a collection — if any child resource is locked or protected, the server cannot complete the delete and reports the specific failures.
- LOCK applied to a collection — describes lock results for each member.
A minimal PROPFIND response for a collection with two members looks like this:
HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">
<response>
<href>/files/</href>
<propstat>
<prop><displayname>files</displayname></prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/files/report.pdf</href>
<propstat>
<prop><displayname>report.pdf</displayname></prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/files/locked.docx</href>
<propstat>
<prop><displayname/></prop>
<status>HTTP/1.1 423 Locked</status>
</propstat>
</response>
</multistatus>
The outer HTTP response is 207. The inner statuses are authoritative for each resource. A client must parse the XML and handle each <status> independently rather than treating the entire operation as either success or failure.
207 in Bulk REST API Endpoints
REST APIs that accept arrays of resources in a single request sometimes adopt 207 to report mixed results without WebDAV XML. A POST to /users/batch creating ten users where three fail validation might respond:
HTTP/1.1 207 Multi-Status
Content-Type: application/json
{
"results": [
{ "index": 0, "status": 201, "id": "u_001" },
{ "index": 1, "status": 201, "id": "u_002" },
{ "index": 2, "status": 422, "error": "email already exists" },
{ "index": 3, "status": 201, "id": "u_004" }
]
}
This pattern is used by several major APIs including Google Workspace (batch requests), Microsoft Graph (batch JSON), and Stripe (some bulk operations). The index or ID in each result ties back to the request item so clients can retry only the failed items.
There is no formal standard for the JSON body format. Teams implementing bulk endpoints should agree on a contract and document it clearly: which fields identify each sub-request, what the status field represents, and whether partial success is treated as overall success or failure for billing, logging, and idempotency purposes.
Implementing a 207 Response in Express.js
app.post('/api/messages/batch', async (req, res) => {
const items = req.body.messages; // array of message objects
const results = [];
for (let i = 0; i < items.length; i++) {
try {
const msg = await Message.create(items[i]);
results.push({ index: i, status: 201, id: msg.id });
} catch (err) {
if (err.name === 'ValidationError') {
results.push({ index: i, status: 422, error: err.message });
} else if (err.code === 'DUPLICATE_KEY') {
results.push({ index: i, status: 409, error: 'duplicate' });
} else {
results.push({ index: i, status: 500, error: 'internal error' });
}
}
}
// Use 207 only when outcomes differ; pure success can still be 200/201
const hasFailure = results.some(r => r.status >= 400);
const hasSuccess = results.some(r => r.status < 400);
const code = (hasFailure && hasSuccess) ? 207 : results[0].status;
res.status(code).json({ results });
});
The decision on the outer status code is worth documenting. Some teams always use 207 for any batch endpoint. Others reserve it for genuinely mixed results and return 200 or 201 when all sub-operations succeed. Both are defensible; consistency matters more than the choice itself.
207 vs Related Status Codes
| Code | Meaning | When to use |
|---|---|---|
| 200 OK | Complete success | All items in the batch succeeded |
| 207 Multi-Status | Mixed or partial results | Some items succeeded, some failed; or any WebDAV batch |
| 400 Bad Request | Entire request invalid | Malformed request body before any processing begins |
| 422 Unprocessable Content | Semantic validation failure | Single resource validation failure; per-item status inside 207 |
| 500 Internal Server Error | Server fault | Processing failed entirely — no partial results available |
Client-Side Handling
A fetch-based client that processes a 207 response must iterate the results array and route each item to the appropriate handler. Logging and retry logic should operate at the item level, not the request level. A common mistake is checking only the outer HTTP status: if (res.ok) will be true for a 207 that contains ten 500-status sub-responses.
const res = await fetch('/api/messages/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: payload })
});
const data = await res.json();
const failed = data.results.filter(r => r.status >= 400);
const created = data.results.filter(r => r.status === 201);
console.log(`Created: ${created.length}, Failed: ${failed.length}`);
if (failed.length) {
// retry or surface errors per item
}
Frequently Asked Questions
Is 207 only for WebDAV?
Technically no. RFC 4918 defines it, but the semantics are general enough that REST APIs use it for bulk operations. The XML body format is WebDAV-specific; JSON bulk APIs invent their own body format while borrowing the status code number.
Should I return 207 when all batch items succeed?
For WebDAV, yes — 207 is always used for multi-resource responses. For REST bulk endpoints, many teams return 200 or 201 when all items succeed and reserve 207 for mixed results. Document whichever you choose.
Can 207 contain a 5xx inner status?
Yes. Each inner status is independent and can be any valid HTTP status code including 500 or 503. The outer 207 simply means “this response contains multiple statuses.” A client must treat each inner 5xx as a server-side failure for that specific item.
Is 207 cacheable?
No. Multi-status responses describe the outcome of a specific batch operation and carry no cache-relevant headers. Each request produces a different outcome depending on the request body and server state at that moment.