Skip to content

Confirm a workspace page attachment upload ​

PATCH/api/v2/workspaces/{slug}/pages/{page_id}/attachments/{pk}/

Finish an upload. This is the last of three steps. Call it after the file reaches storage. The call sets the attachment's is_uploaded to true. The attachment then appears in List workspace page attachments, and Download works for it.

This PATCH is the confirm step and nothing else. It changes no other field, and you can't rename an attachment or change its type.

The API doesn't check that the file is in storage before it marks the attachment uploaded. Confirm only after the storage POST succeeds. After the confirm, Plane reads the stored file's metadata in the background.

Confirming again is safe. A second confirm returns 200 and leaves is_uploaded and created_by_id as they are. It still saves the attachment, so updated_at and updated_by_id change.

Path parameters ​

slug:requiredstring

The workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.

page_id:requiredstring (uuid)

The wiki page the attachment belongs to.

pk:requiredstring (uuid)

The attachment id, which is the asset_id from the create call.

Body parameters ​

The body is optional. An empty body confirms the upload.

is_uploaded:optionalboolean

Defaults to true. Send a JSON boolean. Any non-empty string, including "false", confirms the upload. Sending false leaves is_uploaded as it is. It doesn't move a confirmed attachment back to unconfirmed.

Response shaping ​

fields:optionalstring

Comma-separated list of fields to return. Fields you don't request are left out of the response instead of coming back as null, so a missing key means you didn't ask for it and null means the value is null. id is always included, whether or not you name it.

Pass all to get every requestable field. An unknown field name returns 400 with the list of valid names and the closest match, so a typo is caught instead of being silently ignored.

Requestable here: asset_url, attributes, content_type, created_at, created_by_id, id, is_uploaded, name, page_id, size, updated_at, updated_by_id.

See Sparse fields.

Scopes ​

wiki.pages:write

Errors ​

StatusCodeCause
400invalid_requestThe page is locked or archived, or the body failed to parse.
401unauthorizedMissing or invalid credentials.
402payment_requiredThe feature this endpoint belongs to isn't enabled on your plan, or is switched off.
403forbiddenYour role or token scope can't edit this page, or the page is private and you lack Edit access to it.
404not_foundNo such wiki page or attachment, the attachment is on another page, or it was deleted.
406not_acceptableThe Accept header asks for a representation the API can't produce.
409governance_migration_in_progressA governance migration is running in this workspace, so writes are paused. Retry after it finishes.
413payload_too_largeThe request body is over the size limit.
415unsupported_media_typeThe Content-Type isn't one this endpoint accepts.
429rate_limitedThrottled. Honor the Retry-After header before retrying.

Confirming changes the creator

On the first confirm, created_by_id becomes the user who confirmed the upload. When one service requests uploads and another confirms them, the attachment records the confirming user.

Confirm a workspace page attachment upload
bash
curl -X PATCH \
  "https://api.plane.so/api/v2/workspaces/my-team/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/attachments/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "is_uploaded": true
}'
Response200
json
{
  "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13",
  "page_id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88",
  "name": "architecture-diagram.png",
  "content_type": "image/png",
  "size": 482133.0,
  "attributes": {
    "name": "architecture-diagram.png",
    "type": "image/png",
    "size": 482133
  },
  "asset_url": "/api/assets/v2/workspaces/my-team/projects/None/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/",
  "is_uploaded": true,
  "created_at": "2026-01-14T09:22:41.478363Z",
  "updated_at": "2026-01-14T09:23:05.112907Z",
  "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "updated_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"
}
Response400
json
{
  "type": "invalid_request",
  "code": "invalid_request",
  "detail": "Page is archived."
}