# Post update with API and update date

**URL:** <https://forum.ghost.org/t/post-update-with-api-and-update-date/20234>\
**Category:** Developer help\
**Created:** [February 16, 2021, 11:13pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234 "2021-02-16T23:13:31Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![giacomosilli](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/giacomosilli/32/3403_2.png) [@giacomosilli](https://forum.ghost.org/u/giacomosilli)\
**Post date:** [February 16, 2021, 11:13pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/1 "2021-02-16T23:13:31Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![Kevin](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kevin/32/32_2.png) [@Kevin](https://forum.ghost.org/u/Kevin)\
**Post date:** [February 16, 2021, 11:18pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/2 "2021-02-16T23:18:22Z")

</div>

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](https://ghost.org/docs/admin-api/#updating-a-post)

---

<div class="post-metadata">

**Author:** ![giacomosilli](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/giacomosilli/32/3403_2.png) [@giacomosilli](https://forum.ghost.org/u/giacomosilli)\
**Post date:** [February 17, 2021, 4:00pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/3 "2021-02-17T16:00:15Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![markstos](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/markstos/32/18182_2.png) [@markstos](https://forum.ghost.org/u/markstos)\
**Post date:** [May 19, 2023, 1:51pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/4 "2023-05-19T13:51:50Z")

</div>

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

> <https://github.com/TryGhost/SDK/issues/103>
>
> \### Issue Summary
> 
> I am unable to update a post using Admin API. I get \`UPDATE…\_COLLISION\` error.
> 
> \### To Reproduce
> 
> I run
> \`\`\`js
> await api.posts.edit({"id": "5c8051da2a25b600c01ff9ee", "mobiledoc": JSON.stringify(md), "updated\_at": "2019-05-09T11:39:37.000Z"})
> \`\`\`
> 
> and get
> 
> \`\`\`
> { UpdateCollisionError: Saving failed! Someone else is editing this post.
> at makeRequest.catch (/tmp/node\_modules/@tryghost/admin-api/lib/index.js:292:33)
> at process.\_tickCallback (internal/process/next\_tick.js:68:7)
> name: 'UpdateCollisionError',
> context: 'Saving failed! Someone else is editing this post.',
> type: 'UpdateCollisionError',
> details:
> { clientUpdatedAt: '2019-05-09T11:39:37.000Z',
> serverUpdatedAt: '2019-05-08T15:40:57.000Z' },
> property: null,
> help: null,
> code: 'UPDATE\_COLLISION',
> id: '36069d20-723f-11e9-9b05-d55f47be1c26' }
> \`\`\`
> 
> I guarantee that nobody else is editing that post. I don't have it open in a web browser, and nobody else knows that the post I am editing even exists.
> 
> \### Technical details:
> 
> \* Ghost Version: 2.22
> \* Node Version: v10.15.3
> \* Browser/OS: N/A
> \* Database: N/A
> 
> \### P.S.
> 
> JS API docs suck. I mean, really suck. Typos ("api.pages.edit" instead of "api.posts.edit"), "updated\_at" is not included, there is only one example (to publish). It's a miracle that I ever found how to use it all.
> 
> !\[image\](https://user-images.githubusercontent.com/2793551/57444870-6ab9f500-7251-11e9-95c3-a1348d0def4d.png)

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.

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [May 23, 2024, 2:23am UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/5 "2024-05-23T02:23:04Z")

</div>

@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?

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [May 23, 2024, 2:32am UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/6 "2024-05-23T02:32:01Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Kevin](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kevin/32/32_2.png) [@Kevin](https://forum.ghost.org/u/Kevin)\
**Post date:** [May 23, 2024, 6:13am UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/7 "2024-05-23T06:13:25Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [May 23, 2024, 2:49pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/8 "2024-05-23T14:49:14Z")

</div>

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\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/96768655.jpg)\n\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/95750279.jpg)\n\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/95750280.jpg)\n\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/95750281.jpg)”}],“direction”:null,“format”:“”,“indent”:0,“type”:“root”,“version”:1}}’, ‘comment\_id’: ‘664c1abf57f55790747b8caf’, ‘feature\_image’: ‘[https://cdn](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](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](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](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](http://www).[ghost\_domain].org/tag/adopted/’}, ‘email\_segment’: ‘all’, ‘url’: ‘[http://www](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](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\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/96768655.jpg)\n\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/95750279.jpg)\n\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/95750280.jpg)\n\n ![Image](https://cdn.%5Bapi%5D.org/8409/pictures/animals/19650/19650207/95750281.jpg)”}],“direction”:null,“format”:“”,“indent”:0,“type”:“root”,“version”:1}}’, ‘comment\_id’: ‘664c1abf57f55790747b8caf’, ‘feature\_image’: ‘[https://cdn](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](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](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](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](http://www).[ghost\_domain].org/tag/adopted/’}, ‘email\_segment’: ‘all’, ‘url’: ‘[http://www](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](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](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}")
> 
> ```

---

<div class="post-metadata">

**Author:** ![Cathy\_Sarisky](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/cathy_sarisky/32/25750_2.png) [@Cathy\_Sarisky](https://forum.ghost.org/u/Cathy_Sarisky)\
**Post date:** [May 23, 2024, 5:47pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/9 "2024-05-23T17:47:45Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [May 24, 2024, 1:45am UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/10 "2024-05-24T01:45:17Z")

</div>

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.

> **[import requestsimport jsonimport jwtfrom datetime import datetime as d -...](https://pastebin.com/QF7qKmRU)**
>
> Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.

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?

> **[Ghost Admin API Documentation](https://ghost.org/docs/admin-api/#updating-a-post)**
>
> Manage content via Ghost's Admin API, with secure role-based authentication. Read more on Ghost Docs 👉

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.

---

<div class="post-metadata">

**Author:** ![Kevin](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kevin/32/32_2.png) [@Kevin](https://forum.ghost.org/u/Kevin)\
**Post date:** [May 24, 2024, 7:24am UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/11 "2024-05-24T07:24:28Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [May 24, 2024, 12:16pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/12 "2024-05-24T12:16:30Z")

</div>

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.

```auto
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:

```auto
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](https://pastebin.com/QF7qKmRU)

---

<div class="post-metadata">

**Author:** ![Kevin](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kevin/32/32_2.png) [@Kevin](https://forum.ghost.org/u/Kevin)\
**Post date:** [May 24, 2024, 1:33pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/13 "2024-05-24T13:33:38Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [May 24, 2024, 5:42pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/14 "2024-05-24T17:42:56Z")

</div>

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](https://ghost.org/docs/admin-api/#updating-a-post)

---

<div class="post-metadata">

**Author:** ![markstos](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/markstos/32/18182_2.png) [@markstos](https://forum.ghost.org/u/markstos)\
**Post date:** [June 20, 2024, 1:39am UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/15 "2024-06-20T01:39:37Z")

</div>

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:

---

<div class="post-metadata">

**Author:** ![csffsc](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/csffsc/32/30214_2.png) [@csffsc](https://forum.ghost.org/u/csffsc)\
**Post date:** [June 20, 2024, 11:46pm UTC](https://forum.ghost.org/t/post-update-with-api-and-update-date/20234/16 "2024-06-20T23:46:02Z")

</div>

> [@markstos](#):
>
> 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.

Thank’s for the update @markstos
