You generated a post, wrote the content, and shipped it to WordPress through the REST API. The post goes live. The featured image slot sits empty. Now your post looks unfinished in search results, social shares, and your blog archive, all because the image upload step is documented nowhere authoritative and the top Google results point to a 2016 guide for a deprecated client library.
Setting a featured image via the WordPress REST API requires three sequential API calls in a specific order: upload the image as raw binary to the media endpoint, create or update the post, then assign the media ID to the post’s featured_media field. Most developers get tripped up on step one because the media endpoint expects a raw binary body with a Content-Disposition header, not multipart form data. Here is the exact sequence, the common errors, and working code in curl, Python, and n8n.
The Three Calls, In Order
The WordPress REST API does not let you attach an image file and create a post in a single request. The featured image workflow is strictly sequential: upload first, capture the media ID from the response, then use that ID when creating or updating the post. Trying to shortcut this by passing a file URL or base64 string in the post body does not work.
| Step | Endpoint | Method | Key Input | What You Capture |
|---|---|---|---|---|
| 1. Upload image | /wp/v2/media | POST | Raw binary body + Content-Disposition header | id from response JSON |
| 2. Create or update post | /wp/v2/posts or /wp/v2/posts/{id} | POST or PUT | Post content, title, categories, tags | Post id |
| 3. Set featured_media | /wp/v2/posts/{id} | POST | {"featured_media": media_id} | Updated post object with featured_media set |
Steps 2 and 3 can be combined into a single call: if you already have the post ID, you can send a POST to /wp/v2/posts/{id} with featured_media in the body. But if you are creating a new post, you must create it first (step 2), get the post ID, then update it with the featured media ID (step 3). Alternatively, you can upload the image first, then pass featured_media in the initial post creation body, combining steps 2 and 3 into one call since you already have the media ID.
The most reliable pattern: upload image, then create the post with featured_media included in the same request body. That is two calls, not three. You only need three calls if the post already exists and you are updating it.
Step 1: Upload to /wp/v2/media

The media endpoint expects the image file as a raw binary body, not as multipart form data. This is the single most common mistake. If you send multipart/form-data, WordPress will accept the request but the file may not be processed correctly, or you will get a rest_upload_no_content_disposition error if the Content-Disposition header is missing or malformed.
Three headers are required for this call:
Content-Disposition: attachment; filename="my-image.jpg"tells WordPress the original filename and extension. The filename must include a valid extension (jpg, png, gif, webp, etc.) or the upload will fail.Content-Type: image/jpegshould match the actual file type. If you omit this, WordPress will try to infer it from the Content-Disposition filename, but explicit is safer.Authorization: Basic ...or your application password credentials. Media upload requires an authenticated user with theupload_filescapability.
curl Example: Upload Image
Here is the minimal curl command to upload an image and capture the media ID:
curl -X POST "https://yoursite.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=hero-image.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/hero-image.jpg
The response is a JSON object. The field you need is id, which is the numeric media item ID. Save it for steps 2 and 3.
Critical: Use –data-binary, Not -d or –data
The -d flag in curl sends data as application/x-www-form-urlencoded, which strips binary data. You must use --data-binary @filename to send the raw file bytes unchanged. Using -d @filename will corrupt the upload silently, and the image will either fail to process or arrive damaged.
Step 2: Create or Update the Post
Once you have the media ID from step 1, creating a post with a featured image is a single POST to /wp/v2/posts with featured_media included in the JSON body alongside your title, content, and other fields.
curl Example: Create Post with Featured Image
curl -X POST "https://yoursite.com/wp-json/wp/v2/posts"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{
"title": "How to Choose a Knee Surgeon in Atlanta",
"content": "<p>Your post content here.</p>",
"status": "draft",
"categories": [5],
"featured_media": 142
}'
If the post already exists and you just need to attach the image, POST to /wp/v2/posts/{post_id} with only the featured_media field:
curl -X POST "https://yoursite.com/wp-json/wp/v2/posts/88"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{"featured_media": 142}'
Step 3: Set featured_media
If you included featured_media in the post creation body (step 2), this step is already done. The separate step matters only when you are updating an existing post or when your workflow uploads the image after post creation.
Send a POST request to /wp/v2/posts/{post_id} with a JSON body containing {"featured_media": media_id}. The response returns the full post object. Verify that featured_media in the response matches the ID you sent. If it returns 0, the media ID was invalid or the media item does not exist.
Setting Alt Text (Do Not Skip This)
The WordPress REST API lets you set alt text on the media item via the alt_text field. This is a separate call to /wp/v2/media/{media_id} after the upload. Alt text is an SEO requirement, not a nice-to-have: Google uses it to understand image content, screen readers depend on it for accessibility, and it appears in image search results.
curl Example: Set Alt Text
curl -X POST "https://yoursite.com/wp-json/wp/v2/media/142"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"}'
You can also include alt_text in the initial upload request body, but only if you send it as a JSON field alongside the binary body. Since the upload uses a raw binary body (not JSON), the cleaner approach is a follow-up call to update the media item. Some developers send alt text as a query parameter on the upload (?alt_text=...), but the POST update to /wp/v2/media/{id} is the documented method and is more reliable across WordPress versions.
Good alt text describes the image specifically and includes relevant keywords naturally. “Knee surgeon examining patient X-ray” is useful. “Image” or “photo” is not. For a deeper look at on-page optimization including image SEO, see our Yoast SEO plugin review which covers how SEO plugins handle image alt text validation.
Common Errors and Fixes

| Error | Cause | Fix |
|---|---|---|
rest_upload_no_content_disposition | Missing or malformed Content-Disposition header | Ensure header is attachment; filename="file.jpg" with a valid extension |
rest_invalid_param on media upload | Content-Type header does not match the actual file | Set Content-Type to match the file (image/jpeg, image/png, etc.) |
rest_upload_invalid_disposition | Filename in Content-Disposition has no extension or invalid characters | Use a simple filename with a standard extension: image.jpg, not image or im age.jpg |
rest_forbidden or 401 on media upload | User lacks upload_files capability or credentials are wrong | Use an Author or higher role with application passwords; verify credentials |
rest_upload_file_too_large | File exceeds server upload limit (default 2MB on many hosts) | Check upload_max_filesize and post_max_size in php.ini; compress the image |
rest_upload_unknown_error | File type not permitted by WordPress or server-level restrictions | Check allowed MIME types; WordPress blocks some file types by default |
featured_media returns 0 after POST | Media ID does not exist or was deleted | Verify the media ID from the upload response; re-upload if necessary |
| Image appears corrupted or will not process | Used -d instead of --data-binary in curl, or sent multipart form data | Use raw binary body with --data-binary @file; do not wrap in form-data |
Multipart vs Raw Binary: Why It Matters
Many upload examples online show multipart form data with a file field. The WordPress REST API media endpoint does accept multipart uploads, but the raw binary approach is simpler and more reliable because it avoids boundary parsing issues and works consistently across hosting environments. With raw binary, the entire request body is the file content, and the Content-Disposition header provides the filename metadata.
File Type Restrictions
WordPress allows the following image types by default: jpg, jpeg, png, gif, webp, ico, and svg (SVG support depends on version and configuration). If you try to upload a file type not in the allowed list, you will get rest_upload_unknown_error or a Sorry, you are not allowed to upload this file type message. The allowed types are controlled by wp_get_mime_types() and can be filtered by plugins or server-level configurations. Check with your hosting provider if a standard image type is being rejected.
Upload Size Limits
The upload size limit is governed by two PHP settings: upload_max_filesize and post_max_size. Many shared hosts default to 2MB or 8MB. If your image exceeds this, the server returns a 413 error or a WordPress rest_upload_file_too_large response. 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.
Permission Errors on Media Upload
Media upload requires the upload_files capability. The Contributor role does not have this capability by default. Authors, Editors, and Administrators can upload files. If you are using application passwords for authentication, ensure the WordPress user account has at least the Author role. If you get a 403 or rest_forbidden error, the user account is the first thing to check.
Related: Invalid parameter(s): categories
If you are setting a featured image, you are probably also assigning categories and tags in the same request. The Invalid parameter(s): categories error is the second most common REST API failure after image upload issues, and developers hit both together because they appear in the same post creation call.
The error occurs when you pass category values that WordPress cannot resolve to valid term IDs. There are three causes:
- Passing category names instead of IDs. The
categoriesfield expects an array of integer term IDs, not slugs or names."categories": [5, 12]is correct."categories": ["News", "Updates"]fails. - Passing IDs for categories that do not exist or belong to a different taxonomy. If you pass an ID from a custom taxonomy, it will be rejected. The
categoriesfield maps to thecategorytaxonomy only. - Passing string IDs instead of integers.
"categories": ["5"]may fail depending on WordPress version. Always use integers:"categories": [5].
How to Fix It
- List your categories first with a GET request to
/wp/v2/categoriesto capture the correct term IDs. - Pass only integer IDs in the
categoriesarray. If you need to create a new category, POST to/wp/v2/categoriesfirst, capture the returned ID, then use it in the post creation call. - For custom taxonomies, use the
/wp/v2/{taxonomy_slug}endpoint to list or create terms, then pass them in the post body under the taxonomy’s REST field name (not undercategories).
If you are building an automated content pipeline and need to handle category creation and assignment programmatically, the same REST API patterns apply. Our guide on programmatic SEO on WordPress covers the broader publishing workflow for bulk content, including how to structure taxonomy assignment at scale.
Complete Examples: curl, Python, n8n

Full curl Workflow (Upload + Create Post + Set Alt Text)
This three-command sequence uploads the image, creates the post with the featured image attached, and sets alt text on the media item. Replace the URL, credentials, and file path with your own.
# Step 1: Upload the image, capture media ID
RESPONSE=$(curl -s -X POST "https://yoursite.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=hero-image.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/hero-image.jpg)
MEDIA_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Media ID: $MEDIA_ID"
# Step 2: Create the post with featured_media
POST_RESPONSE=$(curl -s -X POST "https://yoursite.com/wp-json/wp/v2/posts"
-u "user:app-password"
-H "Content-Type: application/json"
-d "{
"title": "How to Choose a Knee Surgeon in Atlanta",
"content": "<p>Post content here.</p>",
"status": "draft",
"categories": [5],
"featured_media": $MEDIA_ID
}")
# Step 3: Set alt text on the media item
curl -s -X POST "https://yoursite.com/wp-json/wp/v2/media/$MEDIA_ID"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"}'
Python (requests) Full Workflow
The Python version uses the requests library. The key detail: the file is sent as data=file_content (raw binary), not as files={...} (which triggers multipart form data).
import requests
import json
BASE_URL = "https://yoursite.com/wp-json/wp/v2"
AUTH = ("user", "app-password")
# Step 1: Upload image as raw binary
image_path = "/path/to/hero-image.jpg"
with open(image_path, "rb") as f:
image_data = f.read()
headers = {
"Content-Disposition": "attachment; filename=hero-image.jpg",
"Content-Type": "image/jpeg"
}
resp = requests.post(
f"{BASE_URL}/media",
headers=headers,
auth=AUTH,
data=image_data # raw binary, NOT files={...}
)
media_id = resp.json()["id"]
print(f"Uploaded media ID: {media_id}")
# Step 2: Create post with featured_media
post_data = {
"title": "How to Choose a Knee Surgeon in Atlanta",
"content": "<p>Post content here.</p>",
"status": "draft",
"categories": [5],
"featured_media": media_id
}
resp = requests.post(
f"{BASE_URL}/posts",
json=post_data,
auth=AUTH
)
post_id = resp.json()["id"]
print(f"Created post ID: {post_id}")
# Step 3: Set alt text
alt_resp = requests.post(
f"{BASE_URL}/media/{media_id}",
json={"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"},
auth=AUTH
)
print(f"Alt text set: {alt_resp.json()['alt_text']}")
n8n / Make Configuration
In n8n, the workflow uses three HTTP Request nodes connected in sequence. The critical configuration is in the first node: you must set the body type to “Binary” and reference the binary property that holds the file data, not use the default JSON or Form-Data body type.
Node 1: HTTP Request (Upload Image)
- Method: POST
- URL:
https://yoursite.com/wp-json/wp/v2/media - Authentication: Basic Auth (username + application password)
- Headers:
Content-Disposition: attachment; filename=hero-image.jpgandContent-Type: image/jpeg - Body Type: Binary Data (this is the critical setting)
- Binary Property:
data(or whichever property holds the file from the previous node, such as a Read Binary File node) - Capture output: The JSON response includes
id. Store it in a variable or pass it to the next node via{{$json.id}}.
Node 2: HTTP Request (Create Post)
- Method: POST
- URL:
https://yoursite.com/wp-json/wp/v2/posts - Authentication: Basic Auth (same as node 1)
- Body Type: JSON
- Body:
{"title": "How to Choose a Knee Surgeon in Atlanta", "content": "<p>Post content here.</p>", "status": "draft", "categories": [5], "featured_media": {{$json.id}}}. The{{$json.id}}expression pulls the media ID from node 1’s response.
Node 3: HTTP Request (Set Alt Text)
- Method: POST
- URL:
https://yoursite.com/wp-json/wp/v2/media/{{$node["Node 1"].json.id}} - Body Type: JSON
- Body:
{"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"}
Make.com (Integromat) Configuration
In Make.com, the setup is similar. Use a “Create a post” WordPress module for step 2, which natively supports a “Featured Media ID” field. For step 1, use an HTTP module with the binary payload. Make.com’s WordPress app module does not expose the media upload with raw binary control, so the HTTP module is required for the upload step. The native “Upload Media” module in Make’s WordPress app may work, but verify that it sends the Content-Disposition header correctly. If it fails, fall back to the raw HTTP module.
If you are building this workflow because you are tired of manually uploading images and copy-pasting content between tools, that is exactly the problem ClearPost was built to solve. ClearPost handles the image 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 manual API calls, no curl scripts to maintain, no missing featured images on published posts.
Frequently Asked Questions
See the FAQ section below for answers to common questions about setting featured images via the WordPress REST API.
The featured image workflow comes down to three things: send raw binary (not form data), include a valid Content-Disposition header with a filename and extension, and use the returned media ID in the featured_media field. Get those three right and the rest follows. For a complete look at how automated content pipelines fit into your SEO strategy, see our analysis of WordPress content scheduling tools and how they compare on automation and publishing control.
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.
Frequently Asked Questions
Can I set a featured image and create a post in a single REST API call?
No. You must first upload the image to /wp/v2/media (capturing the returned media ID), then include that ID in the featured_media field when creating or updating the post. The upload and post creation are always separate requests. However, you can combine the post creation and featured_media assignment into one call if you already have the media ID.
Why do I get rest_upload_no_content_disposition when uploading media?
This error means the Content-Disposition header is missing or malformed. The header must follow the format: attachment; filename=”your-image.jpg”. The filename must include a valid file extension (jpg, png, gif, webp). Without this header, WordPress cannot determine the filename and rejects the upload.
Should I send multipart form data or raw binary to the WordPress media endpoint?
Raw binary is the recommended approach. Send the file contents directly as the request body with Content-Disposition and Content-Type headers. Multipart form data can work but introduces boundary parsing issues and is less reliable across hosting environments. In curl, use –data-binary @filename, not -d or -F.
How do I set alt text on a featured image via the REST API?
After uploading, send a POST request to /wp/v2/media/{media_id} with a JSON body containing the alt_text field. For example: {“alt_text”: “Description of the image”}. Alt text is required for SEO and accessibility, and should describe the image specifically rather than using generic placeholders like “image” or “photo”.
What causes ‘Invalid parameter(s): categories’ when creating a post via REST API?
This error occurs when you pass category names or slugs instead of integer term IDs, when the IDs do not exist in the category taxonomy, or when string IDs are used instead of integers. List categories first via GET /wp/v2/categories to find valid IDs, then pass them as an array of integers in the categories field.
