'Publish WordPress Post with Python Requests and REST API

I try to publish post to WordPress blog with python requests and rest api by following code:

auth = 'Basic ' + str(base64.b64encode(b'admin:123456'), 'utf-8')
headers = {'Authorization': auth}
body = {'title': 'Hello World'}
r = requests.post('wp-json/wp/v2/posts', headers=headers, data=body)

and always got 401 error:

>>> r.text
'{"code":"rest_cannot_create","message":"Sorry, you are not allowed to create posts as this user.","data":{"status":401}}'

I'm pretty sure that the account admin and password is correct and has administrator role, Did I miss anything?



Solution 1:[1]

I was able to solve this

  1. I installed & activated this plugin on my wordpress https://wordpress.org/plugins/application-passwords/

  2. follow the help text there and create your password string for your userid - assume it is mypassword123

  3. now do this on your terminal "admin:mypassword123" | base64

You will get a new password - say pwdabc123

  1. Code looks like
url_srcdest = "http://example.com/wp-json/wp/v2/pages/"

headers = {'Content-Type': 'application/json', 
          'Authorization': 'Basic pwdabc123',
          'Username': '<your username>', 
          'Password':'pwdabc123'}
data = \
    {
        "title":"Testing via API via Python",
        "content":"tEST CONTENT OF THE THIS TEST PAGE via PYTHON",
        "status": "publish"
    }

response = requests.post(url_srcdest, data=json.dumps(data), headers=headers)

Solution 2:[2]

you missed:

  • auth IS your jwt token, NOT base64 encode token
    • jwt token is got from POST /wp-json/jwt-auth/v1/token

How to get jwt token ?

prerequisite

installed jwt plugin

install and enable WordPress plugin:

wordpress server enable jwt authorization

for example, my wordpress server is: CentOS

  1. edit .htaccess in wordpress root folder

added:

RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
  1. edit wp-config.php

add:

define('JWT_AUTH_SECRET_KEY', 'anyValueIsOk');
  • anyValueIsOk: change to yours, any value is OK
    • just do NOT tell other, ^_^
  • Remember service php-fpm restart after edit php file

POST /wp-json/jwt-auth/v1/token to generate jwt token

call REST api to generate jwt token

POST https://www.yourWebsite.com/wp-json/jwt-auth/v1/token

{
    "username": "wordpress_username",
    "password": "wordpress_password"
}
  • wordpress_username: here is your admin
  • wordpress_password: here is your 123456

response:

{
    "token": "eyJ0eXAiOxxxxxxxxxxxxHYBhtuzc",
    "user_email": "xxx@yyy.com",
    "user_nicename": "xxx",
    "user_display_name": "xxx"
}
  • eyJ0eXAiOxxxxxxxxxxxxHYBhtuzc: is the jwt token, which is what your need

[Optional] validate token is valid

POST https://www.crifan.com/wp-json/jwt-auth/v1/token/validate

headers:
Authorization: Bearer eyJ0eXAiOxxxxxxxxxxxxHYBhtuzc

response:

{
    "code": "jwt_auth_valid_token",
    "data": {
        "status": 200
    }
}

Additionally: use python call wordpress REST api to create posts

code:

import requests

your_jwt_token = "xxx"

curHeaders = {
    "Authorization": "Bearer %s" % your_jwt_token,
    "Content-Type": "application/json",
    "Accept": "application/json",
}

categoryIdList = []
tagIdList = []

if categoryNameList:
    # ['Mac']
    categoryIdList = self.getTaxonomyIdList(categoryNameList, taxonomy="category")
    # category nameList=['Mac'] -> taxonomyIdList=[1374]

if tagNameList:
    # ['??', 'GPU', 'pmset', '????']
    tagIdList = self.getTaxonomyIdList(tagNameList, taxonomy="post_tag")
    # post_tag nameList=['??', 'GPU', 'pmset', '????'] -> taxonomyIdList=[1367, 13224, 13225, 13226]

postDict = {
    "title": title, # '????Mac??pmset??GPU??????'
    "content": content, # '<div>\n  ???\n </div>\n <div>\n  ?????Mac Pro 2018??????????\n </div>\n <div>\n  ???...performance graphic cards\n    </li>\n   </ul>\n  </ul>\n </ul>\n <div>\n  <br/>\n </div>'
    # "date_gmt": dateStr,
    "date": dateStr, # '2020-08-17T10:16:34'
    "slug": slug, # 'on_mac_pmset_is_used_set_gpu_graphics_card_switching_mode'
    "status": status, # 'draft'
    "format": postFormat, # 'standard'
    "categories": categoryIdList, # [1374]
    "tags": tagIdList, # [1367, 13224, 13225, 13226]
    # TODO: featured_media, excerpt
}

yourHost = 'https://www.crifan.com'
createPostUrl = yourHost + "/wp-json/wp/v2/posts" # 'https://www.crifan.com/wp-json/wp/v2/posts'

resp = requests.post(
    createPostUrl,
    # proxies=self.requestsProxies,
    headers=curHeaders,
    # data=json.dumps(postDict),
    json=postDict, # internal auto do json.dumps
)

response example:

{
  "id": 70410,
  "date": "2020-02-27T21:11:49",
  "date_gmt": "2020-02-27T13:11:49",
  "guid": {
      "rendered": "https://www.crifan.com/?p=70410",
      "raw": "https://www.crifan.com/?p=70410"
  },
  "modified": "2020-02-27T21:11:49",
  "modified_gmt": "2020-02-27T13:11:49",
  "password": "",
  "slug": "mac_pip_change_source_server_to_spped_up_download",
  "status": "draft",
  "type": "post",
  "link": "https://www.crifan.com/?p=70410",
  "title": {
      'raw": "?????Mac??pip????????",
      "rendered": "?????Mac??pip????????"
  },
  "content": {
      ...
  }
}

Note:


more detail pls refer my Chinese posts:

Solution 3:[3]

I just use requests as described here, along with the application password:

import requests
r = requests.get('https://example-wp.com/wp-json/', auth=('username', 'APPLICATIONPASSWORD'))
r.status_code

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ruvee
Solution 2 crifan
Solution 3 Muminpappa