For a small integration, I would rather use a well-understood WordPress authentication feature than invent a separate API-key system. Application Passwords give a script its own revocable credential without requiring someone to hand over their everyday login password.
The interesting part is the boundary around that credential. Authenticating a request does not decide what the integration should be allowed to change. I still need to choose the user, permissions, destination and failure behaviour deliberately.
In this tutorial: What we are building; Before you start; 1. Confirm the identity before writing; 2. Create a draft with an explicit request; 3. Decide what a retry means; Check the behaviour.
What we are building
A server-side PHP script that creates a draft post through the WordPress REST API. The example describes a small integration I can reason about and adapt, rather than a public browser form carrying a secret.
Before you start
Application Passwords are built into WordPress from version 5.6. Use HTTPS and create a separate WordPress user with only the capabilities the task requires. For creating ordinary draft posts, a standard Contributor account is a more appropriate starting point than an Administrator.
Generate a named Application Password from that user's profile. Give it a recognisable integration name and store it in the calling application's private configuration. WordPress shows the generated credential once; keep it out of repositories, frontend JavaScript, screenshots and logs.
1. Confirm the identity before writing
An authenticated request to /wp-json/wp/v2/users/me is a useful first check. Confirm it returns the expected account. That separates a credential or transport problem from a later permission or content problem.
Application Passwords inherit the capabilities of their WordPress user. They are not, by themselves, tokens scoped to one endpoint or one post. If several integrations share an Administrator user, giving each a different password does not turn those users into restricted accounts.
2. Create a draft with an explicit request
$username = getenv('WP_API_USER');
$password = getenv('WP_API_APPLICATION_PASSWORD');
if (!$username || !$password) {
throw new RuntimeException('WordPress credentials are not configured.');
}
$payload = json_encode([
'title' => 'A draft from the integration',
'content' => 'Content ready for an editor to review.',
'status' => 'draft',
], JSON_THROW_ON_ERROR);
$curl = curl_init('https://your-site.example/wp-json/wp/v2/posts');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $username . ':' . $password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$failed = $response === false;
curl_close($curl);
if ($failed || $status !== 201) {
throw new RuntimeException('The draft was not confirmed as created.');
}
$post = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($post) || !isset($post['id'])) {
throw new RuntimeException('WordPress returned an unexpected response.');
}
This needs PHP with cURL and JSON support. The .example address is a placeholder. Replace it with a fixed, trusted HTTPS destination, not a URL supplied by a visitor. Keep certificate verification enabled.
The script deliberately requests a draft and refuses redirects. A successful creation returns HTTP 201. A 401 response generally sends me back to authentication; a 403 means I also need to examine capability checks and any access restrictions. I would log a safe failure category, not credentials or the entire submitted body.
3. Decide what a retry means
A timeout is awkward because WordPress might have created the post before the response was lost. Blindly repeating the request can create duplicates.
For a recurring integration I would design a stable external identifier, an authorised lookup and a deliberate create-or-update process. The small example above does not implement idempotency, so it should not be described as a complete unattended publishing service.
Check the behaviour
Use a development site. Confirm the expected user can create a draft but cannot perform unrelated administrative actions. Revoke the Application Password and confirm the next request fails. Check that a failed request cannot report success to the caller.
If authentication never reaches WordPress, check whether the host or proxy forwards the Authorization header. I would investigate that configuration rather than weakening authentication or switching off HTTPS to make the test pass.
The useful result is a connection whose authority I can explain, whose failures are visible and whose access I can revoke independently.
References
WordPress's Application Passwords integration guide describes the feature. The REST authentication handbook explains the authentication methods available to integrations.