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 is idempotent. A second confirm returns 200 and changes nothing.

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. Sending false changes nothing. It doesn't move a confirmed attachment back to unconfirmed.

Response shaping ​

fields:optionalstring

Comma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.

Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.

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.
409conflictAll write endpoints declare this status. This endpoint has no conflict case of its own.
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."
}