Help to understand a complete workflow for creating pages with images using API #4380

Closed
opened 2026-02-05 08:44:00 +03:00 by OVERLORD · 5 comments
Owner

Originally created by @yulia-roensch on GitHub (Dec 28, 2023).

Attempted Debugging

  • I have read the debugging page

Searched GitHub Issues

  • I have searched GitHub for the issue.

Describe the Scenario

Hi,
I am trying to create a page using the API. The page must have images.
Steps:

  • Exported a complete Confluence space with attachments, in HTML format.
  • Using Jupyter and Python requests library, I created a page using HTML as a body.
  • There've been a link to an image inside this HTML: <span class="confluence-embedded-file-wrapper"><img class="confluence-embedded-image" draggable="false" src="attachments/104497408/104497409.jpg" data-image-src="attachments/104497408/104497409.jpg" data-unresolved-comment-count="0" data-linked-resource-id="104497409" data-linked-resource-version="1" data-linked-resource-type="attachment" data-linked-resource-default-alias="(Docu User) Asset page concept 2018.3.jpg" data-base-url="https://confluence.company.com" data-linked-resource-content-type="image/jpeg" data-linked-resource-container-id="104497408" data-linked-resource-container-version="1" alt=""></span>
  • As a result, when I check the created page in Bookstack, instead of this image, there is an empty line. In the code of the page, I see the link from above. At the bottom of the page, the image is referenced again as an attachment, but there is no one (it opens a 404 page).
    My question is: should I upload the image before creating the page?
    I tried to upload image as image and as attachment following your API documentation. But I am not sure what is wrong with my call:
image_url = 'https://bookstack.company.com/api/image-gallery'
payload={
    "type":"gallery",
    "uploaded_to":36,
    "image":{
        "file":"/Users/user/htmlexport-20231222-135352-809/CS20231PAR/attachments/104497408"
    }
    
}
response = requests.post(
   image_url,
   headers=headers,
    data=payload
    
)

Throws an error:

{'error': {'message': 'Call to a member function getClientOriginalExtension() on string',
  'code': 500}}

Tried to upload the image as an attachment:

attach_url='https://bookstack.company.com/api/attachments'
payload={
    "name":"my image",
    "uploaded_to":36,
    "file": "/Users/user/htmlexport-20231222-135352-809/CS20231PAR/attachments/104497408"
   
}
response = requests.post(
   attach_url,
   headers=headers,
    data=payload
    
)

Throws an error:

{'error': {'message': 'The given data was invalid.',
  'validation': {'name': ['The name field is required.'],
   'file': ['The file must be provided as a valid file.']},
  'code': 422}}

Please help me to understand what is wrong with my requests or maybe with the order in general (first upload images then create a page, but you need a page id to upload an image, right?)

Thank you!

Exact BookStack Version

v23.08.3

Log Content

No response

Hosting Environment

Bookstack is currently running on a VM that uses the following hardware:

  • 2 vCPUs (AMD64)
  • 8 GiB RAM
  • 100GiB total space on a local filesystem

Software:

  • OS: Debian 12 Bookworm
  • nginx/1.22.1 (WebServer/Reverse Proxy)
  • PHP8.2
  • MariaDB 10.11.3

It has been installed using the official installation script.

Originally created by @yulia-roensch on GitHub (Dec 28, 2023). ### Attempted Debugging - [X] I have read the debugging page ### Searched GitHub Issues - [X] I have searched GitHub for the issue. ### Describe the Scenario Hi, I am trying to create a page using the API. The page must have images. Steps: * Exported a complete Confluence space with attachments, in HTML format. * Using Jupyter and Python `requests` library, I created a page using HTML as a body. * There've been a link to an image inside this HTML: `<span class="confluence-embedded-file-wrapper"><img class="confluence-embedded-image" draggable="false" src="attachments/104497408/104497409.jpg" data-image-src="attachments/104497408/104497409.jpg" data-unresolved-comment-count="0" data-linked-resource-id="104497409" data-linked-resource-version="1" data-linked-resource-type="attachment" data-linked-resource-default-alias="(Docu User) Asset page concept 2018.3.jpg" data-base-url="https://confluence.company.com" data-linked-resource-content-type="image/jpeg" data-linked-resource-container-id="104497408" data-linked-resource-container-version="1" alt=""></span>` * As a result, when I check the created page in Bookstack, instead of this image, there is an empty line. In the code of the page, I see the link from above. At the bottom of the page, the image is referenced again as an attachment, but there is no one (it opens a 404 page). My question is: should I upload the image _before_ creating the page? I tried to upload image as image and as attachment following your API documentation. But I am not sure what is wrong with my call: ``` image_url = 'https://bookstack.company.com/api/image-gallery' payload={ "type":"gallery", "uploaded_to":36, "image":{ "file":"/Users/user/htmlexport-20231222-135352-809/CS20231PAR/attachments/104497408" } } response = requests.post( image_url, headers=headers, data=payload ) ``` Throws an error: ``` {'error': {'message': 'Call to a member function getClientOriginalExtension() on string', 'code': 500}} ``` Tried to upload the image as an attachment: ``` attach_url='https://bookstack.company.com/api/attachments' payload={ "name":"my image", "uploaded_to":36, "file": "/Users/user/htmlexport-20231222-135352-809/CS20231PAR/attachments/104497408" } response = requests.post( attach_url, headers=headers, data=payload ) ``` Throws an error: ``` {'error': {'message': 'The given data was invalid.', 'validation': {'name': ['The name field is required.'], 'file': ['The file must be provided as a valid file.']}, 'code': 422}} ``` Please help me to understand what is wrong with my requests or maybe with the order in general (first upload images then create a page, but you need a page id to upload an image, right?) Thank you! ### Exact BookStack Version v23.08.3 ### Log Content _No response_ ### Hosting Environment Bookstack is currently running on a VM that uses the following hardware: - 2 vCPUs (AMD64) - 8 GiB RAM - 100GiB total space on a local filesystem Software: - OS: Debian 12 Bookworm - nginx/1.22.1 (WebServer/Reverse Proxy) - PHP8.2 - MariaDB 10.11.3 It has been installed using the official installation script.
OVERLORD added the 🐕 Support label 2026-02-05 08:44:00 +03:00
Author
Owner

@toras9000 commented on GitHub (Jan 27, 2024):

It looks like you are sending the "file location" in your API request.
Even if you send the location in your computer, the server cannot see the contents of your computer.

You need to have a good grasp of what data you need to send with the API. This also requires knowledge of HTTP.
A sample code that probably matches what you are looking for can be found here.
https://github.com/BookStackApp/api-scripts

If you find it difficult, you can use a wrapper library.

@toras9000 commented on GitHub (Jan 27, 2024): It looks like you are sending the "file location" in your API request. Even if you send the location in your computer, the server cannot see the contents of your computer. You need to have a good grasp of what data you need to send with the API. This also requires knowledge of HTTP. A sample code that probably matches what you are looking for can be found here. https://github.com/BookStackApp/api-scripts If you find it difficult, you can use a wrapper library.
Author
Owner

@whimsee commented on GitHub (Jan 18, 2025):

I gave this a shot because I was already using the API via Python Requests to create entries. Except for images, which took a lot of tinkering to work out due to how Requests abstracts the process and how the php backend interprets the request.

Setup:

import requests
todo = {
"name": "some_title",
"description_html": "some_description",
}
files = {
"image": (open(image_path, "rb"))
}
headers = {"Authorization" : auth_token}

Originally, I tried the following:

response = requests.post(url, json=todo, files=files, headers=headers)

Note "json" was used instead of "data" because it worked so far, but apparently there's some fuzziness with the Requests library per #5087 that I actually didn't know at the time; I just prefer using json payloads.

This doesn't work as far as I've tried. You always end up with validation errors with "name" being required (which isn't an issue if files is removed) and if you do it like so:

files = {
"name": "some_title",
"description_html": "some_description",
"image": (open((image_path, "rb"))
}

response = requests.post(url, files=files, headers=headers)

You end up with "name" and "description_html" expecting a string, which you can't really do much with because of how the Requests library processes multipart requests.

So I eventually tried this:

todo = {
"name": "some_title",
"description_html": "some_description",
}
files = {
"image": (open((image_path, "rb"))
}

response = requests.post(url, data=todo, files=files, headers=headers)

And it worked! Note that "data" is now used instead of "json". Not sure if this will cause some hiccups down the road but it got the image to upload provided the required fields are filled in.

As for files, it's the standard workflow for uploading files via multipart forms. It can take open() as is (the filename is supplied automatically), but you can tuple it with a different filename and an optional MIME type if needed.

@whimsee commented on GitHub (Jan 18, 2025): I gave this a shot because I was already using the API via Python Requests to create entries. Except for images, which took a lot of tinkering to work out due to how Requests abstracts the process and how the php backend interprets the request. Setup: > import requests > todo = { > "name": "some_title", > "description_html": "some_description", > } > files = { > "image": (open(image_path, "rb")) > } > headers = {"Authorization" : auth_token} Originally, I tried the following: `response = requests.post(url, json=todo, files=files, headers=headers)` Note "json" was used instead of "data" because it worked so far, but apparently there's some fuzziness with the Requests library per #5087 that I actually didn't know at the time; I just prefer using json payloads. This doesn't work as far as I've tried. You always end up with validation errors with "name" being required (which isn't an issue if files is removed) and if you do it like so: > files = { > "name": "some_title", > "description_html": "some_description", > "image": (open((image_path, "rb")) > } > > response = requests.post(url, files=files, headers=headers) You end up with "name" and "description_html" expecting a string, which you can't really do much with because of how the Requests library processes multipart requests. So I eventually tried this: > todo = { > "name": "some_title", > "description_html": "some_description", > } > files = { > "image": (open((image_path, "rb")) > } > > response = requests.post(url, data=todo, files=files, headers=headers) And it worked! Note that "data" is now used instead of "json". Not sure if this will cause some hiccups down the road but it got the image to upload provided the required fields are filled in. As for files, it's the standard workflow for uploading files via multipart forms. It can take open() as is (the filename is supplied automatically), but you can tuple it with a different filename and an optional MIME type if needed.
Author
Owner

@ssddanbrown commented on GitHub (Jan 18, 2025):

@whimsee You can send everything via the files parameter as shown here: 28f1407c82/python-upload-attachment/main.py (L68-L75)

Just have to use the tuple format expected by requests.

@ssddanbrown commented on GitHub (Jan 18, 2025): @whimsee You can send everything via the `files` parameter as shown here: https://codeberg.org/bookstack/api-scripts/src/commit/28f1407c82b92d297ac6f322b296bdde51cf97b3/python-upload-attachment/main.py#L68-L75 Just have to use the tuple format expected by requests.
Author
Owner

@whimsee commented on GitHub (Jan 18, 2025):

Ah. Nice. Good to know there's another, more elegant solution. Thanks! I'll repurpose that for my code and hopefully this helps whoever else is having this issue since I couldn't find that when I looking for a solution.

@whimsee commented on GitHub (Jan 18, 2025): Ah. Nice. Good to know there's another, more elegant solution. Thanks! I'll repurpose that for my code and hopefully this helps whoever else is having this issue since I couldn't find that when I looking for a solution.
Author
Owner

@ssddanbrown commented on GitHub (Jan 18, 2025):

I'm going to close this off since there was no further follow up from OP.
Thanks @toras9000 for helping originally, and thanks @whimsee for sharing your own solution/findings to help others!

@ssddanbrown commented on GitHub (Jan 18, 2025): I'm going to close this off since there was no further follow up from OP. Thanks @toras9000 for helping originally, and thanks @whimsee for sharing your own solution/findings to help others!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/BookStack#4380