API: Attachments

Attachments are files that were uploaded to OpenProject. Each attachment belongs to a single container (e.g. a work package or a board message).

Direct uploads

Instead of sending the file content to POST /api/v3/attachments (or one of its per-container equivalents, e.g. POST /api/v3/work_packages/{id}/attachments) directly, clients can, where supported by the server configuration, upload the file straight to the underlying storage backend. This is a three-step process starting at POST /api/v3/attachments/prepare (or its per-container equivalents, e.g. POST /api/v3/work_packages/{id}/attachments/prepare) - see that operation for a full description of the flow.

Actions

Link Description Condition
delete Deletes this attachment Permission: edit on attachment container or being the author for attachments without container

Linked Properties

Link Description Type Constraints Supported operations
self This attachment Attachment not null READ
container The object (e.g. WorkPackage) housing the attachment Anything not null READ
author The user who uploaded the attachment User not null READ
downloadLocation Direct download link to the attachment - not null READ

Local Properties

Property Description Type Constraints Supported operations
id Attachment’s id Integer x > 0 READ
title The name of the file String not null READ
fileName The name of the uploaded file String not null READ
fileSize The size of the uploaded file in Bytes Integer x >= 0 READ
description A user provided description of the file Formattable not null READ
contentType The files MIME-Type as determined by the server String not null READ
digest A checksum for the files content Digest not null READ
createdAt Time of creation DateTime not null READ

Methods

List attachments by activity

List all attachments of a single activity.

id
integer

required path

ID of the activity whose attachments will be listed

Example:
1

200

OK

Attachments_Model
{
  "allOf": [
    {
      "$ref": "#/components/schemas/CollectionModel"
    },
    {
      "type": "object",
      "required": [
        "_links",
        "_embedded"
      ],
      "properties": {
        "_links": {
          "type": "object",
          "required": [
            "self"
          ],
          "properties": {
            "self": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Link"
                },
                {
                  "description": "The attachments collection\n\n**Resource**: AttachmentsCollection",
                  "readOnly": true
                }
              ]
            }
          }
        },
        "_embedded": {
          "type": "object",
          "properties": {
            "elements": {
              "type": "array",
              "readOnly": true,
              "items": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AttachmentModel"
                  },
                  {
                    "description": "Collection of Attachments"
                  }
                ]
              }
            }
          }
        }
      }
    }
  ]
}

404

Returned if the activity does not exist or the client does not have sufficient permissions to see it.

Required permission:

  • view_work_packages
  • for internal comments: view_internal_comments

Note: A client without sufficient permissions shall not be able to test for the existence of an activity. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Add attachment to activity

Adds an attachment to the specified activity.

id
integer

required path

ID of the activity to receive the attachment

Example:
1

{
  "metadata": {
    "type": "object",
    "properties": {
      "fileName": {
        "type": "string"
      }
    }
  },
  "file": {
    "type": "string",
    "format": "binary"
  }
}

200

OK

AttachmentModel
{
  "type": "object",
  "required": [
    "fileName",
    "description",
    "status",
    "contentType",
    "digest",
    "createdAt"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Attachment's id",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name of the uploaded file"
    },
    "fileSize": {
      "type": "integer",
      "description": "The size of the uploaded file in Bytes",
      "minimum": 0
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file"
        }
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "uploaded",
        "prepared",
        "scanned",
        "quarantined",
        "rescan"
      ]
    },
    "contentType": {
      "type": "string",
      "description": "The files MIME-Type as determined by the server"
    },
    "digest": {
      "type": "object",
      "description": "A checksum for the files content",
      "required": [
        "algorithm",
        "hash"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "description": "The algorithm used to generate the digest."
        },
        "hash": {
          "type": "string",
          "description": "The hexadecimal representation of the digested hash value."
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time of creation"
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "container",
        "author",
        "downloadLocation"
      ],
      "properties": {
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes this attachment\n\n# Conditions\n\n**Permission**: edit on attachment container or being the author for attachments without container"
            }
          ]
        },
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "This attachment\n\n**Resource**: Attachment"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) housing the attachment\n\n**Resource**: Anything"
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user who uploaded the attachment\n\n**Resource**: User"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "Attachment",
    "_links": {
      "self": {
        "href": "/api/v3/attachments/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/1/content"
      },
      "downloadLocation": {
        "href": "/some/remote/aws/url/image.png"
      }
    },
    "id": 1,
    "fileName": "cat.png",
    "filesize": 24,
    "status": "uploaded",
    "description": {
      "format": "plain",
      "raw": "A picture of a cute cat",
      "html": "<p>A picture of a cute cat</p>"
    },
    "contentType": "image/png",
    "digest": {
      "algorithm": "md5",
      "hash": "64c26a8403cd796ea4cf913cda2ee4a9"
    },
    "createdAt": "2014-05-21T08:51:20.396Z"
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting one of the required parts (metadata and file)

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: view_work_packages or view_internal_comments (for internal comments)

Note that you will only receive this error, if you are at least allowed to see the activity

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to add attachments to this activity."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the activity does not exist or the client does not have sufficient permissions to see it.

Required permission: view_work_packages or view_internal_comments (for internal comments)

Note: A client without sufficient permissions shall not be able to test for the existence of an activity. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to send an invalid attachment. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Sending a file that is too large

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File is too large (maximum size is 5242880 Bytes)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Prepare a direct attachment upload for an activity

Prepares a direct upload of an attachment for the given activity (e.g. a work package comment), to be performed by the client without the file content passing through the OpenProject server. See POST /api/v3/attachments/prepare for a detailed description of the three-step flow this endpoint is the first step of.

Unlike POST /api/v3/attachments/prepare, the resulting pending attachment is immediately associated with this activity as its container.

id
integer

required path

ID of the activity to receive the attachment

Example:
1

{
  "type": "object",
  "required": [
    "metadata"
  ],
  "properties": {
    "metadata": {
      "type": "string",
      "contentMediaType": "application/json",
      "contentSchema": {
        "$ref": "#/components/schemas/AttachmentPrepareUploadWriteModel"
      },
      "description": "A **single** part, named `metadata`, whose value is a *JSON-encoded string*\nconforming to the schema referenced above (see the *Model* tab for its\nproperties) - it must **not** be split into separate `fileName`, `fileSize`, ...\nform fields. No file content is sent with this request.",
      "example": "{\"fileName\":\"cat.png\",\"fileSize\":24,\"contentType\":\"image/png\"}"
    }
  }
}

201

OK

AttachmentUploadModel
{
  "type": "object",
  "required": [
    "_type",
    "_links",
    "id",
    "fileName"
  ],
  "properties": {
    "_type": {
      "type": "string",
      "enum": [
        "AttachmentUpload"
      ]
    },
    "id": {
      "type": "integer",
      "description": "The id of the pending `Attachment` record that was created for this upload. It can be used to\nreference the attachment (e.g. for deletion) before the upload has been completed.",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name the file will be stored under, as given in the request."
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file."
        }
      ]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time this pending attachment record was created."
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "author",
        "addAttachment",
        "completeUpload"
      ],
      "properties": {
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Not an actual retrievable resource. As the pending upload is a temporary, one-time\nobject, requesting this URL directly will return a `404 Not Found`. Use the `id`\nproperty together with the container's attachments endpoints once the upload has\nbeen completed."
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user preparing the upload, i.e. the currently authenticated user.\n\n**Resource**: User"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) the resulting attachment will belong to. Absent if the\nupload was prepared without a container (see `POST /api/v3/attachments/prepare`).\n\n**Resource**: Anything"
            }
          ]
        },
        "addAttachment": {
          "type": "object",
          "description": "The direct upload instruction for the storage backend (e.g. an S3-compatible object store).\nThe client must issue exactly this request, unmodified, to upload the actual file content.\n\n# Conditions\n\nThe storage backend, and therefore the exact set of `form_fields`, is implementation\ndefined and may change between requests and OpenProject versions. Clients *must not*\nassume a specific set of fields and instead pass through the ones given.",
          "required": [
            "href",
            "method",
            "form_fields"
          ],
          "properties": {
            "href": {
              "type": "string",
              "description": "The URL of the storage backend the file needs to be uploaded to."
            },
            "method": {
              "type": "string",
              "description": "The HTTP verb to use for the direct upload request (currently always `post`)."
            },
            "form_fields": {
              "type": "object",
              "description": "A set of additional fields that *must* be included, in this exact form and order, as\nparts of the `multipart/form-data` request sent to `href`, in addition to a `file`\npart carrying the raw file content (which *must* be the last part of the request).\n\nThese fields typically include storage credentials in the form of a short-lived,\npre-signed upload policy (e.g. `key`, `policy`, `X-Amz-Credential`, `X-Amz-Signature`,\n`X-Amz-Algorithm`, `X-Amz-Date`, `success_action_status`, `Content-Type` for an S3\nbackend) and *must* be treated as opaque values by the client.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "completeUpload": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "To be called by the client after the direct upload to `addAttachment` has finished\nsuccessfully, in order to complete the attachment.\n\n**Resource**: Attachment"
            }
          ]
        },
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes the pending attachment, e.g. to abort the upload.\n\n**Resource**: -"
            }
          ]
        },
        "staticDownloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment once it has been uploaded and stored on the\nOpenProject server.\n\n**Resource**: -"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Download link to the attachment. Points to the external storage location once the\nattachment resides in remote storage, otherwise identical to `staticDownloadLocation`.\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "AttachmentUpload",
    "id": 42,
    "fileName": "cat.png",
    "description": {
      "format": "plain",
      "raw": "",
      "html": ""
    },
    "createdAt": "2014-05-21T08:51:20.396Z",
    "_links": {
      "self": {
        "href": "/api/v3/attachment_upload/42",
        "title": "cat.png"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "addAttachment": {
        "href": "https://my-bucket.s3.amazonaws.com/",
        "method": "post",
        "form_fields": {
          "key": "uploads/42/cat.png",
          "acl": "private",
          "success_action_status": "201",
          "Content-Type": "image/png",
          "X-Amz-Credential": "AKIAIOSFODNN7EXAMPLE/20260101/eu-west-1/s3/aws4_request",
          "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
          "X-Amz-Date": "20260101T000000Z",
          "policy": "eyJleHBpcmF0aW9uIjogIjIwMjYtMDEtMDFUMDE6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbXX0=",
          "X-Amz-Signature": "abcdef0123456789"
        }
      },
      "completeUpload": {
        "href": "/api/v3/attachments/42/uploaded"
      },
      "delete": {
        "href": "/api/v3/attachments/42",
        "method": "delete"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/42/content"
      },
      "downloadLocation": {
        "href": "/api/v3/attachments/42/content"
      }
    }
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting the metadata part

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: add_work_package_comments

Note that you will only receive this error, if you are at least allowed to see the activity

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to add attachments to this activity."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the activity does not exist or the client does not have sufficient permissions to see it.

Required permission:

  • view_work_packages
  • for internal comments: view_internal_comments

Note: A client without sufficient permissions shall not be able to test for the existence of an activity. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to prepare an invalid attachment upload. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Omitting the file size (fileSize property of metadata part)

  • The instance is not configured for direct uploads

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Direct file upload is not available due to a system configuration issue. Please contact your administrator."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File can't be blank."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Create attachment

Clients can create attachments without a container first and attach them later on. This is useful if the container does not exist at the time the attachment is uploaded. After the upload, the client can then claim such containerless attachments for any resource eligible (e.g. WorkPackage) on subsequent requests. The upload and the claiming must be done for the same user account. Attachments uploaded by another user cannot be claimed and once claimed for a resource, they cannot be claimed by another.

The upload request must be of type multipart/form-data with exactly two parts.

The first part must be called metadata. Its content type is expected to be application/json, the body must be a single JSON object, containing at least the fileName and optionally the attachments description.

The second part must be called file, its content type should match the mime type of the file. The body must be the raw content of the file. Note that a filename must be indicated in the Content-Disposition of this part, although it will be ignored. Instead the fileName inside the JSON of the metadata part will be used.

No parameters

200

OK

AttachmentModel
{
  "type": "object",
  "required": [
    "fileName",
    "description",
    "status",
    "contentType",
    "digest",
    "createdAt"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Attachment's id",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name of the uploaded file"
    },
    "fileSize": {
      "type": "integer",
      "description": "The size of the uploaded file in Bytes",
      "minimum": 0
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file"
        }
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "uploaded",
        "prepared",
        "scanned",
        "quarantined",
        "rescan"
      ]
    },
    "contentType": {
      "type": "string",
      "description": "The files MIME-Type as determined by the server"
    },
    "digest": {
      "type": "object",
      "description": "A checksum for the files content",
      "required": [
        "algorithm",
        "hash"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "description": "The algorithm used to generate the digest."
        },
        "hash": {
          "type": "string",
          "description": "The hexadecimal representation of the digested hash value."
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time of creation"
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "container",
        "author",
        "downloadLocation"
      ],
      "properties": {
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes this attachment\n\n# Conditions\n\n**Permission**: edit on attachment container or being the author for attachments without container"
            }
          ]
        },
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "This attachment\n\n**Resource**: Attachment"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) housing the attachment\n\n**Resource**: Anything"
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user who uploaded the attachment\n\n**Resource**: User"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "Attachment",
    "_links": {
      "self": {
        "href": "/api/v3/attachments/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/1/content"
      },
      "downloadLocation": {
        "href": "/some/remote/aws/url/image.png"
      }
    },
    "id": 1,
    "fileName": "cat.png",
    "filesize": 24,
    "status": "uploaded",
    "description": {
      "format": "plain",
      "raw": "A picture of a cute cat",
      "html": "<p>A picture of a cute cat</p>"
    },
    "contentType": "image/png",
    "digest": {
      "algorithm": "md5",
      "hash": "64c26a8403cd796ea4cf913cda2ee4a9"
    },
    "createdAt": "2014-05-21T08:51:20.396Z"
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting one of the required parts (metadata and file)

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: At least one permission in any project: edit work package, add work package, edit messages, edit wiki pages (plugins might extend this list)

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to delete this attachment."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to send an invalid attachment. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Sending a file that is too large

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File is too large (maximum size is 5242880 Bytes)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Prepare a direct attachment upload

Prepares a direct upload of a file to OpenProject’s attachment storage, bypassing the OpenProject server for the actual file transfer.

Why this endpoint exists

Attachments can always be created in one step by sending the raw file content together with the request, see POST /api/v3/attachments and the equivalent .../attachments endpoints of the various containers (work packages, wiki pages, meetings, …). For large files or slow connections this means the file has to be transferred to the OpenProject server first, and then from there to the configured remote storage (e.g. an S3-compatible object store) - twice the traffic and twice the time.

When OpenProject is configured to use a remote, S3-compatible storage for attachments, it can instead hand the client a short-lived, pre-signed upload instruction that allows the client to upload the file directly to that storage, without the OpenProject server ever being involved in the transfer. This is what this endpoint, and its per-container siblings such as POST /api/v3/work_packages/{id}/attachments/prepare, are used for.

If direct uploads are not available (e.g. because attachments are stored on the local disk of the OpenProject server, or a remote storage without access-key credentials is used), this request fails with a 422 error. Clients should fall back to the regular, single-step upload in that case.

The three-step flow

  1. Prepare (this request): the client tells the server the file name and size (and optionally its content type, description and a checksum) it intends to upload. The server creates a pending Attachment (status: prepared) without any file content yet and responds with an AttachmentUpload resource. That resource contains, among others:

    • _links.addAttachment: the direct upload instruction (href, method and form_fields) for the storage backend.
    • _links.completeUpload: the link to call once step 2 has finished.
  2. Upload: the client sends a multipart/form-data request exactly as instructed by _links.addAttachment, i.e. to the given href, using the given method, including every given form_fields entry as its own part (in the given order), and finally a file part carrying the raw file content as the last part of the request. This request goes directly to the storage backend, not to the OpenProject server.

  3. Complete (GET the href given in _links.completeUpload, i.e. /api/v3/attachments/{id}/uploaded): tells the OpenProject server that the direct upload has finished. The server verifies that the file is now present in the storage, and asynchronously finalizes the attachment (determining its definitive content type, matching it against the configured content type allowlist if any, running a virus scan if enabled, and creating activity/notifications for its container). The response is the regular Attachment resource. Until this step has completed, the pending attachment is not considered part of its container and will eventually be cleaned up if it is never completed.

Only the user that prepared the upload can complete it; a pending upload that is never completed (e.g. because the client failed to perform step 2 or 3) is garbage collected after some time.

Container vs. containerless uploads

This endpoint (/api/v3/attachments/prepare) does not take a container - it prepares an attachment that is not yet attached to anything, e.g. because the containing resource (a new work package, for instance) does not exist yet. Such a containerless attachment can later be claimed by referencing its id while creating the eligible resource, exactly like containerless attachments created via POST /api/v3/attachments.

Most containers that support attachments (e.g. work packages, wiki pages, meetings, forum messages, activity comments) additionally expose the very same operation scoped to an existing instance of themselves, e.g. POST /api/v3/work_packages/{id}/attachments/prepare. Using one of those instead immediately associates the resulting attachment with that container, and is subject to the same permission requirements as POST .../attachments on that container.

No parameters
{
  "type": "object",
  "required": [
    "metadata"
  ],
  "properties": {
    "metadata": {
      "type": "string",
      "contentMediaType": "application/json",
      "contentSchema": {
        "$ref": "#/components/schemas/AttachmentPrepareUploadWriteModel"
      },
      "description": "A **single** part, named `metadata`, whose value is a *JSON-encoded string*\nconforming to the schema referenced above (see the *Model* tab for its\nproperties) - it must **not** be split into separate `fileName`, `fileSize`, ...\nform fields. This is, unlike the regular attachment creation endpoints, the\n*only* part of this request - no file content is sent here.",
      "example": "{\"fileName\":\"cat.png\",\"fileSize\":24,\"contentType\":\"image/png\"}"
    }
  }
}

201

OK

AttachmentUploadModel
{
  "type": "object",
  "required": [
    "_type",
    "_links",
    "id",
    "fileName"
  ],
  "properties": {
    "_type": {
      "type": "string",
      "enum": [
        "AttachmentUpload"
      ]
    },
    "id": {
      "type": "integer",
      "description": "The id of the pending `Attachment` record that was created for this upload. It can be used to\nreference the attachment (e.g. for deletion) before the upload has been completed.",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name the file will be stored under, as given in the request."
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file."
        }
      ]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time this pending attachment record was created."
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "author",
        "addAttachment",
        "completeUpload"
      ],
      "properties": {
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Not an actual retrievable resource. As the pending upload is a temporary, one-time\nobject, requesting this URL directly will return a `404 Not Found`. Use the `id`\nproperty together with the container's attachments endpoints once the upload has\nbeen completed."
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user preparing the upload, i.e. the currently authenticated user.\n\n**Resource**: User"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) the resulting attachment will belong to. Absent if the\nupload was prepared without a container (see `POST /api/v3/attachments/prepare`).\n\n**Resource**: Anything"
            }
          ]
        },
        "addAttachment": {
          "type": "object",
          "description": "The direct upload instruction for the storage backend (e.g. an S3-compatible object store).\nThe client must issue exactly this request, unmodified, to upload the actual file content.\n\n# Conditions\n\nThe storage backend, and therefore the exact set of `form_fields`, is implementation\ndefined and may change between requests and OpenProject versions. Clients *must not*\nassume a specific set of fields and instead pass through the ones given.",
          "required": [
            "href",
            "method",
            "form_fields"
          ],
          "properties": {
            "href": {
              "type": "string",
              "description": "The URL of the storage backend the file needs to be uploaded to."
            },
            "method": {
              "type": "string",
              "description": "The HTTP verb to use for the direct upload request (currently always `post`)."
            },
            "form_fields": {
              "type": "object",
              "description": "A set of additional fields that *must* be included, in this exact form and order, as\nparts of the `multipart/form-data` request sent to `href`, in addition to a `file`\npart carrying the raw file content (which *must* be the last part of the request).\n\nThese fields typically include storage credentials in the form of a short-lived,\npre-signed upload policy (e.g. `key`, `policy`, `X-Amz-Credential`, `X-Amz-Signature`,\n`X-Amz-Algorithm`, `X-Amz-Date`, `success_action_status`, `Content-Type` for an S3\nbackend) and *must* be treated as opaque values by the client.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "completeUpload": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "To be called by the client after the direct upload to `addAttachment` has finished\nsuccessfully, in order to complete the attachment.\n\n**Resource**: Attachment"
            }
          ]
        },
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes the pending attachment, e.g. to abort the upload.\n\n**Resource**: -"
            }
          ]
        },
        "staticDownloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment once it has been uploaded and stored on the\nOpenProject server.\n\n**Resource**: -"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Download link to the attachment. Points to the external storage location once the\nattachment resides in remote storage, otherwise identical to `staticDownloadLocation`.\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "AttachmentUpload",
    "id": 42,
    "fileName": "cat.png",
    "description": {
      "format": "plain",
      "raw": "",
      "html": ""
    },
    "createdAt": "2014-05-21T08:51:20.396Z",
    "_links": {
      "self": {
        "href": "/api/v3/attachment_upload/42",
        "title": "cat.png"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "addAttachment": {
        "href": "https://my-bucket.s3.amazonaws.com/",
        "method": "post",
        "form_fields": {
          "key": "uploads/42/cat.png",
          "acl": "private",
          "success_action_status": "201",
          "Content-Type": "image/png",
          "X-Amz-Credential": "AKIAIOSFODNN7EXAMPLE/20260101/eu-west-1/s3/aws4_request",
          "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
          "X-Amz-Date": "20260101T000000Z",
          "policy": "eyJleHBpcmF0aW9uIjogIjIwMjYtMDEtMDFUMDE6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbXX0=",
          "X-Amz-Signature": "abcdef0123456789"
        }
      },
      "completeUpload": {
        "href": "/api/v3/attachments/42/uploaded"
      },
      "delete": {
        "href": "/api/v3/attachments/42",
        "method": "delete"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/42/content"
      },
      "downloadLocation": {
        "href": "/api/v3/attachments/42/content"
      }
    }
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting the metadata part

  • Sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: At least one permission in any project that allows creating attachments (e.g. edit work package, add work package, edit messages, edit wiki pages; plugins might extend this list)

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to upload attachments."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to prepare an invalid attachment upload.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Direct file upload is not available due to a system configuration issue. Please contact your administrator."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File can't be blank."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Size can't be blank."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "'text/plain' is not allowed for upload."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

View attachment

id
integer

required path

Attachment id

Example:
1

200

OK

AttachmentModel
{
  "type": "object",
  "required": [
    "fileName",
    "description",
    "status",
    "contentType",
    "digest",
    "createdAt"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Attachment's id",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name of the uploaded file"
    },
    "fileSize": {
      "type": "integer",
      "description": "The size of the uploaded file in Bytes",
      "minimum": 0
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file"
        }
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "uploaded",
        "prepared",
        "scanned",
        "quarantined",
        "rescan"
      ]
    },
    "contentType": {
      "type": "string",
      "description": "The files MIME-Type as determined by the server"
    },
    "digest": {
      "type": "object",
      "description": "A checksum for the files content",
      "required": [
        "algorithm",
        "hash"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "description": "The algorithm used to generate the digest."
        },
        "hash": {
          "type": "string",
          "description": "The hexadecimal representation of the digested hash value."
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time of creation"
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "container",
        "author",
        "downloadLocation"
      ],
      "properties": {
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes this attachment\n\n# Conditions\n\n**Permission**: edit on attachment container or being the author for attachments without container"
            }
          ]
        },
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "This attachment\n\n**Resource**: Attachment"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) housing the attachment\n\n**Resource**: Anything"
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user who uploaded the attachment\n\n**Resource**: User"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "Attachment",
    "_links": {
      "self": {
        "href": "/api/v3/attachments/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/1/content"
      },
      "downloadLocation": {
        "href": "/some/remote/aws/url/image.png"
      }
    },
    "id": 1,
    "fileName": "cat.png",
    "filesize": 24,
    "status": "uploaded",
    "description": {
      "format": "plain",
      "raw": "A picture of a cute cat",
      "html": "<p>A picture of a cute cat</p>"
    },
    "contentType": "image/png",
    "digest": {
      "algorithm": "md5",
      "hash": "64c26a8403cd796ea4cf913cda2ee4a9"
    },
    "createdAt": "2014-05-21T08:51:20.396Z"
  }
}

404

Returned if the attachment does not exist or the client does not have sufficient permissions to see it.

Required permission: view permission for the container of the attachment or being the author for attachments without container

Note: A client without sufficient permissions shall not be able to test for the existence of an attachment. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The specified attachment does not exist."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Delete attachment

Permanently deletes the specified attachment.

id
integer

required path

Attachment id

Example:
1

204

Returned if the attachment was deleted successfully.

Note that the response body is empty as of now. In future versions of the API a body might be returned along with an appropriate HTTP status.

403

Returned if the client does not have sufficient permissions.

Required permission: edit permission for the container of the attachment or being the author for attachments without container

Note that you will only receive this error, if you are at least allowed to see the attachment.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to delete this attachment."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the attachment does not exist or the client does not have sufficient permissions to see it.

Required permission: view permission for the container of the attachment or being the author for attachments without container

Note: A client without sufficient permissions shall not be able to test for the existence of an attachment. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The specified attachment does not exist."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Complete a direct attachment upload

This is the third and final step of the direct upload flow described at POST /api/v3/attachments/prepare. It is called by the client after it has successfully uploaded the file content directly to the storage backend, as instructed by the addAttachment link of the preceding prepare step.

The server checks that the uploaded file is now present in the storage. If so, the attachment is scheduled to be finalized asynchronously (its definitive content type is determined, matched against the configured content type allowlist if any is configured, a virus scan is run if enabled, and the attachment’s container is notified). The finalization may therefore not yet be reflected in the returned representation, in particular the status property might still read prepared for a brief moment, but will eventually change to uploaded (or the attachment removed, should the finalization fail, e.g. because the content type is not allowed).

Calling this endpoint before the direct upload to the storage has actually finished (or if it failed) results in a 404 response.

id
integer

required path

The id of the pending attachment, as returned by the prepare step (either in the top-level id property or, equivalently, in the _links.completeUpload link of the response).

Example:
42

200

OK

AttachmentModel
{
  "type": "object",
  "required": [
    "fileName",
    "description",
    "status",
    "contentType",
    "digest",
    "createdAt"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Attachment's id",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name of the uploaded file"
    },
    "fileSize": {
      "type": "integer",
      "description": "The size of the uploaded file in Bytes",
      "minimum": 0
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file"
        }
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "uploaded",
        "prepared",
        "scanned",
        "quarantined",
        "rescan"
      ]
    },
    "contentType": {
      "type": "string",
      "description": "The files MIME-Type as determined by the server"
    },
    "digest": {
      "type": "object",
      "description": "A checksum for the files content",
      "required": [
        "algorithm",
        "hash"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "description": "The algorithm used to generate the digest."
        },
        "hash": {
          "type": "string",
          "description": "The hexadecimal representation of the digested hash value."
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time of creation"
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "container",
        "author",
        "downloadLocation"
      ],
      "properties": {
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes this attachment\n\n# Conditions\n\n**Permission**: edit on attachment container or being the author for attachments without container"
            }
          ]
        },
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "This attachment\n\n**Resource**: Attachment"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) housing the attachment\n\n**Resource**: Anything"
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user who uploaded the attachment\n\n**Resource**: User"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "Attachment",
    "_links": {
      "self": {
        "href": "/api/v3/attachments/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/1/content"
      },
      "downloadLocation": {
        "href": "/some/remote/aws/url/image.png"
      }
    },
    "id": 1,
    "fileName": "cat.png",
    "filesize": 24,
    "status": "uploaded",
    "description": {
      "format": "plain",
      "raw": "A picture of a cute cat",
      "html": "<p>A picture of a cute cat</p>"
    },
    "contentType": "image/png",
    "digest": {
      "algorithm": "md5",
      "hash": "64c26a8403cd796ea4cf913cda2ee4a9"
    },
    "createdAt": "2014-05-21T08:51:20.396Z"
  }
}

404

Returned if any of the following is true:

  • No pending, prepared upload with the given id exists (e.g. it was already completed, was never prepared, or does not belong to the current user)

  • the client does not have sufficient permissions to see the attachment

  • the file has not actually been uploaded to the storage backend yet (i.e. step 2 of the flow was skipped or has not finished successfully)

Note: A client without sufficient permissions shall not be able to test for the existence of an attachment. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

List attachments by meeting

id
integer

required path

ID of the meeting whose attachments will be listed

Example:
1

200

OK

{
  "_embedded": {
    "elements": [
      {
        "_links": {
          "author": {
            "href": "/api/v3/users/1",
            "title": "OpenProject Admin"
          },
          "container": {
            "href": "/api/v3/meetings/72",
            "title": "meeting"
          },
          "delete": {
            "href": "/api/v3/attachments/376",
            "method": "delete"
          },
          "downloadLocation": {
            "href": "/api/v3/attachments/376/content"
          },
          "self": {
            "href": "/api/v3/attachments/376",
            "title": "200.gif"
          }
        },
        "_type": "Attachment",
        "contentType": "image/gif",
        "createdAt": "2018-06-01T07:24:19.896Z",
        "description": {
          "format": "plain",
          "html": "",
          "raw": ""
        },
        "digest": {
          "algorithm": "md5",
          "hash": "7ac9c97ef73d47127f590788b84c0c1c"
        },
        "fileName": "some.gif",
        "fileSize": 3521772,
        "id": 376
      }
    ]
  },
  "_links": {
    "self": {
      "href": "/api/v3/meetings/72/attachments"
    }
  },
  "_type": "Collection",
  "count": 1,
  "total": 1
}
Attachments_Model
{
  "allOf": [
    {
      "$ref": "#/components/schemas/CollectionModel"
    },
    {
      "type": "object",
      "required": [
        "_links",
        "_embedded"
      ],
      "properties": {
        "_links": {
          "type": "object",
          "required": [
            "self"
          ],
          "properties": {
            "self": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Link"
                },
                {
                  "description": "The attachments collection\n\n**Resource**: AttachmentsCollection",
                  "readOnly": true
                }
              ]
            }
          }
        },
        "_embedded": {
          "type": "object",
          "properties": {
            "elements": {
              "type": "array",
              "readOnly": true,
              "items": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AttachmentModel"
                  },
                  {
                    "description": "Collection of Attachments"
                  }
                ]
              }
            }
          }
        }
      }
    }
  ]
}

404

Returned if the meeting does not exist or the client does not have sufficient permissions to see it.

Required permission: view meetings

Note: A client without sufficient permissions shall not be able to test for the existence of a meeting. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Add attachment to meeting

Adds an attachment with the meeting as its container.

id
integer

required path

ID of the meeting to receive the attachment

Example:
1

200

OK

{
  "_embedded": {
    "author": {
      "_links": {
        "lock": {
          "href": "/api/v3/users/1/lock",
          "method": "post",
          "title": "Set lock on admin"
        },
        "self": {
          "href": "/api/v3/users/1",
          "title": "OpenProject Admin"
        },
        "showUser": {
          "href": "/users/1",
          "type": "text/html"
        },
        "updateImmediately": {
          "href": "/api/v3/users/1",
          "method": "patch",
          "title": "Update admin"
        }
      },
      "_type": "User",
      "admin": true,
      "avatar": "",
      "createdAt": "2015-03-20T12:56:52.343Z",
      "email": null,
      "firstName": "OpenProject",
      "id": 1,
      "identityUrl": null,
      "lastName": "Admin",
      "login": "admin",
      "name": "OpenProject Admin",
      "status": "active",
      "updatedAt": "2018-05-29T13:57:44.662Z"
    },
    "container": {
      "_links": {
        "addAttachment": {
          "href": "/api/v3/meetings/72/attachments",
          "method": "post"
        },
        "attachments": {
          "href": "/api/v3/meetings/72/attachments"
        },
        "project": {
          "href": "/api/v3/projects/12",
          "title": "Demo project"
        },
        "self": {
          "href": "/api/v3/meetings/72"
        }
      },
      "_type": "Meeting",
      "id": 72,
      "title": "meeting"
    }
  },
  "_links": {
    "author": {
      "href": "/api/v3/users/1",
      "title": "OpenProject Admin"
    },
    "container": {
      "href": "/api/v3/meetings/72",
      "title": "meeting"
    },
    "delete": {
      "href": "/api/v3/attachments/376",
      "method": "delete"
    },
    "downloadLocation": {
      "href": "/api/v3/attachments/376/content"
    },
    "self": {
      "href": "/api/v3/attachments/376",
      "title": "200.gif"
    }
  },
  "_type": "Attachment",
  "contentType": "image/gif",
  "createdAt": "2018-06-01T07:24:19.896Z",
  "description": {
    "format": "plain",
    "html": "",
    "raw": ""
  },
  "digest": {
    "algorithm": "md5",
    "hash": "7ac9c97ef73d47127f590788b84c0c1c"
  },
  "fileName": "some.gif",
  "fileSize": 3521772,
  "id": 376
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting one of the required parts (metadata and file)

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit meetings

Note that you will only receive this error, if you are at least allowed to see the meeting

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to delete this attachment."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the meeting does not exist or the client does not have sufficient permissions to see it.

Required permission: view meetings

Note: A client without sufficient permissions shall not be able to test for the existence of a meeting That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to send an invalid attachment. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Sending a file that is too large

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File is too large (maximum size is 5242880 Bytes)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Prepare a direct attachment upload for a meeting

Prepares a direct upload of an attachment for the given meeting, to be performed by the client without the file content passing through the OpenProject server. See POST /api/v3/attachments/prepare for a detailed description of the three-step flow this endpoint is the first step of.

Unlike POST /api/v3/attachments/prepare, the resulting pending attachment is immediately associated with this meeting as its container.

id
integer

required path

ID of the meeting to receive the attachment

Example:
1

{
  "type": "object",
  "required": [
    "metadata"
  ],
  "properties": {
    "metadata": {
      "type": "string",
      "contentMediaType": "application/json",
      "contentSchema": {
        "$ref": "#/components/schemas/AttachmentPrepareUploadWriteModel"
      },
      "description": "A **single** part, named `metadata`, whose value is a *JSON-encoded string*\nconforming to the schema referenced above (see the *Model* tab for its\nproperties) - it must **not** be split into separate `fileName`, `fileSize`, ...\nform fields. No file content is sent with this request.",
      "example": "{\"fileName\":\"cat.png\",\"fileSize\":24,\"contentType\":\"image/png\"}"
    }
  }
}

201

OK

AttachmentUploadModel
{
  "type": "object",
  "required": [
    "_type",
    "_links",
    "id",
    "fileName"
  ],
  "properties": {
    "_type": {
      "type": "string",
      "enum": [
        "AttachmentUpload"
      ]
    },
    "id": {
      "type": "integer",
      "description": "The id of the pending `Attachment` record that was created for this upload. It can be used to\nreference the attachment (e.g. for deletion) before the upload has been completed.",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name the file will be stored under, as given in the request."
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file."
        }
      ]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time this pending attachment record was created."
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "author",
        "addAttachment",
        "completeUpload"
      ],
      "properties": {
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Not an actual retrievable resource. As the pending upload is a temporary, one-time\nobject, requesting this URL directly will return a `404 Not Found`. Use the `id`\nproperty together with the container's attachments endpoints once the upload has\nbeen completed."
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user preparing the upload, i.e. the currently authenticated user.\n\n**Resource**: User"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) the resulting attachment will belong to. Absent if the\nupload was prepared without a container (see `POST /api/v3/attachments/prepare`).\n\n**Resource**: Anything"
            }
          ]
        },
        "addAttachment": {
          "type": "object",
          "description": "The direct upload instruction for the storage backend (e.g. an S3-compatible object store).\nThe client must issue exactly this request, unmodified, to upload the actual file content.\n\n# Conditions\n\nThe storage backend, and therefore the exact set of `form_fields`, is implementation\ndefined and may change between requests and OpenProject versions. Clients *must not*\nassume a specific set of fields and instead pass through the ones given.",
          "required": [
            "href",
            "method",
            "form_fields"
          ],
          "properties": {
            "href": {
              "type": "string",
              "description": "The URL of the storage backend the file needs to be uploaded to."
            },
            "method": {
              "type": "string",
              "description": "The HTTP verb to use for the direct upload request (currently always `post`)."
            },
            "form_fields": {
              "type": "object",
              "description": "A set of additional fields that *must* be included, in this exact form and order, as\nparts of the `multipart/form-data` request sent to `href`, in addition to a `file`\npart carrying the raw file content (which *must* be the last part of the request).\n\nThese fields typically include storage credentials in the form of a short-lived,\npre-signed upload policy (e.g. `key`, `policy`, `X-Amz-Credential`, `X-Amz-Signature`,\n`X-Amz-Algorithm`, `X-Amz-Date`, `success_action_status`, `Content-Type` for an S3\nbackend) and *must* be treated as opaque values by the client.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "completeUpload": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "To be called by the client after the direct upload to `addAttachment` has finished\nsuccessfully, in order to complete the attachment.\n\n**Resource**: Attachment"
            }
          ]
        },
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes the pending attachment, e.g. to abort the upload.\n\n**Resource**: -"
            }
          ]
        },
        "staticDownloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment once it has been uploaded and stored on the\nOpenProject server.\n\n**Resource**: -"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Download link to the attachment. Points to the external storage location once the\nattachment resides in remote storage, otherwise identical to `staticDownloadLocation`.\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "AttachmentUpload",
    "id": 42,
    "fileName": "cat.png",
    "description": {
      "format": "plain",
      "raw": "",
      "html": ""
    },
    "createdAt": "2014-05-21T08:51:20.396Z",
    "_links": {
      "self": {
        "href": "/api/v3/attachment_upload/42",
        "title": "cat.png"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "addAttachment": {
        "href": "https://my-bucket.s3.amazonaws.com/",
        "method": "post",
        "form_fields": {
          "key": "uploads/42/cat.png",
          "acl": "private",
          "success_action_status": "201",
          "Content-Type": "image/png",
          "X-Amz-Credential": "AKIAIOSFODNN7EXAMPLE/20260101/eu-west-1/s3/aws4_request",
          "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
          "X-Amz-Date": "20260101T000000Z",
          "policy": "eyJleHBpcmF0aW9uIjogIjIwMjYtMDEtMDFUMDE6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbXX0=",
          "X-Amz-Signature": "abcdef0123456789"
        }
      },
      "completeUpload": {
        "href": "/api/v3/attachments/42/uploaded"
      },
      "delete": {
        "href": "/api/v3/attachments/42",
        "method": "delete"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/42/content"
      },
      "downloadLocation": {
        "href": "/api/v3/attachments/42/content"
      }
    }
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting the metadata part

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit meetings

Note that you will only receive this error, if you are at least allowed to see the meeting

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to add attachments to this meeting."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the meeting does not exist or the client does not have sufficient permissions to see it.

Required permission: view meetings

Note: A client without sufficient permissions shall not be able to test for the existence of a meeting. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to prepare an invalid attachment upload. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Omitting the file size (fileSize property of metadata part)

  • The instance is not configured for direct uploads

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Direct file upload is not available due to a system configuration issue. Please contact your administrator."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File can't be blank."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

List attachments by post

id
integer

required path

ID of the post whose attachments will be listed

Example:
1

200

OK

{
  "_embedded": {
    "elements": [
      {
        "_links": {
          "author": {
            "href": "/api/v3/users/1",
            "title": "OpenProject Admin"
          },
          "container": {
            "href": "/api/v3/posts/72",
            "title": "wiki"
          },
          "delete": {
            "href": "/api/v3/attachments/376",
            "method": "delete"
          },
          "downloadLocation": {
            "href": "/api/v3/attachments/376/content"
          },
          "self": {
            "href": "/api/v3/attachments/376",
            "title": "200.gif"
          }
        },
        "_type": "Attachment",
        "contentType": "image/gif",
        "createdAt": "2018-06-01T07:24:19.706Z",
        "description": {
          "format": "plain",
          "html": "",
          "raw": ""
        },
        "digest": {
          "algorithm": "md5",
          "hash": "7ac9c97ef73d47127f590788b84c0c1c"
        },
        "fileName": "some.gif",
        "fileSize": 3521772,
        "id": 376
      }
    ]
  },
  "_links": {
    "self": {
      "href": "/api/v3/posts/72/attachments"
    }
  },
  "_type": "Collection",
  "count": 1,
  "total": 1
}
Attachments_Model
{
  "allOf": [
    {
      "$ref": "#/components/schemas/CollectionModel"
    },
    {
      "type": "object",
      "required": [
        "_links",
        "_embedded"
      ],
      "properties": {
        "_links": {
          "type": "object",
          "required": [
            "self"
          ],
          "properties": {
            "self": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Link"
                },
                {
                  "description": "The attachments collection\n\n**Resource**: AttachmentsCollection",
                  "readOnly": true
                }
              ]
            }
          }
        },
        "_embedded": {
          "type": "object",
          "properties": {
            "elements": {
              "type": "array",
              "readOnly": true,
              "items": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AttachmentModel"
                  },
                  {
                    "description": "Collection of Attachments"
                  }
                ]
              }
            }
          }
        }
      }
    }
  ]
}

404

Returned if the post does not exist or the client does not have sufficient permissions to see it.

Required permission: view messages

Note: A client without sufficient permissions shall not be able to test for the existence of a post. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Add attachment to post

Adds an attachment with the post as its container.

id
integer

required path

ID of the post to receive the attachment

Example:
1

200

OK

{
  "_embedded": {
    "author": {
      "_links": {
        "lock": {
          "href": "/api/v3/users/1/lock",
          "method": "post",
          "title": "Set lock on admin"
        },
        "self": {
          "href": "/api/v3/users/1",
          "title": "OpenProject Admin"
        },
        "showUser": {
          "href": "/users/1",
          "type": "text/html"
        },
        "updateImmediately": {
          "href": "/api/v3/users/1",
          "method": "patch",
          "title": "Update admin"
        }
      },
      "_type": "User",
      "admin": true,
      "avatar": "",
      "createdAt": "2015-03-20T12:56:52.850Z",
      "email": null,
      "firstName": "OpenProject",
      "id": 1,
      "identityUrl": null,
      "lastName": "Admin",
      "login": "admin",
      "name": "OpenProject Admin",
      "status": "active",
      "updatedAt": "2018-05-29T13:57:44.604Z"
    },
    "container": {
      "_links": {
        "addAttachment": {
          "href": "/api/v3/posts/150/attachments",
          "method": "post"
        },
        "attachments": {
          "href": "/api/v3/posts/150/attachments"
        },
        "project": {
          "href": "/api/v3/projects/12",
          "title": "Demo project"
        },
        "self": {
          "href": "/api/v3/posts/150"
        }
      },
      "_type": "Post",
      "id": 150,
      "subject": "sfsdfsdfsdfsdf"
    }
  },
  "_links": {
    "author": {
      "href": "/api/v3/users/1",
      "title": "OpenProject Admin"
    },
    "container": {
      "href": "/api/v3/posts/150",
      "title": "sfsdfsdfsdfsdf"
    },
    "delete": {
      "href": "/api/v3/attachments/377",
      "method": "delete"
    },
    "downloadLocation": {
      "href": "/api/v3/attachments/377/content"
    },
    "self": {
      "href": "/api/v3/attachments/377",
      "title": "200.gif"
    }
  },
  "_type": "Attachment",
  "contentType": "image/gif",
  "createdAt": "2018-06-01T07:53:36.831Z",
  "description": {
    "format": "plain",
    "html": "",
    "raw": ""
  },
  "digest": {
    "algorithm": "md5",
    "hash": "7ac9c97ef73d47127f590788b84c0c1c"
  },
  "fileName": "some.gif",
  "fileSize": 3521772,
  "id": 377
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting one of the required parts (metadata and file)

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit messages

Note that you will only receive this error, if you are at least allowed to see the wiki page

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to delete this attachment."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the post does not exist or the client does not have sufficient permissions to see it.

Required permission: view messages

Note: A client without sufficient permissions shall not be able to test for the existence of a post. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to send an invalid attachment. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Sending a file that is too large

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File is too large (maximum size is 5242880 Bytes)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Prepare a direct attachment upload for a post

Prepares a direct upload of an attachment for the given forum post, to be performed by the client without the file content passing through the OpenProject server. See POST /api/v3/attachments/prepare for a detailed description of the three-step flow this endpoint is the first step of.

Unlike POST /api/v3/attachments/prepare, the resulting pending attachment is immediately associated with this post as its container.

id
integer

required path

ID of the post to receive the attachment

Example:
1

{
  "type": "object",
  "required": [
    "metadata"
  ],
  "properties": {
    "metadata": {
      "type": "string",
      "contentMediaType": "application/json",
      "contentSchema": {
        "$ref": "#/components/schemas/AttachmentPrepareUploadWriteModel"
      },
      "description": "A **single** part, named `metadata`, whose value is a *JSON-encoded string*\nconforming to the schema referenced above (see the *Model* tab for its\nproperties) - it must **not** be split into separate `fileName`, `fileSize`, ...\nform fields. No file content is sent with this request.",
      "example": "{\"fileName\":\"cat.png\",\"fileSize\":24,\"contentType\":\"image/png\"}"
    }
  }
}

201

OK

AttachmentUploadModel
{
  "type": "object",
  "required": [
    "_type",
    "_links",
    "id",
    "fileName"
  ],
  "properties": {
    "_type": {
      "type": "string",
      "enum": [
        "AttachmentUpload"
      ]
    },
    "id": {
      "type": "integer",
      "description": "The id of the pending `Attachment` record that was created for this upload. It can be used to\nreference the attachment (e.g. for deletion) before the upload has been completed.",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name the file will be stored under, as given in the request."
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file."
        }
      ]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time this pending attachment record was created."
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "author",
        "addAttachment",
        "completeUpload"
      ],
      "properties": {
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Not an actual retrievable resource. As the pending upload is a temporary, one-time\nobject, requesting this URL directly will return a `404 Not Found`. Use the `id`\nproperty together with the container's attachments endpoints once the upload has\nbeen completed."
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user preparing the upload, i.e. the currently authenticated user.\n\n**Resource**: User"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) the resulting attachment will belong to. Absent if the\nupload was prepared without a container (see `POST /api/v3/attachments/prepare`).\n\n**Resource**: Anything"
            }
          ]
        },
        "addAttachment": {
          "type": "object",
          "description": "The direct upload instruction for the storage backend (e.g. an S3-compatible object store).\nThe client must issue exactly this request, unmodified, to upload the actual file content.\n\n# Conditions\n\nThe storage backend, and therefore the exact set of `form_fields`, is implementation\ndefined and may change between requests and OpenProject versions. Clients *must not*\nassume a specific set of fields and instead pass through the ones given.",
          "required": [
            "href",
            "method",
            "form_fields"
          ],
          "properties": {
            "href": {
              "type": "string",
              "description": "The URL of the storage backend the file needs to be uploaded to."
            },
            "method": {
              "type": "string",
              "description": "The HTTP verb to use for the direct upload request (currently always `post`)."
            },
            "form_fields": {
              "type": "object",
              "description": "A set of additional fields that *must* be included, in this exact form and order, as\nparts of the `multipart/form-data` request sent to `href`, in addition to a `file`\npart carrying the raw file content (which *must* be the last part of the request).\n\nThese fields typically include storage credentials in the form of a short-lived,\npre-signed upload policy (e.g. `key`, `policy`, `X-Amz-Credential`, `X-Amz-Signature`,\n`X-Amz-Algorithm`, `X-Amz-Date`, `success_action_status`, `Content-Type` for an S3\nbackend) and *must* be treated as opaque values by the client.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "completeUpload": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "To be called by the client after the direct upload to `addAttachment` has finished\nsuccessfully, in order to complete the attachment.\n\n**Resource**: Attachment"
            }
          ]
        },
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes the pending attachment, e.g. to abort the upload.\n\n**Resource**: -"
            }
          ]
        },
        "staticDownloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment once it has been uploaded and stored on the\nOpenProject server.\n\n**Resource**: -"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Download link to the attachment. Points to the external storage location once the\nattachment resides in remote storage, otherwise identical to `staticDownloadLocation`.\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "AttachmentUpload",
    "id": 42,
    "fileName": "cat.png",
    "description": {
      "format": "plain",
      "raw": "",
      "html": ""
    },
    "createdAt": "2014-05-21T08:51:20.396Z",
    "_links": {
      "self": {
        "href": "/api/v3/attachment_upload/42",
        "title": "cat.png"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "addAttachment": {
        "href": "https://my-bucket.s3.amazonaws.com/",
        "method": "post",
        "form_fields": {
          "key": "uploads/42/cat.png",
          "acl": "private",
          "success_action_status": "201",
          "Content-Type": "image/png",
          "X-Amz-Credential": "AKIAIOSFODNN7EXAMPLE/20260101/eu-west-1/s3/aws4_request",
          "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
          "X-Amz-Date": "20260101T000000Z",
          "policy": "eyJleHBpcmF0aW9uIjogIjIwMjYtMDEtMDFUMDE6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbXX0=",
          "X-Amz-Signature": "abcdef0123456789"
        }
      },
      "completeUpload": {
        "href": "/api/v3/attachments/42/uploaded"
      },
      "delete": {
        "href": "/api/v3/attachments/42",
        "method": "delete"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/42/content"
      },
      "downloadLocation": {
        "href": "/api/v3/attachments/42/content"
      }
    }
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting the metadata part

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit messages

Note that you will only receive this error, if you are at least allowed to see the post.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to add attachments to this post."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the post does not exist or the client does not have sufficient permissions to see it.

Required permission: view messages

Note: A client without sufficient permissions shall not be able to test for the existence of a post. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to prepare an invalid attachment upload. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Omitting the file size (fileSize property of metadata part)

  • The instance is not configured for direct uploads

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Direct file upload is not available due to a system configuration issue. Please contact your administrator."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File can't be blank."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

List attachments by wiki page

id
integer

required path

ID of the wiki page whose attachments will be listed

Example:
1

200

OK

{
  "_embedded": {
    "elements": [
      {
        "_links": {
          "author": {
            "href": "/api/v3/users/1",
            "title": "OpenProject Admin"
          },
          "container": {
            "href": "/api/v3/wiki_pages/72",
            "title": "wiki"
          },
          "delete": {
            "href": "/api/v3/attachments/376",
            "method": "delete"
          },
          "downloadLocation": {
            "href": "/api/v3/attachments/376/content"
          },
          "self": {
            "href": "/api/v3/attachments/376",
            "title": "200.gif"
          }
        },
        "_type": "Attachment",
        "contentType": "image/gif",
        "createdAt": "2018-06-01T07:24:19.896Z",
        "description": {
          "format": "plain",
          "html": "",
          "raw": ""
        },
        "digest": {
          "algorithm": "md5",
          "hash": "7ac9c97ef73d47127f590788b84c0c1c"
        },
        "fileName": "some.gif",
        "fileSize": 3521772,
        "id": 376
      }
    ]
  },
  "_links": {
    "self": {
      "href": "/api/v3/wiki_pages/72/attachments"
    }
  },
  "_type": "Collection",
  "count": 1,
  "total": 1
}
Attachments_Model
{
  "allOf": [
    {
      "$ref": "#/components/schemas/CollectionModel"
    },
    {
      "type": "object",
      "required": [
        "_links",
        "_embedded"
      ],
      "properties": {
        "_links": {
          "type": "object",
          "required": [
            "self"
          ],
          "properties": {
            "self": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Link"
                },
                {
                  "description": "The attachments collection\n\n**Resource**: AttachmentsCollection",
                  "readOnly": true
                }
              ]
            }
          }
        },
        "_embedded": {
          "type": "object",
          "properties": {
            "elements": {
              "type": "array",
              "readOnly": true,
              "items": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AttachmentModel"
                  },
                  {
                    "description": "Collection of Attachments"
                  }
                ]
              }
            }
          }
        }
      }
    }
  ]
}

404

Returned if the wiki page does not exist or the client does not have sufficient permissions to see it.

Required permission: view wiki pages

Note: A client without sufficient permissions shall not be able to test for the existence of a work package. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Add attachment to wiki page

Adds an attachment with the wiki page as its container.

id
integer

required path

ID of the wiki page to receive the attachment

Example:
1

200

OK

{
  "_embedded": {
    "author": {
      "_links": {
        "lock": {
          "href": "/api/v3/users/1/lock",
          "method": "post",
          "title": "Set lock on admin"
        },
        "self": {
          "href": "/api/v3/users/1",
          "title": "OpenProject Admin"
        },
        "showUser": {
          "href": "/users/1",
          "type": "text/html"
        },
        "updateImmediately": {
          "href": "/api/v3/users/1",
          "method": "patch",
          "title": "Update admin"
        }
      },
      "_type": "User",
      "admin": true,
      "avatar": "",
      "createdAt": "2015-03-20T12:56:52.343Z",
      "email": null,
      "firstName": "OpenProject",
      "id": 1,
      "identityUrl": null,
      "lastName": "Admin",
      "login": "admin",
      "name": "OpenProject Admin",
      "status": "active",
      "updatedAt": "2018-05-29T13:57:44.662Z"
    },
    "container": {
      "_links": {
        "addAttachment": {
          "href": "/api/v3/wiki_pages/72/attachments",
          "method": "post"
        },
        "attachments": {
          "href": "/api/v3/wiki_pages/72/attachments"
        },
        "project": {
          "href": "/api/v3/projects/12",
          "title": "Demo project"
        },
        "self": {
          "href": "/api/v3/wiki_pages/72"
        }
      },
      "_type": "WikiPage",
      "id": 72,
      "title": "wiki"
    }
  },
  "_links": {
    "author": {
      "href": "/api/v3/users/1",
      "title": "OpenProject Admin"
    },
    "container": {
      "href": "/api/v3/wiki_pages/72",
      "title": "wiki"
    },
    "delete": {
      "href": "/api/v3/attachments/376",
      "method": "delete"
    },
    "downloadLocation": {
      "href": "/api/v3/attachments/376/content"
    },
    "self": {
      "href": "/api/v3/attachments/376",
      "title": "200.gif"
    }
  },
  "_type": "Attachment",
  "contentType": "image/gif",
  "createdAt": "2018-06-01T07:24:19.896Z",
  "description": {
    "format": "plain",
    "html": "",
    "raw": ""
  },
  "digest": {
    "algorithm": "md5",
    "hash": "7ac9c97ef73d47127f590788b84c0c1c"
  },
  "fileName": "some.gif",
  "fileSize": 3521772,
  "id": 376
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting one of the required parts (metadata and file)

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit wiki pages

Note that you will only receive this error, if you are at least allowed to see the wiki page

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to delete this attachment."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the wiki page does not exist or the client does not have sufficient permissions to see it.

Required permission: view wiki pages

Note: A client without sufficient permissions shall not be able to test for the existence of a wiki page That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to send an invalid attachment. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Sending a file that is too large

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File is too large (maximum size is 5242880 Bytes)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Prepare a direct attachment upload for a wiki page

Prepares a direct upload of an attachment for the given wiki page, to be performed by the client without the file content passing through the OpenProject server. See POST /api/v3/attachments/prepare for a detailed description of the three-step flow this endpoint is the first step of.

Unlike POST /api/v3/attachments/prepare, the resulting pending attachment is immediately associated with this wiki page as its container.

id
integer

required path

ID of the wiki page to receive the attachment

Example:
1

{
  "type": "object",
  "required": [
    "metadata"
  ],
  "properties": {
    "metadata": {
      "type": "string",
      "contentMediaType": "application/json",
      "contentSchema": {
        "$ref": "#/components/schemas/AttachmentPrepareUploadWriteModel"
      },
      "description": "A **single** part, named `metadata`, whose value is a *JSON-encoded string*\nconforming to the schema referenced above (see the *Model* tab for its\nproperties) - it must **not** be split into separate `fileName`, `fileSize`, ...\nform fields. No file content is sent with this request.",
      "example": "{\"fileName\":\"cat.png\",\"fileSize\":24,\"contentType\":\"image/png\"}"
    }
  }
}

201

OK

AttachmentUploadModel
{
  "type": "object",
  "required": [
    "_type",
    "_links",
    "id",
    "fileName"
  ],
  "properties": {
    "_type": {
      "type": "string",
      "enum": [
        "AttachmentUpload"
      ]
    },
    "id": {
      "type": "integer",
      "description": "The id of the pending `Attachment` record that was created for this upload. It can be used to\nreference the attachment (e.g. for deletion) before the upload has been completed.",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name the file will be stored under, as given in the request."
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file."
        }
      ]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time this pending attachment record was created."
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "author",
        "addAttachment",
        "completeUpload"
      ],
      "properties": {
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Not an actual retrievable resource. As the pending upload is a temporary, one-time\nobject, requesting this URL directly will return a `404 Not Found`. Use the `id`\nproperty together with the container's attachments endpoints once the upload has\nbeen completed."
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user preparing the upload, i.e. the currently authenticated user.\n\n**Resource**: User"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) the resulting attachment will belong to. Absent if the\nupload was prepared without a container (see `POST /api/v3/attachments/prepare`).\n\n**Resource**: Anything"
            }
          ]
        },
        "addAttachment": {
          "type": "object",
          "description": "The direct upload instruction for the storage backend (e.g. an S3-compatible object store).\nThe client must issue exactly this request, unmodified, to upload the actual file content.\n\n# Conditions\n\nThe storage backend, and therefore the exact set of `form_fields`, is implementation\ndefined and may change between requests and OpenProject versions. Clients *must not*\nassume a specific set of fields and instead pass through the ones given.",
          "required": [
            "href",
            "method",
            "form_fields"
          ],
          "properties": {
            "href": {
              "type": "string",
              "description": "The URL of the storage backend the file needs to be uploaded to."
            },
            "method": {
              "type": "string",
              "description": "The HTTP verb to use for the direct upload request (currently always `post`)."
            },
            "form_fields": {
              "type": "object",
              "description": "A set of additional fields that *must* be included, in this exact form and order, as\nparts of the `multipart/form-data` request sent to `href`, in addition to a `file`\npart carrying the raw file content (which *must* be the last part of the request).\n\nThese fields typically include storage credentials in the form of a short-lived,\npre-signed upload policy (e.g. `key`, `policy`, `X-Amz-Credential`, `X-Amz-Signature`,\n`X-Amz-Algorithm`, `X-Amz-Date`, `success_action_status`, `Content-Type` for an S3\nbackend) and *must* be treated as opaque values by the client.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "completeUpload": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "To be called by the client after the direct upload to `addAttachment` has finished\nsuccessfully, in order to complete the attachment.\n\n**Resource**: Attachment"
            }
          ]
        },
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes the pending attachment, e.g. to abort the upload.\n\n**Resource**: -"
            }
          ]
        },
        "staticDownloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment once it has been uploaded and stored on the\nOpenProject server.\n\n**Resource**: -"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Download link to the attachment. Points to the external storage location once the\nattachment resides in remote storage, otherwise identical to `staticDownloadLocation`.\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "AttachmentUpload",
    "id": 42,
    "fileName": "cat.png",
    "description": {
      "format": "plain",
      "raw": "",
      "html": ""
    },
    "createdAt": "2014-05-21T08:51:20.396Z",
    "_links": {
      "self": {
        "href": "/api/v3/attachment_upload/42",
        "title": "cat.png"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "addAttachment": {
        "href": "https://my-bucket.s3.amazonaws.com/",
        "method": "post",
        "form_fields": {
          "key": "uploads/42/cat.png",
          "acl": "private",
          "success_action_status": "201",
          "Content-Type": "image/png",
          "X-Amz-Credential": "AKIAIOSFODNN7EXAMPLE/20260101/eu-west-1/s3/aws4_request",
          "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
          "X-Amz-Date": "20260101T000000Z",
          "policy": "eyJleHBpcmF0aW9uIjogIjIwMjYtMDEtMDFUMDE6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbXX0=",
          "X-Amz-Signature": "abcdef0123456789"
        }
      },
      "completeUpload": {
        "href": "/api/v3/attachments/42/uploaded"
      },
      "delete": {
        "href": "/api/v3/attachments/42",
        "method": "delete"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/42/content"
      },
      "downloadLocation": {
        "href": "/api/v3/attachments/42/content"
      }
    }
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting the metadata part

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit wiki pages

Note that you will only receive this error, if you are at least allowed to see the wiki page

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to add attachments to this wiki page."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the wiki page does not exist or the client does not have sufficient permissions to see it.

Required permission: view wiki pages

Note: A client without sufficient permissions shall not be able to test for the existence of a wiki page That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The requested resource could not be found."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to prepare an invalid attachment upload. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Omitting the file size (fileSize property of metadata part)

  • The instance is not configured for direct uploads

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Direct file upload is not available due to a system configuration issue. Please contact your administrator."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File can't be blank."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

List attachments by work package

id
integer

required path

ID of the work package whose attachments will be listed

Example:
1

200

OK

Attachments_Model
{
  "allOf": [
    {
      "$ref": "#/components/schemas/CollectionModel"
    },
    {
      "type": "object",
      "required": [
        "_links",
        "_embedded"
      ],
      "properties": {
        "_links": {
          "type": "object",
          "required": [
            "self"
          ],
          "properties": {
            "self": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Link"
                },
                {
                  "description": "The attachments collection\n\n**Resource**: AttachmentsCollection",
                  "readOnly": true
                }
              ]
            }
          }
        },
        "_embedded": {
          "type": "object",
          "properties": {
            "elements": {
              "type": "array",
              "readOnly": true,
              "items": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AttachmentModel"
                  },
                  {
                    "description": "Collection of Attachments"
                  }
                ]
              }
            }
          }
        }
      }
    }
  ]
}

404

Returned if the work package does not exist or the client does not have sufficient permissions to see it.

Required permission: view work package

Note: A client without sufficient permissions shall not be able to test for the existence of a work package. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The specified work package does not exist."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Create work package attachment

To add an attachment to a work package, a client needs to issue a request of type multipart/form-data with exactly two parts.

The first part must be called metadata. Its content type is expected to be application/json, the body must be a single JSON object, containing at least the fileName and optionally the attachments description.

The second part must be called file, its content type should match the mime type of the file. The body must be the raw content of the file. Note that a filename must be indicated in the Content-Disposition of this part, however it will be ignored. Instead the fileName inside the JSON of the metadata part will be used.

id
integer

required path

ID of the work package to receive the attachment

Example:
1

200

OK

AttachmentModel
{
  "type": "object",
  "required": [
    "fileName",
    "description",
    "status",
    "contentType",
    "digest",
    "createdAt"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Attachment's id",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name of the uploaded file"
    },
    "fileSize": {
      "type": "integer",
      "description": "The size of the uploaded file in Bytes",
      "minimum": 0
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file"
        }
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "uploaded",
        "prepared",
        "scanned",
        "quarantined",
        "rescan"
      ]
    },
    "contentType": {
      "type": "string",
      "description": "The files MIME-Type as determined by the server"
    },
    "digest": {
      "type": "object",
      "description": "A checksum for the files content",
      "required": [
        "algorithm",
        "hash"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "description": "The algorithm used to generate the digest."
        },
        "hash": {
          "type": "string",
          "description": "The hexadecimal representation of the digested hash value."
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time of creation"
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "container",
        "author",
        "downloadLocation"
      ],
      "properties": {
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes this attachment\n\n# Conditions\n\n**Permission**: edit on attachment container or being the author for attachments without container"
            }
          ]
        },
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "This attachment\n\n**Resource**: Attachment"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) housing the attachment\n\n**Resource**: Anything"
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user who uploaded the attachment\n\n**Resource**: User"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "Attachment",
    "_links": {
      "self": {
        "href": "/api/v3/attachments/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/1/content"
      },
      "downloadLocation": {
        "href": "/some/remote/aws/url/image.png"
      }
    },
    "id": 1,
    "fileName": "cat.png",
    "filesize": 24,
    "status": "uploaded",
    "description": {
      "format": "plain",
      "raw": "A picture of a cute cat",
      "html": "<p>A picture of a cute cat</p>"
    },
    "contentType": "image/png",
    "digest": {
      "algorithm": "md5",
      "hash": "64c26a8403cd796ea4cf913cda2ee4a9"
    },
    "createdAt": "2014-05-21T08:51:20.396Z"
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting one of the required parts (metadata and file)

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit work package or add work package

Note that you will only receive this error, if you are at least allowed to see the work package.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to delete this attachment."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the work package does not exist or the client does not have sufficient permissions to see it.

Required permission: view work package

Note: A client without sufficient permissions shall not be able to test for the existence of a work package. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The specified work package does not exist."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to send an invalid attachment. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Sending a file that is too large

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File is too large (maximum size is 5242880 Bytes)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

Prepare a direct attachment upload for a work package

Prepares a direct upload of an attachment for the given work package, to be performed by the client without the file content passing through the OpenProject server. See POST /api/v3/attachments/prepare for a detailed description of the three-step flow this endpoint is the first step of.

Unlike POST /api/v3/attachments/prepare, the resulting pending attachment is immediately associated with this work package as its container.

id
integer

required path

ID of the work package to receive the attachment

Example:
1

{
  "type": "object",
  "required": [
    "metadata"
  ],
  "properties": {
    "metadata": {
      "type": "string",
      "contentMediaType": "application/json",
      "contentSchema": {
        "$ref": "#/components/schemas/AttachmentPrepareUploadWriteModel"
      },
      "description": "A **single** part, named `metadata`, whose value is a *JSON-encoded string*\nconforming to the schema referenced above (see the *Model* tab for its\nproperties) - it must **not** be split into separate `fileName`, `fileSize`, ...\nform fields. No file content is sent with this request.",
      "example": "{\"fileName\":\"cat.png\",\"fileSize\":24,\"contentType\":\"image/png\"}"
    }
  }
}

201

OK

AttachmentUploadModel
{
  "type": "object",
  "required": [
    "_type",
    "_links",
    "id",
    "fileName"
  ],
  "properties": {
    "_type": {
      "type": "string",
      "enum": [
        "AttachmentUpload"
      ]
    },
    "id": {
      "type": "integer",
      "description": "The id of the pending `Attachment` record that was created for this upload. It can be used to\nreference the attachment (e.g. for deletion) before the upload has been completed.",
      "minimum": 1
    },
    "fileName": {
      "type": "string",
      "description": "The name the file will be stored under, as given in the request."
    },
    "description": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Formattable"
        },
        {
          "description": "A user provided description of the file."
        }
      ]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Time this pending attachment record was created."
    },
    "_links": {
      "type": "object",
      "required": [
        "self",
        "author",
        "addAttachment",
        "completeUpload"
      ],
      "properties": {
        "self": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Not an actual retrievable resource. As the pending upload is a temporary, one-time\nobject, requesting this URL directly will return a `404 Not Found`. Use the `id`\nproperty together with the container's attachments endpoints once the upload has\nbeen completed."
            }
          ]
        },
        "author": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The user preparing the upload, i.e. the currently authenticated user.\n\n**Resource**: User"
            }
          ]
        },
        "container": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "The object (e.g. WorkPackage) the resulting attachment will belong to. Absent if the\nupload was prepared without a container (see `POST /api/v3/attachments/prepare`).\n\n**Resource**: Anything"
            }
          ]
        },
        "addAttachment": {
          "type": "object",
          "description": "The direct upload instruction for the storage backend (e.g. an S3-compatible object store).\nThe client must issue exactly this request, unmodified, to upload the actual file content.\n\n# Conditions\n\nThe storage backend, and therefore the exact set of `form_fields`, is implementation\ndefined and may change between requests and OpenProject versions. Clients *must not*\nassume a specific set of fields and instead pass through the ones given.",
          "required": [
            "href",
            "method",
            "form_fields"
          ],
          "properties": {
            "href": {
              "type": "string",
              "description": "The URL of the storage backend the file needs to be uploaded to."
            },
            "method": {
              "type": "string",
              "description": "The HTTP verb to use for the direct upload request (currently always `post`)."
            },
            "form_fields": {
              "type": "object",
              "description": "A set of additional fields that *must* be included, in this exact form and order, as\nparts of the `multipart/form-data` request sent to `href`, in addition to a `file`\npart carrying the raw file content (which *must* be the last part of the request).\n\nThese fields typically include storage credentials in the form of a short-lived,\npre-signed upload policy (e.g. `key`, `policy`, `X-Amz-Credential`, `X-Amz-Signature`,\n`X-Amz-Algorithm`, `X-Amz-Date`, `success_action_status`, `Content-Type` for an S3\nbackend) and *must* be treated as opaque values by the client.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "completeUpload": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "To be called by the client after the direct upload to `addAttachment` has finished\nsuccessfully, in order to complete the attachment.\n\n**Resource**: Attachment"
            }
          ]
        },
        "delete": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Deletes the pending attachment, e.g. to abort the upload.\n\n**Resource**: -"
            }
          ]
        },
        "staticDownloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Direct download link to the attachment once it has been uploaded and stored on the\nOpenProject server.\n\n**Resource**: -"
            }
          ]
        },
        "downloadLocation": {
          "allOf": [
            {
              "$ref": "#/components/schemas/Link"
            },
            {
              "description": "Download link to the attachment. Points to the external storage location once the\nattachment resides in remote storage, otherwise identical to `staticDownloadLocation`.\n\n**Resource**: -"
            }
          ]
        }
      }
    }
  },
  "example": {
    "_type": "AttachmentUpload",
    "id": 42,
    "fileName": "cat.png",
    "description": {
      "format": "plain",
      "raw": "",
      "html": ""
    },
    "createdAt": "2014-05-21T08:51:20.396Z",
    "_links": {
      "self": {
        "href": "/api/v3/attachment_upload/42",
        "title": "cat.png"
      },
      "author": {
        "href": "/api/v3/users/1"
      },
      "container": {
        "href": "/api/v3/work_packages/1"
      },
      "addAttachment": {
        "href": "https://my-bucket.s3.amazonaws.com/",
        "method": "post",
        "form_fields": {
          "key": "uploads/42/cat.png",
          "acl": "private",
          "success_action_status": "201",
          "Content-Type": "image/png",
          "X-Amz-Credential": "AKIAIOSFODNN7EXAMPLE/20260101/eu-west-1/s3/aws4_request",
          "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
          "X-Amz-Date": "20260101T000000Z",
          "policy": "eyJleHBpcmF0aW9uIjogIjIwMjYtMDEtMDFUMDE6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbXX0=",
          "X-Amz-Signature": "abcdef0123456789"
        }
      },
      "completeUpload": {
        "href": "/api/v3/attachments/42/uploaded"
      },
      "delete": {
        "href": "/api/v3/attachments/42",
        "method": "delete"
      },
      "staticDownloadLocation": {
        "href": "/api/v3/attachments/42/content"
      },
      "downloadLocation": {
        "href": "/api/v3/attachments/42/content"
      }
    }
  }
}

400

Returned if the client sends a not understandable request. Reasons include:

  • Omitting the metadata part

  • sending unparsable JSON in the metadata part

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request could not be parsed as JSON."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

403

Returned if the client does not have sufficient permissions.

Required permission: edit work package or add work package

Note that you will only receive this error, if you are at least allowed to see the work package.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to add attachments to this work package."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

404

Returned if the work package does not exist or the client does not have sufficient permissions to see it.

Required permission: view work package

Note: A client without sufficient permissions shall not be able to test for the existence of a work package. That’s why a 404 is returned here, even if a 403 might be more appropriate.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
  "message": "The specified work package does not exist."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

406

Occurs when the client did not send a Content-Type header

"Missing content-type header"
{
  "type": "string"
}

415

Occurs when the client sends an unsupported Content-Type header.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:TypeNotSupported",
  "message": "Expected CONTENT-TYPE to be (expected value) but got (actual value)."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}

422

Returned if the client tries to prepare an invalid attachment upload. Reasons are:

  • Omitting the file name (fileName property of metadata part)

  • Omitting the file size (fileSize property of metadata part)

  • The instance is not configured for direct uploads

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "Direct file upload is not available due to a system configuration issue. Please contact your administrator."
}
{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
  "message": "File can't be blank."
}
ErrorResponse
{
  "type": "object",
  "required": [
    "_type",
    "errorIdentifier",
    "message"
  ],
  "properties": {
    "_embedded": {
      "type": "object",
      "properties": {
        "details": {
          "type": "object",
          "properties": {
            "attribute": {
              "type": "string",
              "example": "project"
            }
          }
        }
      }
    },
    "_type": {
      "type": "string",
      "enum": [
        "Error"
      ]
    },
    "errorIdentifier": {
      "type": "string",
      "example": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
    },
    "message": {
      "type": "string",
      "example": "Project can't be blank."
    }
  }
}