Post update with API and update date

I use the APIs with NodeJS to update my posts.

apparently I can’t update a post without first GET the post.

I understand that when I update a post, the updated_at parameter is mandatory, but it only works if I use the same date already present in the database.

So I get the last post update date (updated_at) with GET, then I can PUT and in the updated_at parameter I put the same updated_at value that I got with GET.

If I put the current date in the updated_at parameter in the PUT, I get a 409 error.

What am I doing wrong? How do I enter the current update date without using the old one?

You’re not doing anything wrong, that’s the intended behaviour of the API. updated_at can’t be set manually, it’s automatically updated by the server with the current time any time a change is made. The reason you need to supply the last-known date when saving is to avoid overwriting changes made by other users or even the system if a scheduled post had been published for example.

https://ghost.org/docs/admin-api/#updating-a-post

1 Like

hi @Kevin thanks for confirming.
I read the DOC but it wasn’t clear to me.
Thank you

@Kevin The docs linked to are not clear enough. See also the confused users here:

The docs need to clarify that the updated_at isn’t simply required, but that it must exactly match the current value of updated_at received in a prior GET request.

The docs could further mention that submitting a different value for for updated_at could result in an UPDATE_COLLISION error.

Thanks.

@Kevin Could you restate this / show an example?

I’m using requests to successfully get a post with the following url ‘/ghost/api/admin/posts/{post_id}/’, reading and changing the response data, and then failing to update the Post with a put request.

response = requests.get(url, headers=headers)
post_data = response.json()

post_data[‘posts’][0][‘tags’][0][‘name’] = ‘Different String’
post_data[‘posts’][0][‘tags’][0][‘slug’] = ‘Different String’

and then sending it back:
response = requests.put(url, headers=headers, json=post_data)

and I recieve a response such as, when the dictionary key ‘posts’ is certainly in there:

Failed to update post: b’{“errors”:[{“message”:“Validation error, cannot edit post.”,“context”:“Validation failed for posts.”,“type”:“ValidationError”,“details”:[{“keyword”:“required”,“dataPath”:“”,“schemaPath”:“#/required”,“params”:{“missingProperty”:“posts”},“message”:“should have required property 'posts'”}],“property”:“posts”,“help”:null,“code”:null,“id”:“9ff5fca0-18aa-11ef-9c9a-4f504e4b4824”,“ghostErrorCode”:null}]}’

What am I doing incorrectly so I can update existing posts via API?

@markstos I’m sending this data back with the updated_at field unchanged from the value that was sent in the get request.

Your error isn’t anything to do with an update collision error.

should have required property 'posts' is telling you that your request body content is incorrect because the server never received the top-level {posts: []} property. Your code sample is missing a lot of context needed to help you. What language are you using? What is requests and how does it handle JSON bodies in put requests?

1 Like

Python, requests is the library that replaces curl in python.

Response object from get request:

{‘posts’: [{‘id’: ‘664c1abf57f55790747b8caf’, ‘uuid’: ‘098d09c5-1e2f-4d26-9198-5e95d86b3617’, ‘title’: ‘Ark’, ‘slug’: ‘ark’, ‘mobiledoc’: None, ‘lexical’: ‘{“root”:{“children”:[{“type”:“markdown”,“version”:1,“markdown”:“#Please welcome Ark\n###Black Mouth Cur / Mixed (short coat)\nPlease welcome Ark\xa0to WWR! This southern mystery boy went from an urgent euthanasia list to arriving in style by plane and has been aged at 7\xa0months. Ark has been fostered with other dogs and has acclimated well with proper introduction and bonds quickly. Ark is a confident pup and requires an experienced handler who will continue training with Green Mountain K9, Paws in Motion, or Radford K9 specifically. The adoption fee is 600 and he is currently on hold for adoptions at this time.\xa0 If you are interested in adopting, please fill out an application.\n***\n### More about Ark\n- Not good with cats\n- Good with dogs\n- Not good with children\n- Reaction to new people: Aggressive\n- House trained\n- Color: Tricolor (Tan/Brown & Black & White)\n- Activity level: Moderately Active\n- Has Basic Training\n- Indoor/outdoor: Indoor and Outdoor\n\n\nImage\n\nImage\n\nImage\n\nImage”}],“direction”:null,“format”:“”,“indent”:0,“type”:“root”,“version”:1}}’, ‘comment_id’: ‘664c1abf57f55790747b8caf’, ‘feature_image’: ‘https://cdn.[api].org/8409/pictures/animals/19650/19650207/96768655.jpg’, ‘featured’: False, ‘status’: ‘published’, ‘visibility’: ‘public’, ‘created_at’: ‘2024-05-21T03:53:35.000Z’, ‘updated_at’: ‘2024-05-22T02:26:43.000Z’, ‘published_at’: ‘2024-05-21T03:53:35.000Z’, ‘custom_excerpt’: None, ‘codeinjection_head’: None, ‘codeinjection_foot’: None, ‘custom_template’: None, ‘canonical_url’: None, ‘tags’: [{‘id’: ‘66496ed488f6e604b8dda029’, ‘name’: ‘Adopted’, ‘slug’: ‘adopted’, ‘description’: ‘Our rescue hall of fame, dogs which have been adopted thru our rescue.’, ‘feature_image’: None, ‘visibility’: ‘public’, ‘og_image’: None, ‘og_title’: None, ‘og_description’: None, ‘twitter_image’: None, ‘twitter_title’: None, ‘twitter_description’: None, ‘meta_title’: None, ‘meta_description’: None, ‘codeinjection_head’: None, ‘codeinjection_foot’: None, ‘canonical_url’: None, ‘accent_color’: ‘#7a7a7a’, ‘created_at’: ‘2024-05-19T03:15:32.000Z’, ‘updated_at’: ‘2024-05-19T04:29:12.000Z’, ‘url’: ‘http://www.[ghost_domain].org/tag/adopted/’}], ‘authors’: [{‘id’: ‘1’, ‘name’: ‘Chris name’, ‘slug’: ‘chris’, ‘email’: ‘email@gmail.com’, ‘profile_image’: None, ‘cover_image’: None, ‘bio’: None, ‘website’: None, ‘location’: None, ‘facebook’: None, ‘twitter’: None, ‘accessibility’: ‘{“nightShift”:false}’, ‘status’: ‘active’, ‘meta_title’: None, ‘meta_description’: None, ‘tour’: None, ‘last_seen’: ‘2024-05-23T14:22:51.000Z’, ‘comment_notifications’: True, ‘free_member_signup_notification’: True, ‘paid_subscription_started_notification’: True, ‘paid_subscription_canceled_notification’: False, ‘mention_notifications’: True, ‘recommendation_notifications’: True, ‘milestone_notifications’: True, ‘donation_notifications’: True, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-05-23T14:22:51.000Z’, ‘roles’: [{‘id’: ‘660d6421edf285469a44b782’, ‘name’: ‘Owner’, ‘description’: ‘Blog Owner’, ‘created_at’: ‘2024-04-03T14:13:53.000Z’, ‘updated_at’: ‘2024-04-03T14:13:53.000Z’}], ‘url’: ‘http://www.[ghost_domain].org/author/chris/’}], ‘tiers’: [{‘id’: ‘660d6422edf285469a44b78b’, ‘name’: ‘Free’, ‘slug’: ‘free’, ‘active’: True, ‘welcome_page_url’: None, ‘visibility’: ‘public’, ‘trial_days’: 0, ‘description’: ‘See Weekly Updates from Wags!’, ‘type’: ‘free’, ‘currency’: None, ‘monthly_price’: None, ‘yearly_price’: None, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-04-03T18:54:53.000Z’, ‘monthly_price_id’: None, ‘yearly_price_id’: None}, {‘id’: ‘660d6422edf285469a44b78c’, ‘name’: ‘Wags n Rescue’, ‘slug’: ‘default-product’, ‘active’: True, ‘welcome_page_url’: None, ‘visibility’: ‘public’, ‘trial_days’: 0, ‘description’: None, ‘type’: ‘paid’, ‘currency’: ‘usd’, ‘monthly_price’: 500, ‘yearly_price’: 5000, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-04-03T14:17:57.000Z’, ‘monthly_price_id’: None, ‘yearly_price_id’: None}], ‘count’: {‘clicks’: 0, ‘positive_feedback’: 0, ‘negative_feedback’: 0}, ‘primary_author’: {‘id’: ‘1’, ‘name’: ‘Chris name’, ‘slug’: ‘chris’, ‘email’: ‘email@gmail.com’, ‘profile_image’: None, ‘cover_image’: None, ‘bio’: None, ‘website’: None, ‘location’: None, ‘facebook’: None, ‘twitter’: None, ‘accessibility’: ‘{“nightShift”:false}’, ‘status’: ‘active’, ‘meta_title’: None, ‘meta_description’: None, ‘tour’: None, ‘last_seen’: ‘2024-05-23T14:22:51.000Z’, ‘comment_notifications’: True, ‘free_member_signup_notification’: True, ‘paid_subscription_started_notification’: True, ‘paid_subscription_canceled_notification’: False, ‘mention_notifications’: True, ‘recommendation_notifications’: True, ‘milestone_notifications’: True, ‘donation_notifications’: True, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-05-23T14:22:51.000Z’, ‘roles’: [{‘id’: ‘660d6421edf285469a44b782’, ‘name’: ‘Owner’, ‘description’: ‘Blog Owner’, ‘created_at’: ‘2024-04-03T14:13:53.000Z’, ‘updated_at’: ‘2024-04-03T14:13:53.000Z’}], ‘url’: ‘http://www.[ghost_domain].org/author/chris/’}, ‘primary_tag’: {‘id’: ‘66496ed488f6e604b8dda029’, ‘name’: ‘Adopted’, ‘slug’: ‘adopted’, ‘description’: ‘Our rescue hall of fame, dogs which have been adopted thru our rescue.’, ‘feature_image’: None, ‘visibility’: ‘public’, ‘og_image’: None, ‘og_title’: None, ‘og_description’: None, ‘twitter_image’: None, ‘twitter_title’: None, ‘twitter_description’: None, ‘meta_title’: None, ‘meta_description’: None, ‘codeinjection_head’: None, ‘codeinjection_foot’: None, ‘canonical_url’: None, ‘accent_color’: ‘#7a7a7a’, ‘created_at’: ‘2024-05-19T03:15:32.000Z’, ‘updated_at’: ‘2024-05-19T04:29:12.000Z’, ‘url’: ‘http://www.[ghost_domain].org/tag/adopted/’}, ‘email_segment’: ‘all’, ‘url’: ‘http://www.[ghost_domain].org/ark/’, ‘excerpt’: ‘Please welcome Ark\n\n\n\nBlack Mouth Cur / Mixed (short coat)\n\n\nPlease welcome Ark\xa0to WWR! This southern mystery boy went from an urgent euthanasia list to arriving in style by plane and has been aged at 7\xa0months. Ark has been fostered with other dogs and has acclimated well with proper introduction and bonds quickly. Ark is a confident pup and requires an experienced handler who will continue training with Green Mountain K9, Paws in Motion, or Radford K9 specifically. The adoption fee is 600 and h’, ‘reading_time’: 1, ‘og_image’: ‘https://cdn.[api].org/8409/pictures/animals/19650/19650207/96768655.jpg’, ‘og_title’: ‘Please welcome Ark’, ‘og_description’: None, ‘twitter_image’: None, ‘twitter_title’: None, ‘twitter_description’: None, ‘meta_title’: None, ‘meta_description’: None, ‘email_subject’: None, ‘frontmatter’: None, ‘feature_image_alt’: None, ‘feature_image_caption’: None, ‘email_only’: False, ‘email’: None, ‘newsletter’: None}]}

Put object, two fields were updated, posts/tags/name, and post/tags/slug.

{‘posts’: [{‘id’: ‘664c1abf57f55790747b8caf’, ‘uuid’: ‘098d09c5-1e2f-4d26-9198-5e95d86b3617’, ‘title’: ‘Ark’, ‘slug’: ‘ark’, ‘mobiledoc’: None, ‘lexical’: ‘{“root”:{“children”:[{“type”:“markdown”,“version”:1,“markdown”:“#Please welcome Ark\n###Black Mouth Cur / Mixed (short coat)\nPlease welcome Ark\xa0to WWR! This southern mystery boy went from an urgent euthanasia list to arriving in style by plane and has been aged at 7\xa0months. Ark has been fostered with other dogs and has acclimated well with proper introduction and bonds quickly. Ark is a confident pup and requires an experienced handler who will continue training with Green Mountain K9, Paws in Motion, or Radford K9 specifically. The adoption fee is 600 and he is currently on hold for adoptions at this time.\xa0 If you are interested in adopting, please fill out an application.\n***\n### More about Ark\n- Not good with cats\n- Good with dogs\n- Not good with children\n- Reaction to new people: Aggressive\n- House trained\n- Color: Tricolor (Tan/Brown & Black & White)\n- Activity level: Moderately Active\n- Has Basic Training\n- Indoor/outdoor: Indoor and Outdoor\n\n\nImage\n\nImage\n\nImage\n\nImage”}],“direction”:null,“format”:“”,“indent”:0,“type”:“root”,“version”:1}}’, ‘comment_id’: ‘664c1abf57f55790747b8caf’, ‘feature_image’: ‘https://cdn.[api].org/8409/pictures/animals/19650/19650207/96768655.jpg’, ‘featured’: False, ‘status’: ‘published’, ‘visibility’: ‘public’, ‘created_at’: ‘2024-05-21T03:53:35.000Z’, ‘updated_at’: ‘2024-05-22T02:26:43.000Z’, ‘published_at’: ‘2024-05-21T03:53:35.000Z’, ‘custom_excerpt’: None, ‘codeinjection_head’: None, ‘codeinjection_foot’: None, ‘custom_template’: None, ‘canonical_url’: None, ‘tags’: [{‘id’: ‘66496ed488f6e604b8dda029’, ‘name’: ‘Adoptable’, ‘slug’: ‘adoptable’, ‘description’: ‘Our rescue hall of fame, dogs which have been adopted thru our rescue.’, ‘feature_image’: None, ‘visibility’: ‘public’, ‘og_image’: None, ‘og_title’: None, ‘og_description’: None, ‘twitter_image’: None, ‘twitter_title’: None, ‘twitter_description’: None, ‘meta_title’: None, ‘meta_description’: None, ‘codeinjection_head’: None, ‘codeinjection_foot’: None, ‘canonical_url’: None, ‘accent_color’: ‘#7a7a7a’, ‘created_at’: ‘2024-05-19T03:15:32.000Z’, ‘updated_at’: ‘2024-05-19T04:29:12.000Z’, ‘url’: ‘http://www.[ghost_domain].org/tag/adopted/’}], ‘authors’: [{‘id’: ‘1’, ‘name’: ‘Chris name’, ‘slug’: ‘chris’, ‘email’: ‘email@gmail.com’, ‘profile_image’: None, ‘cover_image’: None, ‘bio’: None, ‘website’: None, ‘location’: None, ‘facebook’: None, ‘twitter’: None, ‘accessibility’: ‘{“nightShift”:false}’, ‘status’: ‘active’, ‘meta_title’: None, ‘meta_description’: None, ‘tour’: None, ‘last_seen’: ‘2024-05-23T14:22:51.000Z’, ‘comment_notifications’: True, ‘free_member_signup_notification’: True, ‘paid_subscription_started_notification’: True, ‘paid_subscription_canceled_notification’: False, ‘mention_notifications’: True, ‘recommendation_notifications’: True, ‘milestone_notifications’: True, ‘donation_notifications’: True, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-05-23T14:22:51.000Z’, ‘roles’: [{‘id’: ‘660d6421edf285469a44b782’, ‘name’: ‘Owner’, ‘description’: ‘Blog Owner’, ‘created_at’: ‘2024-04-03T14:13:53.000Z’, ‘updated_at’: ‘2024-04-03T14:13:53.000Z’}], ‘url’: ‘http://www.[ghost_domain].org/author/chris/’}], ‘tiers’: [{‘id’: ‘660d6422edf285469a44b78b’, ‘name’: ‘Free’, ‘slug’: ‘free’, ‘active’: True, ‘welcome_page_url’: None, ‘visibility’: ‘public’, ‘trial_days’: 0, ‘description’: ‘See Weekly Updates from Wags!’, ‘type’: ‘free’, ‘currency’: None, ‘monthly_price’: None, ‘yearly_price’: None, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-04-03T18:54:53.000Z’, ‘monthly_price_id’: None, ‘yearly_price_id’: None}, {‘id’: ‘660d6422edf285469a44b78c’, ‘name’: ‘Wags n Rescue’, ‘slug’: ‘default-product’, ‘active’: True, ‘welcome_page_url’: None, ‘visibility’: ‘public’, ‘trial_days’: 0, ‘description’: None, ‘type’: ‘paid’, ‘currency’: ‘usd’, ‘monthly_price’: 500, ‘yearly_price’: 5000, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-04-03T14:17:57.000Z’, ‘monthly_price_id’: None, ‘yearly_price_id’: None}], ‘count’: {‘clicks’: 0, ‘positive_feedback’: 0, ‘negative_feedback’: 0}, ‘primary_author’: {‘id’: ‘1’, ‘name’: ‘Chris name’, ‘slug’: ‘chris’, ‘email’: ‘email@gmail.com’, ‘profile_image’: None, ‘cover_image’: None, ‘bio’: None, ‘website’: None, ‘location’: None, ‘facebook’: None, ‘twitter’: None, ‘accessibility’: ‘{“nightShift”:false}’, ‘status’: ‘active’, ‘meta_title’: None, ‘meta_description’: None, ‘tour’: None, ‘last_seen’: ‘2024-05-23T14:22:51.000Z’, ‘comment_notifications’: True, ‘free_member_signup_notification’: True, ‘paid_subscription_started_notification’: True, ‘paid_subscription_canceled_notification’: False, ‘mention_notifications’: True, ‘recommendation_notifications’: True, ‘milestone_notifications’: True, ‘donation_notifications’: True, ‘created_at’: ‘2024-04-03T14:13:54.000Z’, ‘updated_at’: ‘2024-05-23T14:22:51.000Z’, ‘roles’: [{‘id’: ‘660d6421edf285469a44b782’, ‘name’: ‘Owner’, ‘description’: ‘Blog Owner’, ‘created_at’: ‘2024-04-03T14:13:53.000Z’, ‘updated_at’: ‘2024-04-03T14:13:53.000Z’}], ‘url’: ‘http://www.[ghost_domain].org/author/chris/’}, ‘primary_tag’: {‘id’: ‘66496ed488f6e604b8dda029’, ‘name’: ‘Adopted’, ‘slug’: ‘adopted’, ‘description’: ‘Our rescue hall of fame, dogs which have been adopted thru our rescue.’, ‘feature_image’: None, ‘visibility’: ‘public’, ‘og_image’: None, ‘og_title’: None, ‘og_description’: None, ‘twitter_image’: None, ‘twitter_title’: None, ‘twitter_description’: None, ‘meta_title’: None, ‘meta_description’: None, ‘codeinjection_head’: None, ‘codeinjection_foot’: None, ‘canonical_url’: None, ‘accent_color’: ‘#7a7a7a’, ‘created_at’: ‘2024-05-19T03:15:32.000Z’, ‘updated_at’: ‘2024-05-19T04:29:12.000Z’, ‘url’: ‘http://www.[ghost_domain].org/tag/adopted/’}, ‘email_segment’: ‘all’, ‘url’: ‘http://www.[ghost_domain].org/ark/’, ‘excerpt’: ‘Please welcome Ark\n\n\n\nBlack Mouth Cur / Mixed (short coat)\n\n\nPlease welcome Ark\xa0to WWR! This southern mystery boy went from an urgent euthanasia list to arriving in style by plane and has been aged at 7\xa0months. Ark has been fostered with other dogs and has acclimated well with proper introduction and bonds quickly. Ark is a confident pup and requires an experienced handler who will continue training with Green Mountain K9, Paws in Motion, or Radford K9 specifically. The adoption fee is 600 and h’, ‘reading_time’: 1, ‘og_image’: ‘https://cdn.[api].org/8409/pictures/animals/19650/19650207/96768655.jpg’, ‘og_title’: ‘Please welcome Ark’, ‘og_description’: None, ‘twitter_image’: None, ‘twitter_title’: None, ‘twitter_description’: None, ‘meta_title’: None, ‘meta_description’: None, ‘email_subject’: None, ‘frontmatter’: None, ‘feature_image_alt’: None, ‘feature_image_caption’: None, ‘email_only’: False, ‘email’: None, ‘newsletter’: None}]}

The put http response: (the error)

Failed to update post: b’{“errors”:[{“message”:“Validation error, cannot edit post.”,“context”:“Validation failed for posts.”,“type”:“ValidationError”,“details”:[{“keyword”:“required”,“dataPath”:“”,“schemaPath”:“#/required”,“params”:{“missingProperty”:“posts”},“message”:“should have required property 'posts'”}],“property”:“posts”,“help”:null,“code”:null,“id”:“3fc4c2e0-1912-11ef-9c9a-4f504e4b4824”,“ghostErrorCode”:null}]}’

The python function that is successfully getting the data, but unsuccessful in the post update:

def update_post_tags(token, post_id, tag):
url = f’http://www.[ghost_domain].org/ghost/api/admin/posts/{post_id}/’

print(url)
headers = {'Authorization': 'Ghost {}'.format(token), "Content-Type": "application/json;charset=utf-8"}

# Fetch the existing post data
response = requests.get(url, headers=headers)

if response.status_code != 200:
    raise Exception(f"Failed to fetch post: {response.content}")

getData = response.json()

print(getData)
if tag == 0:# mutate the dog to the adopted category tag
    getData['posts'][0]['tags'][0]['name'] = 'Adoptable'
    getData['posts'][0]['tags'][0]['slug'] = 'adoptable'
    #getData['posts'][0]['tags'][0]['updated_at'] = timestamp = now.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'

if tag == 1: # mutate the dog to the adoptable category tag
    getData['posts'][0]['tags'][0]['name'] = 'Adoptable'
    getData['posts'][0]['tags'][0]['slug'] = 'adoptable'
    #getData['posts'][0]['tags'][0]['updated_at'] = timestamp = now.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
print(getData)
print("\n")
response = requests.put(url, headers=headers, json=getData)

if response.status_code == 200:
    print(f"Successfully updated post tags for post ID {post_id} to {tag}.\n{response.content}")
else:
    print(f"Failed to update post: {response.content}")

I’m not a python gal, but I think it’s data= payload, not json=… ?

1 Like

I thought data vs json would get it working, unfortunately not!

Here’s more:
The data is json formatted, because it’s using nulls instead of None
http response to the put request is 422
http response code and response content do not change when data or json=ghost_post_data
json data is from the get request of api/admin/posts/{post_id}
json data passes the linter
json data has two edits which is the reason for the put request, both edits are there, double quoted strings with commas after

same error with a really basic edit consisting of the id and a different title
data = {‘posts’: [{‘id’: ‘664c1ac157f55790747b8cc1’, “title”: “Doby title 2”, “updated_at”: “2024-05-21T03:53:37.000Z”}]}

b’{“errors”:[{“message”:“Validation error, cannot edit post.”,“context”:“Validation failed for posts.”,“type”:“ValidationError”,“details”:[{“keyword”:“required”,“dataPath”:“”,“schemaPath”:“#/required”,“params”:{“missingProperty”:“posts”},“message”:“should have required property 'posts'”}],“property”:“posts”,“help”:null,“code”:null,“id”:“0edd0f00-196e-11ef-9c9a-4f504e4b4824”,“ghostErrorCode”:null}]}’

Here’s some really basic code that returns error 422 in pretty much every situation other than the token being incorrect with requests.put, requests.post and requests.get work and return expected results though.

The id & updated_at, are copy pasted from the get request thru the admin api.

That should be all I need, what am I missing?

A post request to the put url, ghost/api/admin/posts/{post_id}/, returns code 200, along with the json data that the get request would otherwise provide.

checked the logs at /var/www/ghost/content/logs, the ‘_production.error.log’ data shows the {“errors”:[{“message”:“Validation error,… same as the 422 content response.

The error message is telling you what’s wrong. The top-level posts attribute is missing in the request body according to Ghost. It’s nothing to do with ids or updated_at.

You need to figure out exactly what the request looks like that your code is sending because it’s not matching what Ghost expects. Maybe you can try printing the response.request.body after making your request to see what that looks like?

1 Like

I’m not sure? The posts key is certainly there.

The top level posts attribute is not missing. That data object has ‘posts’ as the first key, when I post data instead of put data, this format works just fine.

data = {'posts': [{'id': '664c1ac157f55790747b8cc1', "title": "Doby title 2", "updated_at": "2024-05-21T03:53:37.000Z"}]}

the response.content of the 422 error code is:

b’{“errors”:[{“message”:“Validation error, cannot edit post.”,“context”:“Validation failed for posts.”,“type”:“ValidationError”,“details”:[{“keyword”:“required”,“dataPath”:“”,“schemaPath”:“#/required”,“params”:{“missingProperty”:“posts”},“message”:“should have required property 'posts'”}],“property”:“posts”,“help”:null,“code”:null,“id”:“0edd0f00-196e-11ef-9c9a-4f504e4b4824”,“ghostErrorCode”:null}]}’

look at the pastebin link for more: import requestsimport jsonimport jwtfrom datetime import datetime as d - Pastebin.com

I think you need to try printing response.request.body to see what was actually sent rather than what you think was sent, because whatever is ending up reaching Ghost does not match what Ghost is expecting. You know the problem - Ghost isn’t seeing a top-level posts property in the request body - so you need to work backwards from that, for which the first step will be checking what was sent.

1 Like

I found my answer searching GitHub for put requests to the api endpoint.

Appending the following string: /?formats=mobiledoc%2Clexical to the put url request solved the problem, and now I’m receiving http response codes of 200, and the content is updating.

So to be super clear for other people who might encounter this issue: you want put requests that modify specific posts going to: ghost/api/admin/posts/{post_id}/?formats=mobiledoc%2Clexical

Would be nice if some of this would be discussed in the documentation! Ghost Admin API Documentation

2 Likes

The docs say this now:

Whenever you fetch, create, or edit a post, the API will respond with an array of one or more post objects. These objects will include all related tags, authors, and author roles.

By default, the API expects and returns content in the Lexical format only. To include HTML in the response use the formats parameter:

Thank’s for the update @markstos