Publish to WordPress with the REST API and application passwords
Create a WordPress post with POST /wp-json/wp/v2/posts and an application password: working curl and JavaScript with categories and a featured image.
LLaunchScaler·Published ·9 min read
To create a WordPress post through the REST API, send an authenticated POST request to https://your-site/wp-json/wp/v2/posts with a JSON body holding title, content and status (draft or publish). Authenticate with an application password, built into WordPress since 5.6, sent as Basic auth over HTTPS from a dedicated Author or Editor account.
The full sequence below creates a draft with categories, tags and a featured image, first with curl and then as one JavaScript function you can drop into a script or a server.
What do you need before calling the REST API?
You need WordPress 5.6 or later, a site served over HTTPS, and a user account whose role can publish posts. The REST API lives at /wp-json/ when pretty permalinks are on. On a site still using plain permalinks, the same route goes in a query string instead: https://your-site/?rest_route=/wp/v2/posts.
Check four things before writing any code:
Open https://your-site/wp-json/ in a browser. You should see a large JSON document. A 404 or an HTML page means the REST API is blocked or permalinks need saving.
Look for the authentication key in that JSON. The Application Passwords integration guide says that if the key is empty, application passwords are not available, "perhaps because the request is not over https:// or it has been intentionally disabled."
Confirm the site loads over https:// without redirects to .
Questions, answered
What people ask about this
01
How do I create a post with the WordPress REST API?
Send a POST request to https://your-site/wp-json/wp/v2/posts with a JSON body containing title, content and status, authenticated with Basic auth using a username and an application password. WordPress answers 201 Created with the new post's id and link.
02
Where do I create a WordPress application password?
Decide which user will own the integration (the next section covers roles).
If the Application Passwords section is missing from the profile screen, stop here and read why WordPress application passwords are not showing, because none of the requests below will authenticate until it appears.
Which user role should the integration use?
Create a separate WordPress user for the integration instead of using your own administrator login. An Author can create, publish and upload media for its own posts, which covers most publishing. Choose Editor only if the integration must create categories or keep HTML that WordPress filters out for Authors, such as iframes and scripts.
WordPress's roles and capabilities documentation lists what each role can do. The differences that matter for an API client are these:
Capability
What it allows through the API
Author
Editor
publish_posts
Sending status: "publish"
Yes
Yes
upload_files
POST /wp/v2/media
Yes
Yes
edit_published_posts
Updating its own published posts
Yes
Yes
edit_others_posts
Editing posts written by other users
No
Yes
manage_categories
Creating new categories
No
Yes
unfiltered_html
Keeping iframes, scripts and other unsafe HTML in content
No
Yes, on single sites
Two details from core are worth knowing. Assigning existing categories and tags only needs edit_posts, so an Author can attach category IDs that already exist. Creating a brand new tag through the REST API also only needs that assign permission, because tags are not hierarchical, while creating a new category needs manage_categories, which Authors do not have.
A dedicated account also makes the audit trail clean. Every post the integration creates shows that user as its author (you can pass author with another user's ID if the account can edit others' posts), and revoking access later is a single click that touches nobody's real password.
How do you create an application password?
Log in as the integration user, go to Users, then Profile, and scroll to the Application Passwords section. Type a descriptive name in New Application Password Name, click Add Application Password, and copy the 24-character password WordPress shows. It is displayed once and stored hashed, so it cannot be viewed again later.
WordPress shows the password in groups of four, like abcd EFGH 1234 ijkl MNOP 6789. The integration guide says it works with or without the spaces, because spaces are stripped before the password is checked. An administrator can also create one for another user from Users, then All Users, then Edit.
On a server where you have shell access, WP-CLI does the same thing without the browser. The Advanced Administration Handbook gives this form, where 123 is the user ID and --porcelain prints only the password:
wp user application-password create 123 "content-publisher" --porcelain
Store the password as a secret (an environment variable or your platform's secret store), never in source code. Create one password per integration, so you can revoke one tool without breaking the others. The profile screen also shows when and from which IP each password was last used, accurate to within 24 hours.
How do you test the credentials?
Call GET /wp-json/wp/v2/users/me with the username and application password as Basic auth. A 200 response with your user's id and name proves authentication works. A 401 tells you which part failed through its code field, so read the body, not just the status.
Basic auth means the client sends an Authorization: Basic header containing username:password encoded in Base64. The username is the account's login name (its email address also works), not the display name. The common failures and their meaning:
Response
Code in the body
What it means
Fix
401
incorrect_password
The application password is wrong or was revoked
Create a new one and copy it exactly
401
invalid_username
No user has that login name
Use the login name or email from the user's profile
401
application_passwords_disabled
A plugin or filter switched the feature off
See the not-showing guide linked above
401
rest_not_logged_in
WordPress never received the credentials
The server stripped the Authorization header
404
rest_no_route
The URL or method does not match a route
Check the path, or use ?rest_route=
The rest_not_logged_in case is the confusing one, because the password is fine. The REST API handbook says a CGI environment may strip authentication headers and gives the Apache fix, SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1, and the Nginx fix, fastcgi_pass_header Authorization;. WordPress's Site Health screen also runs a test that reports "The authorization header is missing" when this happens.
How do you find category and tag IDs?
The posts endpoint takes categories and tags as arrays of numeric term IDs, not names. Look up each ID once with a search request to /wp-json/wp/v2/categories or /wp-json/wp/v2/tags, cache the result, and send the numbers. Unknown names should be created before the post, or left off.
# Find a category ID by name or slug
curl "https://example.com/wp-json/wp/v2/categories?search=guides&_fields=id,name,slug"
# Create a tag (an Author can do this; tags are not hierarchical)
curl --user "publisher:$WP_APP_PASSWORD" \
-H "Content-Type: application/json" \
-d '{"name":"rest api"}' \
https://example.com/wp-json/wp/v2/tags
Category and tag lists are public, so the lookup needs no credentials. Creating a tag that already exists returns a 400 error with code term_exists, and its data includes the existing tag's term_id, so a script can read that ID and carry on.
How do you upload the featured image?
Upload the image file to POST /wp-json/wp/v2/media as the raw request body, with a Content-Type header for the file type and a Content-Disposition header naming the file. WordPress returns the new attachment's id. Send that number as featured_media on the post.
WordPress refuses the upload without that header. Its attachments controller returns "No Content-Disposition supplied" when the header is missing, and asks for the format attachment; filename="image.png" when it is malformed. The media endpoint also accepts alt_text, caption and title, so follow the upload with a POST /wp-json/wp/v2/media/<id> that sets alt_text to a real description of the image.
How do you create the post?
With a media ID, category IDs and tag IDs in hand, create the post in one request. Send status: "draft" while you are testing, so nothing goes live by accident. WordPress answers 201 Created and returns the post object, including its id, link and the status it stored.
curl --user "publisher:$WP_APP_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"title": "How to publish to WordPress from a script",
"content": "<p>First paragraph.</p><h2>A section</h2><p>More text.</p>",
"excerpt": "A short summary for archive pages.",
"slug": "publish-from-a-script",
"status": "draft",
"categories": [7],
"tags": [31, 32],
"featured_media": 412
}' \
https://example.com/wp-json/wp/v2/posts
The posts reference lists every field the create endpoint accepts. The ones you will use most:
Field
Type
Notes
title
string
Plain text is fine on create
content
string
HTML; for an Author, unsafe tags are filtered on save
status
string
One of publish, future, draft, pending, private
date
string
Site timezone; with status: "future" it schedules the post
slug
string
The URL segment; WordPress makes it unique if taken
categories, tags
arrays of integers
Term IDs, not names
featured_media
integer
The media ID from the upload
excerpt
string
Used by themes on archive pages
To publish a draft later, send POST /wp-json/wp/v2/posts/<id> with {"status":"publish"}. A user without publish_posts gets rest_cannot_publish instead.
What does the whole flow look like in JavaScript?
The same steps in one function for Node 18 or later, which has fetch built in. It reads the password from an environment variable, uploads the image, creates the post as a draft and returns the post's ID and link.
Throwing on the error body's code means a failure tells you which row of the 401 table you hit. Run it from a server or a scheduled job, never from a browser, because the password would be visible to anyone who opens the page. If you would rather receive articles in your own code and decide there how to publish them, publishing through a signed webhook covers the receiving side.
Have drafts arrive in WordPress without writing the client
The code above handles delivery. The harder part is having something worth sending every day. LaunchScaler's content engine plans a month of articles from your own Search Console queries, writes one a day (thirty a month) from what your product actually does, and lands every draft in your workspace to rewrite, trim or scrap. By default, every draft waits for your review before it publishes; once you approve one, it goes to WordPress, one of its seven destinations alongside Webflow, Ghost, Shopify, Notion, Medium and a signed webhook.
It costs $99/mo per website, with 3 days free before the first charge. For how it compares with WordPress plugins that write posts, see AI blog writers for WordPress, or start the engine and your first draft arrives on day one.
Go to Users, then Profile, scroll to the Application Passwords section, type a name in New Application Password Name and click Add Application Password. WordPress shows the password once, so copy it immediately.
03
Can the WordPress REST API set a featured image?
Yes. Upload the image to /wp-json/wp/v2/media first, which returns a media id, then send that id as featured_media when you create or update the post.
04
Why does the WordPress REST API return 401 with an application password?
The password is wrong or revoked (incorrect_password), application passwords are disabled on the site (application_passwords_disabled), or the server stripped the Authorization header, in which case /wp/v2/users/me answers rest_not_logged_in.
05
Which user role should an integration use to post to WordPress?
A dedicated Author account is enough to create, publish and upload media for its own posts. Use Editor only if the integration must create categories or keep embedded HTML such as iframes, which Authors cannot.
Scaled content abuse is Google's policy against many pages made mainly to rank, by any method. What it covers, and how to publish daily and stay clear.