Your POST to /wp-json/wp/v2/media returns an error. Authentication works, post creation works, taxonomy assignment works, but the image upload fails. You have tried three curl variations, read the WordPress REST API documentation twice, and the media endpoint still throws an error. The media upload is the most fragile step in the WordPress REST API content pipeline because it depends on HTTP headers, PHP configuration, file permissions, and security plugins all cooperating at the same time.
Here is the short version: a WordPress REST API media upload error has six common causes, and five of them take under five minutes to fix. The diagnostic order below walks through each cause from most likely to least likely, with the exact curl command to confirm it and the specific fix to resolve it. If your post creation call also fails with rest_cannot_create, the problem is authentication or capabilities, not the media endpoint. See our guide on fixing rest_cannot_create errors before troubleshooting the media endpoint specifically.
Quick Diagnostic: WordPress REST API Media Upload Error
Run this table top to bottom. Match the error response you are getting to the likely cause, then jump to the corresponding section for the fix and the curl command to confirm it.
| Symptom | HTTP Status | Likely Cause | Jump To |
|---|---|---|---|
| rest_upload_no_content_disposition | 400 | Missing Content-Disposition header | Cause 1 |
| rest_upload_no_content_type | 400 | Missing Content-Type header | Cause 2 |
| rest_upload_invalid_disposition | 400 | Malformed Content-Disposition header | Cause 1 |
| 413 or empty response body | 413 | PHP upload_max_filesize or post_max_size | Cause 3 |
| Sorry, you are not allowed to upload this file type | 400 or 403 | File type not in allowed MIME list | Cause 4 |
| rest_upload_file_error or 500 | 500 | Directory permissions on wp-content/uploads | Cause 5 |
| 403 with valid credentials and headers | 403 | Security plugin blocking uploads | Cause 6 |
Cause 1: Missing Content-Disposition Header

This is the most common cause. The media endpoint requires a Content-Disposition header that tells WordPress the original filename and file extension. Without it, WordPress cannot determine the filename or extension and returns rest_upload_no_content_disposition with HTTP 400 and the message “No Content-Disposition supplied.” This error accounts for the majority of media upload failures because most API integrations omit the header or format it incorrectly.
The header must follow this exact format:
Content-Disposition: attachment; filename="your-image.jpg"
The filename must include a valid file extension: jpg, png, gif, webp, or another type in the WordPress allowed list. If the header is present but the filename cannot be parsed from it, WordPress returns rest_upload_invalid_disposition instead, with the message “Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as attachment; filename=”image.png” or similar.”
How to Confirm It
Send the upload without the Content-Disposition header to reproduce the error:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/image.jpg
If you get rest_upload_no_content_disposition, this is your cause. Now send the same request with the Content-Disposition header added:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=image.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/image.jpg
If this succeeds, the missing header was the only problem. If it still fails, check the error code in the response body to identify the next cause.
The Fix
Add the Content-Disposition header to every media upload request. The format is Content-Disposition: attachment; filename=”filename.ext”. The filename must include a valid extension. “image.jpg” works. “image” without an extension does not. Avoid special characters or spaces in the filename. Use hyphens instead of spaces.
One detail that trips up developers using HTTP client libraries: some libraries do not send custom headers unless you explicitly set them. In Python requests, pass the header in the headers dict. In JavaScript fetch, pass it in the headers option. In curl, use the -H flag. The header name is case-insensitive, but the value format must match the expected pattern exactly. Also, make sure you are using –data-binary to send the file as raw bytes, not -d (which sends application/x-www-form-urlencoded and strips binary data) or -F (which sends multipart form data and can trigger boundary parsing issues).
Cause 2: Wrong or Missing Content-Type
The media endpoint also requires a Content-Type header that matches the actual file being uploaded. If you omit it entirely, WordPress returns rest_upload_no_content_type with HTTP 400 and the message “No Content-Type supplied.” If you send the wrong type, WordPress may accept the request but fail to process the file correctly, or it may reject it depending on how the server validates the content type against the file extension in Content-Disposition.
The Content-Type must match the file being uploaded. For common image formats:
- JPEG: Content-Type: image/jpeg
- PNG: Content-Type: image/png
- GIF: Content-Type: image/gif
- WebP: Content-Type: image/webp
How to Confirm It
Send the upload with a deliberately wrong Content-Type to see how the response differs:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=image.jpg"
-H "Content-Type: application/octet-stream"
--data-binary @/path/to/image.jpg
Compare the error response with the correct Content-Type test:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=image.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/image.jpg
If the second request succeeds and the first fails or produces a different error, the Content-Type header was the problem.
The Fix
Always set Content-Type to match the actual file type. Do not rely on WordPress to infer it from the Content-Disposition filename. While WordPress can sometimes derive the type from the extension, explicit is safer and avoids edge cases where the inference fails or the extension does not match the actual file content.
If you are sending the file through a reverse proxy, CDN, or middleware that modifies headers, verify that the Content-Type header survives the hop. Some proxies strip or rewrite Content-Type headers for binary payloads, replacing them with application/octet-stream or removing them entirely. This is a common cause of failures when the upload works locally but fails in production through a CDN.
Cause 3: PHP upload_max_filesize and post_max_size Limits

The file exceeds PHP’s server-level upload limits. On single-site WordPress, the REST API controller does not enforce its own file size limit. The limit comes from PHP’s upload_max_filesize and post_max_size settings, and from the web server’s own request body size configuration.
Many shared hosting environments default to 2MB for both settings. If your image exceeds this, the request body is truncated or rejected before it reaches WordPress. You may see a 413 Request Entity Too Large response from the web server, an empty response body, or rest_upload_no_data if PHP reads an empty body because post_max_size was exceeded.
The two PHP settings interact:
- upload_max_filesize: maximum size of a single uploaded file
- post_max_size: maximum size of the entire POST body, which must be greater than or equal to upload_max_filesize
For raw binary uploads via the REST API, the file content is the entire POST body, so post_max_size is the binding constraint. Nginx also has its own limit: client_max_body_size, which defaults to 1MB. If the request body exceeds this, Nginx returns 413 before the request ever reaches PHP.
How to Confirm It
Upload a small file under 100KB with the same headers and credentials. If the small file succeeds but a larger file fails, the size limit is your cause:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=tiny-test.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/tiny-test.jpg
To check your current PHP limits from the command line using WP-CLI:
wp eval 'echo ini_get("upload_max_filesize") . "n" . ini_get("post_max_size");'
The Fix
Increase both limits in php.ini:
upload_max_filesize = 64M
post_max_size = 64M
Or in .htaccess for Apache:
php_value upload_max_filesize 64M
php_value post_max_size 64M
For Nginx, also increase client_max_body_size in your server block, then reload:
client_max_body_size 64m;
The practical fix that does not require server access: compress images before uploading. A 1920px wide JPEG at 80% quality is typically 150 to 400KB, well within any limit. Tools like ImageOptim, TinyPNG, or Squoosh handle this in seconds. If your workflow involves automated uploads from a content pipeline, compress images as a preprocessing step so you never hit the server limit.
Cause 4: File Type Not in Allowed MIME List
WordPress restricts which file types can be uploaded through a MIME type allowlist defined in the wp_get_mime_types() function. The default list includes common image formats (jpg, jpeg, png, gif, bmp, tiff, webp, avif, ico), plus video, audio, and document formats like PDF, according to the WordPress developer documentation. If you try to upload a file type not in the list, WordPress rejects the upload.
The error you get depends on how the rejection happens. You may see “Sorry, you are not allowed to upload this file type” or a generic rest_upload_unknown_error. The get_allowed_mime_types() function also removes certain types by default: exe and swf are always removed, and html and js are removed for users without the unfiltered_html capability.
How to Confirm It
Upload a file with a disallowed extension, such as .svg or .txt:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=test.svg"
-H "Content-Type: image/svg+xml"
--data-binary @/path/to/test.svg
If you get a rejection error, rename the same file to .jpg and retry. If the .jpg version succeeds but the .svg version fails, the MIME type restriction is your cause.
The Fix
For standard image types, make sure the Content-Type header matches the file extension in Content-Disposition. WordPress cross-references the extension from Content-Disposition against the allowed MIME types list. A mismatch between the extension and the actual file content can cause rejection even for allowed types.
To add a custom file type to the allowed list, use the upload_mimes filter in a must-use plugin or your theme’s functions.php:
add_filter( 'upload_mimes', function( $mimes ) {
$mimes['svg'] = 'image/svg+xml';
return $mimes;
} );
For SVG specifically, consider the security implications. SVG files can contain embedded JavaScript and are a vector for XSS attacks. Only enable SVG uploads if you trust the source of the files, and consider using a dedicated SVG sanitization plugin rather than simply allowing the MIME type.
Cause 5: Directory Permissions on wp-content/uploads

The wp-content/uploads directory must be writable by the web server user. If the directory permissions are too restrictive, WordPress cannot write the uploaded file to the filesystem after processing it. The error manifests as a 500 status, rest_upload_file_error with the message “Could not open file handle,” or a generic rest_upload_unknown_error when the temp file write or the move to the uploads directory fails.
This cause is less common than the header issues but appears in specific scenarios: fresh server installations, after server migrations, after a permissions reset by a security plugin, or on hosting environments where the web server user differs from the file owner.
How to Confirm It
Upload a small, valid image file with all the correct headers:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=permission-test.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/small-valid.jpg
If you get a 500 error or rest_upload_file_error, and the same file uploads successfully through the WordPress admin media library (wp-admin then Media then Add New), the directory permissions are the cause. The REST API and the admin uploader use the same underlying file handling, so if one works and the other does not, check whether a security plugin is intercepting REST API requests specifically.
The Fix
Set the correct permissions on wp-content/uploads via SSH:
find /path/to/wordpress/wp-content/uploads -type d -exec chmod 755 {} ;
find /path/to/wordpress/wp-content/uploads -type f -exec chmod 644 {} ;
chown -R www-data:www-data /path/to/wordpress/wp-content/uploads
Replace www-data with the actual web server user. On Ubuntu and Debian, the default is www-data. On CentOS and RHEL, it is typically nginx or apache. Check with:
ps aux | grep -E 'nginx|apache|php-fpm' | grep -v grep
The first column shows the user the web server process runs as. If you are on managed hosting without SSH access, contact your hosting provider and ask them to verify that wp-content/uploads is writable by the web server user.
Cause 6: Security Plugins Blocking Uploads
Security plugins with WAF features can intercept media upload requests before they reach the WordPress media handler. The WAF sees a large binary payload with custom headers and flags it as suspicious, returning a 403 before the request reaches WordPress core. Wordfence, Solid Security (formerly iThemes Security), and All In One WP Security are the most common offenders.
This is the hardest cause to diagnose because the error response varies by plugin. You may see 403 Forbidden, 401 Unauthorized if the WAF strips auth headers before WordPress processes them, or a generic error page from the security plugin itself. The key signal: your curl command works with all the correct headers, authentication succeeds on other endpoints, but the media upload specifically returns a 403 or 401.
How to Confirm It
Temporarily deactivate every security plugin on your site and retry the upload with the same curl command:
curl -i -X POST "https://example.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=test.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/test.jpg
If the upload succeeds with security plugins disabled, reactivate them one by one, retesting after each activation, to identify which plugin is the blocker.
The Fix
For Wordfence: add the source IP address of your automation tool or server to the allowlist under Wordfence then All Options then Advanced Firewall Options then Allowlisted IP addresses. You can also check Wordfence then Tools then Live Traffic for blocked requests, which shows the rule that triggered and the option to allowlist the specific request. Alternatively, temporarily switch the WAF to Learning Mode (Wordfence then Manage WAF then Web Application Firewall Status), make your API calls, then switch back to Enabled and Protecting. This trains the WAF to recognize your legitimate upload requests.
For Solid Security: check Security then Settings then WordPress Tweaks for REST API restrictions. If “Restrict REST API access” is enabled, disable it or configure it to allow authenticated requests. Solid Security can also block rapid sequential requests through its brute force protection, so add your source IP to the allowlist if you are making multiple upload calls in quick succession.
For All In One WP Security: check WP Security then Application Firewall then REST API for restrictions that block non-cookie-authenticated requests. Application password requests use Basic Auth, not cookies, so the plugin may treat them as non-logged-in and block them. If the security plugin is stripping the Authorization header before it reaches WordPress, the request arrives as anonymous and fails. This is the same root cause as the application password header stripping issue we cover in a separate guide, which includes the .htaccess and Nginx fixes for passing the Authorization header through to PHP.
Diagnostic Summary
| Cause | Error Code | HTTP Status | Fix Time |
|---|---|---|---|
| Missing Content-Disposition | rest_upload_no_content_disposition | 400 | 2 min |
| Missing Content-Type | rest_upload_no_content_type | 400 | 2 min |
| Malformed Content-Disposition | rest_upload_invalid_disposition | 400 | 2 min |
| PHP size limit exceeded | 413 or empty body | 413 | 10 min |
| File type not allowed | rest_upload_unknown_error | 400 or 403 | 5 min |
| Directory permissions | rest_upload_file_error | 500 | 10 min |
| Security plugin blocking | 403 | 403 | 15 min |
The media upload comes down to three things: send the file as raw binary with –data-binary (not -d or -F), include a Content-Disposition header with a valid filename and extension, and set Content-Type to match the file type. Get those three right and the remaining causes are server configuration, which you can isolate with the curl commands above. For the full media upload workflow including post creation, featured image assignment, and alt text, see our guide on setting featured images via the WordPress REST API.
If you are building a content pipeline and tired of debugging API headers and server configuration for every site you connect to, ClearPost handles the media upload, post creation, featured image assignment, alt text, and SEO metadata in a single automated pipeline inside WordPress. You approve every post before it goes live. No curl scripts to maintain, no PHP ini values to track down, no missing Content-Disposition headers at 2 AM.
Try ClearPost free for 7 days. AI does the heavy lifting, you approve every post before it goes live. No long onboarding, no agency overhead, cancel anytime. See what 30 SEO-optimized posts a month looks like compared to the 4 you are getting now.
Frequently Asked Questions
Why does POST to /wp-json/wp/v2/media return rest_upload_no_content_disposition?
The Content-Disposition header is missing or malformed. The header must follow the format attachment; filename=”your-image.jpg” with a valid file extension. Without it, WordPress cannot determine the filename and rejects the upload with HTTP 400. This is the most common media upload error.
What is the correct way to upload a file to the WordPress REST API media endpoint?
Send the file as raw binary in the request body using –data-binary in curl, include a Content-Disposition header with the filename and extension, and set Content-Type to match the file type. The three required headers are Content-Disposition, Content-Type, and Authorization. Do not use -d (which strips binary data) or -F (which sends multipart form data and can cause boundary parsing issues).
Why does my media upload fail on production but work locally?
The production server likely has a lower PHP upload_max_filesize or post_max_size than your local environment, or Nginx’s client_max_body_size is set too low. Check both PHP values with wp eval ‘echo ini_get(“upload_max_filesize”);’ and increase them in php.ini or .htaccess. Also check client_max_body_size in your Nginx server block if applicable.
How do I fix the error “Sorry, you are not allowed to upload this file type” in the REST API?
The file type is not in WordPress’s allowed MIME types list. Standard image types like jpg, png, gif, and webp are allowed by default. For other types like SVG, add them via the upload_mimes filter in a must-use plugin or theme functions.php. Be aware that some file types, including SVG, carry security risks and should only be enabled with proper sanitization.
Can security plugins block WordPress REST API media uploads?
Yes. Security plugins with WAF features like Wordfence can intercept media upload requests with large binary payloads and custom headers, flagging them as suspicious and returning 403. Temporarily deactivate security plugins and retest. If the upload succeeds, add the source IP to the plugin’s allowlist or train the WAF using Learning Mode.
Frequently Asked Questions
Why does POST to /wp-json/wp/v2/media return rest_upload_no_content_disposition?
The Content-Disposition header is missing or malformed. The header must follow the format attachment; filename=”your-image.jpg” with a valid file extension. Without it, WordPress cannot determine the filename and rejects the upload with HTTP 400. This is the most common media upload error.
What is the correct way to upload a file to the WordPress REST API media endpoint?
Send the file as raw binary in the request body using –data-binary in curl, include a Content-Disposition header with the filename and extension, and set Content-Type to match the file type. Do not use -d (which strips binary data) or -F (which sends multipart form data and can cause boundary parsing issues).
Why does my media upload fail on production but work locally?
The production server likely has a lower PHP upload_max_filesize or post_max_size than your local environment, or Nginx’s client_max_body_size is set too low. Check both PHP values with wp eval and increase them in php.ini or .htaccess. Also check client_max_body_size in your Nginx server block.
How do I fix the error ‘Sorry, you are not allowed to upload this file type’ in the REST API?
The file type is not in WordPress’s allowed MIME types list. Standard image types like jpg, png, gif, and webp are allowed by default. For other types like SVG, add them via the upload_mimes filter in a must-use plugin or theme functions.php. Be aware that some file types carry security risks.
Can security plugins block WordPress REST API media uploads?
Yes. Security plugins with WAF features like Wordfence can intercept media upload requests with large binary payloads and custom headers, flagging them as suspicious and returning 403. Temporarily deactivate security plugins and retest. If the upload succeeds, add the source IP to the plugin’s allowlist.
