207 Multi-Status

A single HTTP response that carries multiple independent status codes for a batch of operations.

Quick Reference

Category2xx Success
RFCRFC 4918 (WebDAV), Section 11.1
CacheableNo — multi-status responses vary per request body
RetryablePer-resource — retry only the failed sub-operations
Typical useWebDAV 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: 1 or Depth: 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

CodeMeaningWhen to use
200 OKComplete successAll items in the batch succeeded
207 Multi-StatusMixed or partial resultsSome items succeeded, some failed; or any WebDAV batch
400 Bad RequestEntire request invalidMalformed request body before any processing begins
422 Unprocessable ContentSemantic validation failureSingle resource validation failure; per-item status inside 207
500 Internal Server ErrorServer faultProcessing 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.