Skip to content

Download a workspace page attachment ​

GET/api/v2/workspaces/{slug}/pages/{page_id}/attachments/{pk}/download/

Get the file behind an attachment. The API doesn't stream the bytes. It answers 302 Found with a Location header that points at a signed URL in object storage, and your client fetches the file from there. This is the one endpoint in the attachment resource that doesn't return JSON on success.

The signed URL is short-lived. It expires after the number of seconds in the instance's SIGNED_URL_EXPIRATION setting, 3600 by default. Request a fresh redirect each time you need the file instead of storing the URL. Storage serves the file as a download, with the attachment's name as the file name. On a self-hosted instance with USE_STORAGE_PROXY turned on, the URL points at the Plane server's download proxy instead of the storage host.

The attachment must be confirmed. An attachment whose upload you haven't confirmed returns 400 invalid_request with detail The attachment has not been uploaded.

Downloads work on locked and archived pages. You need read access to the page, the same as for Get a workspace page attachment.

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 id of the attachment to download.

Scopes ​

wiki.pages:read

Errors ​

StatusCodeCause
400invalid_requestThe attachment's upload hasn't been confirmed yet.
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 read this page.
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.
429rate_limitedThrottled. Honor the Retry-After header before retrying.

Follow the redirect without your API key

Most HTTP clients follow a 302 automatically. The signed URL carries its own authorization, so storage doesn't need your Plane credentials. The examples read the Location header instead of following it, so the X-Api-Key header never reaches storage.

Download a workspace page attachment
bash
curl -s -o /dev/null -w "%{redirect_url}\n" \
  "https://api.plane.so/api/v2/workspaces/my-team/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/attachments/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/download/" \
  -H "X-Api-Key: $PLANE_API_KEY"
Response302
http
HTTP/1.1 302 Found
Location: https://plane-uploads.s3.amazonaws.com/a3d1c5e7-2b4f-4a69-8c0d-7e5f1b3a9d24/5e8b1f0c9d2a4b7e8f3c6a1d0b9e2f47-architecture-diagram.png?response-content-disposition=attachment%3B%20filename%3D%22architecture-diagram.png%22&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Signature=...
Response400
json
{
  "type": "invalid_request",
  "code": "invalid_request",
  "detail": "The attachment has not been uploaded."
}