Get upload credentials
curl --request POST \
--url https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/ \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"name": "<string>",
"type": "<string>",
"size": 123,
"external_id": "<string>",
"external_source": "<string>"
}
'import requests
url = "https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/"
payload = {
"name": "<string>",
"type": "<string>",
"size": 123,
"external_id": "<string>",
"external_source": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
type: '<string>',
size: 123,
external_id: '<string>',
external_source: '<string>'
})
};
fetch('https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'type' => '<string>',
'size' => 123,
'external_id' => '<string>',
'external_source' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"type\": \"<string>\",\n \"size\": 123,\n \"external_id\": \"<string>\",\n \"external_source\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"type\": \"<string>\",\n \"size\": 123,\n \"external_id\": \"<string>\",\n \"external_source\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"type\": \"<string>\",\n \"size\": 123,\n \"external_id\": \"<string>\",\n \"external_source\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyWork Item Attachments
Get upload credentials
Creates a pre-signed POST form data for uploading an attachment directly to S3. This endpoint handles the first step of the two-and-a-half step upload process where you first get the upload credentials and then use them to upload the actual file.
POST
/
api
/
v1
/
workspaces
/
{workspace_slug}
/
projects
/
{project_id}
/
work-items
/
{work_item_id}
/
attachments
/
Get upload credentials
curl --request POST \
--url https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/ \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"name": "<string>",
"type": "<string>",
"size": 123,
"external_id": "<string>",
"external_source": "<string>"
}
'import requests
url = "https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/"
payload = {
"name": "<string>",
"type": "<string>",
"size": 123,
"external_id": "<string>",
"external_source": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
type: '<string>',
size: 123,
external_id: '<string>',
external_source: '<string>'
})
};
fetch('https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'type' => '<string>',
'size' => 123,
'external_id' => '<string>',
'external_source' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"type\": \"<string>\",\n \"size\": 123,\n \"external_id\": \"<string>\",\n \"external_source\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"type\": \"<string>\",\n \"size\": 123,\n \"external_id\": \"<string>\",\n \"external_source\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/attachments/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"type\": \"<string>\",\n \"size\": 123,\n \"external_id\": \"<string>\",\n \"external_source\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyPath parameters
string
required
The workspace_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL
https://app.plane.so/my-team/projects/, the workspace slug is my-team.string
required
The unique identifier of the project.
string
required
The unique identifier of the work item.
Body parameters
string
required
Original filename of the attachment.
string
MIME type of the file (e.g.,
image/png, application/pdf).integer
required
Size of the file in bytes.
string
External identifier for the asset (for integration tracking).
string
External source system (for integration tracking).
Response
Returns an object containing the S3 pre-signed POST data for direct upload.{
"upload_data": {
"url": "<upload-url>",
"fields": {
"Content-Type": "image/png",
"key": "<workspace-id>/<unique-id>-filename",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-credential": "<REDACTED_AWS_KEY>/<date>/<region>/s3/aws4_request",
"x-amz-date": "<ISO8601_TIMESTAMP>",
"policy": "<BASE64_ENCODED_POLICY>",
"x-amz-signature": "<SIGNATURE_HASH>"
}
},
"asset_id": "<uuid>",
"attachment": {
"id": "<uuid>",
"created_at": "2025-01-03T12:07:35.621734Z",
"updated_at": "2025-01-03T12:07:35.621766Z",
"deleted_at": null,
"attributes": {
"name": "filename",
"type": "image/png",
"size": 5242880
},
"asset": "<workspace-id>/<unique-id>-filename",
"entity_type": "ISSUE_ATTACHMENT",
"is_deleted": false,
"is_archived": false,
"external_id": null,
"external_source": null,
"size": 5242880.0,
"is_uploaded": false,
"storage_metadata": {},
"created_by": "<user-id>",
"updated_by": null,
"workspace": "<workspace-id>",
"project": "<project-id>",
"issue": "<issue-id>",
},
"asset_url": "/api/assets/v2/workspaces/<workspace_slug>/projects/<project-id>/work-items/<issue-id>/attachments/<asset-id>/"
}
⌘I

