Skip to content

207 Multi-Status

What is HTTP 207 Multi-Status?

207 Multi-Status
Sometimes you ask a computer to do many things at once - like move 5 files, copy...
HTTP 207 Multi-Status status code illustration

Explain Like I’m 3

You asked about lots of different things all at once, and some worked and some didn’t! The computer gives you a list telling you which ones worked and which ones didn’t, all in one answer!

Example: You ask ‘Can I have a cookie, a toy, and a dinosaur?’ Mom says ‘Yes to cookie, yes to toy, no to dinosaur!’ She tells you about all three things in one answer.

Explain Like I’m 5

Sometimes you ask a computer to do many things at once - like move 5 files, copy 3 folders, and delete 2 pictures. Instead of giving you 10 separate answers, the computer sends ‘207 Multi-Status’ which is like a report card! It lists each thing you asked for and tells you if it worked (got a checkmark) or failed (got an X). Some might have worked and some might have failed, so you need to read the list to see what happened to each one. It’s not just ‘success’ or ‘failure’ - it’s ‘here’s what happened to each thing!’

Example: You try to copy 3 files to a folder. File 1 copies successfully (✓), File 2 already exists so it fails (X), File 3 copies successfully (✓). The computer sends ‘207 Multi-Status’ with a list: File1: Success, File2: Error - already exists, File3: Success.

Jr. Developer

HTTP 207 Multi-Status is used for operations affecting multiple resources where each resource might have a different outcome. It’s primarily a WebDAV status code (RFC 4918) for file operations on collections, but also appears in modern batch APIs and GraphQL. The response body is XML (for WebDAV) or JSON (for REST APIs) containing individual status codes for each sub-resource. Unlike other 2xx codes, 207 doesn’t mean everything succeeded - it means ‘I processed your request, check the body for individual results.’ Some items might be 200 OK, others 404 Not Found, others 403 Forbidden. Use cases: PROPFIND (getting properties of multiple files), COPY/MOVE operations affecting collections, batch API requests (creating multiple resources), partial update scenarios where some succeed and some fail. The client must parse the response body to determine the outcome for each resource. Don’t use 207 for single-resource operations - that’s what regular status codes are for.

Example: A client sends a PROPFIND request to /documents/ to get metadata for all files in a folder. Some files exist, some don’t, some the user lacks permissions for. The server returns 207 with XML listing each file: file1.txt (200 OK with properties), secret.txt (403 Forbidden), missing.txt (404 Not Found).

Code Example

// Express.js batch API returning 207
app.post('/api/batch/users', async (req, res) => {
const { operations } = req.body; // Array of user creation requests
const results = [];
for (const op of operations) {
try {
const user = await db.createUser(op.data);
results.push({
id: op.id,
status: 201,
message: 'Created',
data: user
});
} catch (error) {
if (error.code === 'DUPLICATE_EMAIL') {
results.push({
id: op.id,
status: 409,
message: 'Email already exists'
});
} else {
results.push({
id: op.id,
status: 400,
message: error.message
});
}
}
}
// Return 207 with individual results
res.status(207).json({ results });
});

Crash Course

207 Multi-Status, defined in RFC 4918 for WebDAV, indicates that the response conveys information about multiple resources in situations where multiple status codes might be appropriate. The response body contains a multistatus XML element (for WebDAV) or structured JSON/XML (for REST APIs) with individual status codes for each affected resource. This is fundamentally different from other 2xx codes: 200 OK means unconditional success, while 207 means ‘partial success - inspect the body for details.’ Each resource in the response has its own status (200, 201, 404, 403, 409, etc.) and optional error message. WebDAV operations using 207: PROPFIND (retrieve properties of files/collections), COPY/MOVE (copy/move collections with some items failing), DELETE on collections (some deletions succeed, others fail), PROPPATCH (set properties on multiple resources). Modern REST API usage: Batch operations (create/update multiple resources in one request), Partial updates (update array of items, some succeed, some fail), GraphQL queries (multiple queries in one request, each with status). Response structure (WebDAV XML): multistatus root element, response elements for each resource with href and status or propstat subelements. Response structure (REST JSON): results array with status, message, and data for each operation. Caching: 207 responses are typically not cacheable since they represent transient operation outcomes. Don’t use 207 for all-success or all-failure scenarios - use 200/201 for all success, appropriate error code for all failure. 207 is specifically for mixed outcomes.

Example: A file sync client sends COPY /source-folder/ to /destination-folder/ with Depth: infinity to copy an entire directory tree. The tree contains 100 files. 95 files copy successfully (200 OK), 3 files already exist at destination (412 Precondition Failed), 2 files user lacks read permissions for (403 Forbidden). The server returns 207 Multi-Status with XML body listing all 100 files and their individual statuses. The client parses the response to show which files copied and which failed, allowing retry of failed items.

Code Example

// Production batch API with 207
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();
app.post('/api/batch',
body('operations').isArray({ min: 1, max: 100 }),
body('operations.*.method').isIn(['POST', 'PUT', 'PATCH', 'DELETE']),
body('operations.*.path').isString(),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { operations } = req.body;
const results = [];
for (const op of operations) {
try {
let result;
switch (op.method) {
case 'POST':
result = await handlePost(op.path, op.body);
results.push({
id: op.id,
status: 201,
message: 'Created',
data: result,
location: `${op.path}/${result.id}`
});
break;
case 'PUT':
result = await handlePut(op.path, op.body);
results.push({
id: op.id,
status: 200,
message: 'Updated',
data: result
});
break;
case 'DELETE':
await handleDelete(op.path);
results.push({
id: op.id,
status: 204,
message: 'Deleted'
});
break;
}
} catch (error) {
// Map errors to appropriate status codes
let status = 500;
if (error.name === 'NotFoundError') status = 404;
if (error.name === 'ValidationError') status = 400;
if (error.name === 'ConflictError') status = 409;
if (error.name === 'ForbiddenError') status = 403;
results.push({
id: op.id,
status,
message: error.message,
error: error.name
});
}
}
// Return 207 Multi-Status
res.status(207).json({
multistatus: true,
results,
summary: {
total: results.length,
succeeded: results.filter(r => r.status >= 200 && r.status < 300).length,
failed: results.filter(r => r.status >= 400).length
}
});
}
);
async function handlePost(path, data) {
// Implementation...
}
async function handlePut(path, data) {
// Implementation...
}
async function handleDelete(path) {
// Implementation....

Deep Dive

The 207 Multi-Status status code, defined in RFC 4918 Section 11.1 (HTTP Extensions for Web Distributed Authoring and Versioning - WebDAV), indicates that the message body that follows is by default a text/xml or application/xml HTTP entity with a multistatus root element. The multistatus element contains multiple response elements, each describing the status of an individual resource affected by the request. Per the RFC: ‘The 207 (Multi-Status) status code provides status for multiple independent operations.’ Unlike standard 2xx codes which indicate unqualified success, 207 is semantically neutral - it means ‘I processed your batch request, examine the body for individual outcomes.’ A 207 response might contain entirely successful operations (all 200/201), entirely failed operations (all 404/403), or mixed results.

Technical Details

WebDAV multistatus response structure follows a specific XML schema defined in RFC 4918. The root multistatus element contains one or more response elements. Each response element includes: href (the resource URI), status (overall status for the resource, e.g., ‘HTTP/1.1 200 OK’), or propstat elements for property-specific status (used with PROPFIND/PROPPATCH where individual properties might succeed or fail independently). The propstat element contains prop (the properties), status (status for these properties), and optional responsedescription (human-readable explanation). Example structure:\nxml\n<D:multistatus xmlns:D=\"DAV:\">\n <D:response>\n <D:href>/file1.txt</D:href>\n <D:status>HTTP/1.1 200 OK</D:status>\n </D:response>\n <D:response>\n <D:href>/file2.txt</D:href>\n <D:status>HTTP/1.1 404 Not Found</D:status>\n </D:response>\n</D:multistatus>\n\n\nDepth header interaction: WebDAV requests include Depth header (0, 1, or infinity) indicating operation scope. Depth: 0 affects only the target resource. Depth: 1 affects the target and its immediate children. Depth: infinity affects the entire subtree. For Depth: infinity PROPFIND on a large directory tree, the 207 response might enumerate thousands of resources, each with status and properties. This can cause enormous responses - servers may limit depth or return 507 Insufficient Storage.\n\nModern REST API adoption: While 207 originated with WebDAV, it’s increasingly used in REST APIs for batch operations. JSON response format (unofficial but common): {“results”: [{“id”: 1, “status”: 201, “data”: {…}}, {“id”: 2, “status”: 400, “error”: ”…”}]}. This provides structured individual results without XML overhead. GraphQL uses similar patterns - multiple queries in one request, each with status/data/errors.\n\nCaching semantics: RFC 4918 doesn’t explicitly address caching of 207 responses, and RFC 9111 (HTTP Caching) doesn’t mention 207. In practice, 207 responses are not cacheable - they represent transient operation outcomes, not resource representations. Adding Cache-Control: no-store is prudent to prevent accidental caching.\n\nStatus code selection for individual resources follows standard HTTP semantics: 200 OK for successful GET/property retrieval, 201 Created for successful resource creation, 204 No Content for successful deletion, 403 Forbidden for permission errors, 404 Not Found for missing resources, 409 Conflict for constraints violations, 422 Unprocessable Entity for validation errors, 423 Locked for WebDAV locked resources, 507 Insufficient Storage for quota exceeded.\n\nError handling and partial rollback: Unlike database transactions with automatic rollback, 207 batch operations typically commit each sub-operation independently. If operation 5 of 10 fails, operations 1-4 remain committed. This is ‘best-effort’ semantics. True transactional batch semantics require either custom implementation (track operations, rollback on failure) or appl…

Code Example

// Enterprise WebDAV-style PROPFIND implementation\nconst express = require('express');\nconst xml2js = require('xml2js');\nconst fs = require('fs').promises;\nconst path = require('path');\nconst app = express();\n\napp.propfind = (path, handler) => app.route(path).all((req, res, next) => {\n if (req.method === 'PROPFIND') handler(req, res);\n else next();\n});\n\napp.propfind('/webdav/*', async (req, res) => {\n const resourcePath = req.params[0] || '/';\n const depth = parseInt(req.headers.depth || '0');\n const basePath = path.join(__dirname, 'webdav', resourcePath);\n \n // Parse request body for requested properties\n const requestBody = await parseXML(req.body);\n const requestedProps = extractRequestedProps(requestBody);\n \n try {\n const responses = [];\n \n // Collect resources based on Depth\n const resources = await collectResources(basePath, depth);\n \n for (const resource of resources) {\n const response = await buildResourceResponse(resource, requestedProps);\n responses.push(response);\n }\n \n // Build 207 Multi-Status XML response\n const multistatusXML = buildMultistatusXML(responses);\n \n res.status(207)\n .set('Content-Type', 'application/xml; charset=utf-8')\n .send(multistatusXML);\n \n } catch (error) {\n console.error('PROPFIND error:', error);\n res.status(500).send('Internal Server Error');\n }\n});\n\nasync function collectResources(basePath, depth) {\n const resources = [];\n \n async function collect(currentPath, currentDepth) {\n try {\n const stat = await fs.stat(currentPath);\n resources.push({ path: currentPath, stat });\n \n if (stat.isDirectory() && currentDepth < depth) {\n const entries = await fs.readdir(currentPath);\n for (const entry of entries) {\n await collect(path.join(currentPath, entry), currentDepth + 1);\n }\n }\n } catch (error) {\n // Resource doesn't exist or no permission\n resources.push({ path: currentPath, error });\n }\n }\n \n await collect(basePath, 0);\n return resources;\n}\n\nasync function buildResourceResponse(resource, requestedProps) {\n const relativePath = resource.path.replace(__dirname + '/webdav', '');\n \n if (resource.error) {\n // Resource error - 404 or 403\n const status = resource.error.code === 'ENOENT' ? 404 : 403;\n return {\n href: relativePath,\n status: `HTTP/1.1 ${status} ${status === 404 ? 'Not Found' : 'Forbidden'}`\n };\n }\n \n // Build propstat for requested properties\n const propstats = [];\n const foundProps = {};\n const notFoundProps = [];\n \n for (const prop of requestedProps) {\n if (prop === 'getcontentlength') {\n foundProps.getcontentlength = resource.stat.size;\n } else if (prop === 'getlastmodified') {\n foundProps.getlastmodified = resource.stat.mtime.toUTCString();\n } else if (prop === 'resourcetype') {\n foundProps.resourcet...

Frequently Asked Questions

Does 207 Multi-Status mean all operations succeeded?

No! 207 means 'I processed your batch request - check the body for individual results.' Some operations might have succeeded (200, 201) while others failed (404, 403, 409). You must parse the response body to see what actually happened to each resource. Don't treat 207 as unconditional success like 200 OK.

When should I use 207 instead of 200 or 400?

Use 207 for operations affecting multiple resources where individual outcomes may differ. If all operations succeeded, use 200/201. If all failed for the same reason, use appropriate error code (400, 403, 404). Use 207 only for mixed results where some succeeded and some failed. Don't use 207 for single-resource operations.

Is 207 only for WebDAV, or can I use it in REST APIs?

207 originated with WebDAV (RFC 4918) but is increasingly used in modern REST APIs for batch operations. You can use JSON instead of XML for the response body. Common in batch create/update/delete endpoints, GraphQL multi-query responses, and partial update scenarios. Just ensure clients can parse the structured response.

How do I handle partial failures in 207 responses?

Parse the response body to identify which operations succeeded and which failed. For failed operations, extract the status code and error message. You can then retry only the failed operations, display errors to users for manual correction, or log failures for investigation. Unlike database transactions, 207 operations typically don't auto-rollback on partial failure.

Should 207 responses be cacheable?

No, 207 responses represent transient operation outcomes, not resource representations. They shouldn't be cached. Include Cache-Control: no-store to prevent caching. Each 207 response is specific to a particular batch request at a particular time - caching it would make no sense.

Common Causes

  • WebDAV PROPFIND request retrieving properties of multiple files
  • WebDAV COPY/MOVE operation on collections with mixed success
  • WebDAV DELETE on directory where some files succeed, others fail
  • REST API batch endpoint creating multiple resources (some succeed, some fail)
  • GraphQL multi-query request with individual success/failure per query
  • Batch update operation where some items validate, others don’t
  • Partial import where some rows succeed, others have validation errors

Implementation Guidance

  • Server-side: Only use 207 for operations affecting multiple resources
  • Server-side: Include individual status code for each resource in response body
  • Server-side: Use XML (multistatus element) for WebDAV or JSON (results array) for REST
  • Server-side: Include descriptive error messages for failed operations
  • Server-side: Add summary (total, succeeded, failed counts) for client convenience
  • Server-side: Set Cache-Control: no-store to prevent caching
  • Server-side: Enforce batch size limits (max 100 operations) to prevent abuse
  • Client-side: Don’t treat 207 as unconditional success - parse the body!
  • Client-side: Extract individual status codes and handle each result separately
  • Client-side: Implement retry logic for only the failed operations
  • Client-side: Display user-friendly error messages for failed items
  • For all-success scenarios, use 200/201/204 instead of 207
  • For all-failure scenarios, use appropriate error code (400, 404) instead of 207
📚 Sources:
🕐 Last updated: January 5, 2026

Comments