Confirm a workspace page attachment upload
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:requiredstringThe 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:optionalbooleanDefaults 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:optionalstringComma-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
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | The page is locked or archived, or the body failed to parse. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. |
403 | forbidden | Your role or token scope can't edit this page, or the page is private and you lack Edit access to it. |
404 | not_found | No such wiki page or attachment, the attachment is on another page, or it was deleted. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | governance_migration_in_progress | A governance migration is running in this workspace, so writes are paused. Retry after it finishes. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. 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.
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
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88"
"/attachments/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/",
headers={"X-Api-Key": "your-api-key"},
json={"is_uploaded": True},
)
print(response.json()["is_uploaded"])const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88" +
"/attachments/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({ is_uploaded: true }),
},
);
const data = await response.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"
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "Page is archived."
}
