Images
Upload an image through the API, then use it on a page you create or update. An upload doesn't need a page, so you can upload first and create the page that shows it afterwards.
All examples use https://acme.folioso.dev/api/v1; use your organisation's address instead.
Upload an image
POST /imagesNeeds the write ability, and a plan that includes image uploads. See the pricing page. Send the image as multipart/form-data, not JSON:
| Field | Rules | Description |
|---|---|---|
image |
Required. A PNG, JPEG, GIF, WebP or AVIF file. | The image. The largest file your plan allows is the same as in the app. |
alt |
Optional. Text, up to 500 characters. | The image's alt text. |
curl https://acme.folioso.dev/api/v1/images \
-X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
-F "image=@request-flow.png" \
-F "alt=Request flow"The response is 201 Created:
{
"data": {
"id": 318,
"url": "https://folioso.dev/storage/images/0b9f4c5e-8d7a-4f1e-9c2b-3a4d5e6f7a8b.webp",
"width": 1280,
"height": 720,
"alt": "Request flow",
"expiresAt": "2026-10-20T11:15:02+00:00"
}
}| Field | Type | Description |
|---|---|---|
id |
integer | The upload's identifier. |
url |
string | The stored image's address. Use it, exactly as returned, to put the image on a page. Its host can differ from your organisation's address. |
width, height |
integer | The stored image's size in pixels. |
alt |
string or null | The alt text you sent. |
expiresAt |
string | The time by which a page write must use the image. See How long an upload is kept. |
Uploads are processed exactly as in the app: the file's contents are checked, it's converted to WebP, and animated images are stored as a still. See Images.
Uploading the same file twice makes two separate uploads.
An upload counts towards the organisation's image storage from the moment it's made, while it's still held, so a full organisation refuses the upload itself rather than the page write that uses it.
Use the image on a page
Put the upload's url in an image on a page you create or update with POST /pages or PATCH /pages/{slug}. In TipTap JSON, it's the image's src:
{
"type": "image",
"attrs": { "src": "https://folioso.dev/storage/images/0b9f4c5e-8d7a-4f1e-9c2b-3a4d5e6f7a8b.webp", "alt": "Request flow" }
}In Markdown, it's the image's address:
{
"type": "doc",
"content": [
{
"type": "image",
"attrs": { "src": "https://folioso.dev/storage/images/0b9f4c5e-8d7a-4f1e-9c2b-3a4d5e6f7a8b.webp", "alt": "Request flow", "title": null }
}
]
}The alt text on the page is whatever the image node says. The alt you sent with the upload isn't copied onto the page. To set the image's width or alignment, see Block attributes.
When a page write's body names an upload, the upload becomes one of that page's images, exactly like an image added in the editor. It's kept for as long as a page or snapshot uses it, and it counts towards the organisation's image storage. See Uploads.
- The upload must be named in the body you send, as
contentormarkdown. APATCHthat only renames the page doesn't use it. - Only an upload made with a token for the same organisation as the page can be used. Naming another organisation's upload doesn't move it.
- One upload can be used on several pages. It belongs to the first page whose write names it.
- A write refused with
409 Conflictstill makes the upload the page's image, although the page's content doesn't change. Write the page again to use it. If no page ever does, it's deleted like any image that's no longer used. Every other refusal (403,404,422) comes before the upload is used, so it stays held as before.
How long an upload is kept
An upload no page uses is held until its expiresAt, which is one hour after the upload by default. Once that time has passed, a clean-up that runs every 10 minutes deals with it:
- Still unused: it's deleted.
- Used by a page in your organisation, for example one written with a different token: it becomes that page's image, as if the page had named it.
- Used only elsewhere, such as in another organisation's page or in a snapshot: it's kept for as long as that content uses it, and isn't counted towards your storage.
So write the page before expiresAt. An upload can survive for up to about 10 minutes after it, but don't rely on that.
Errors
| Status | Message | Why |
|---|---|---|
403 |
Invalid ability provided. |
The token is read-only. |
403 |
Your plan does not include this feature. |
The organisation's plan doesn't include image uploads. |
404 |
Image uploads are switched off on this Folioso installation. | |
413 |
That image has too many pixels. |
The image is over 25 megapixels. |
413 |
That image is too large. |
The image is still over 25 MB once converted to WebP. |
422 |
Images must be under N MB on your plan. |
The file is over your plan's largest image. It's also under errors.image. |
422 |
This organisation doesn't have room for this image within its … of image storage. … |
The organisation's image storage is full. The body also has an upgrade object naming the limit. |
422 |
That image could not be read. |
The file is damaged or isn't really an image. |
422 |
image is missing, or its contents aren't a PNG, JPEG, GIF, WebP or AVIF image whatever its name (under errors.image), or alt is too long. See errors. |
|
429 |
Too Many Attempts. |
Your account has uploaded more than 50 images in the last hour. The limit is shared with every image you upload in the app too, including avatars and logos. See Rate limits. |