From 90189e1fb190268faefcfe995a617a788b08922a Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Fri, 1 Jun 2018 10:26:05 +0200 Subject: [PATCH] document added resources --- docs/api/apiv3-documentation.apib | 2 + docs/api/apiv3/endpoints/attachments.apib | 667 +++++++++++++++++++- docs/api/apiv3/endpoints/posts.apib | 86 +++ docs/api/apiv3/endpoints/wiki_pages.apib | 86 +++ docs/api/apiv3/endpoints/work-packages.apib | 18 +- 5 files changed, 846 insertions(+), 13 deletions(-) create mode 100644 docs/api/apiv3/endpoints/posts.apib create mode 100644 docs/api/apiv3/endpoints/wiki_pages.apib diff --git a/docs/api/apiv3-documentation.apib b/docs/api/apiv3-documentation.apib index 4b2247d691..dd528589c7 100644 --- a/docs/api/apiv3-documentation.apib +++ b/docs/api/apiv3-documentation.apib @@ -15,6 +15,7 @@ FORMAT: 1A + @@ -32,4 +33,5 @@ FORMAT: 1A + diff --git a/docs/api/apiv3/endpoints/attachments.apib b/docs/api/apiv3/endpoints/attachments.apib index b0ff4c7d6f..5ddf60732a 100644 --- a/docs/api/apiv3/endpoints/attachments.apib +++ b/docs/api/apiv3/endpoints/attachments.apib @@ -5,9 +5,9 @@ container (e.g. a work package or a board message). ## Actions -| Link | Description | Condition | -|:-------------------:|----------------------------------------------------------------------| -------------------------------------------- | -| delete | Deletes this attachment | **Permission**: edit on attachment container | +| 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 | @@ -21,6 +21,7 @@ container (e.g. a work package or a board message). | 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 | @@ -28,6 +29,97 @@ container (e.g. a work package or a board message). | digest | A checksum for the files content | Digest | not null | READ | | createdAt | Time of creation | DateTime | not null | READ | +## Attachments [/api/v3/attachments] + +## Create Attachment [POST] + +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. + ++ Request (multipart/form-data) + + --boundary-delimiter + Content-Disposition: form-data; name="metadata" + Content-Type: application/json; charset=UTF-8 + + { + "fileName": "cute-cat.png", + "description": { + "raw": "A cute kitty, cuddling with its friends!" + } + } + + --boundary-delimiter + Content-Disposition: form-data; name="file"; filename="attachment" + Content-Type: image/png + + PNG file data + --boundary-delimiter-- + ++ Response 200 (application/hal+json) + + [Attachment][] + ++ Response 400 (application/hal+json) + + 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 + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody", + "message": "The request could not be parsed as JSON." + } + ++ Response 403 (application/hal+json) + + 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) + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission", + "message": "You are not allowed to delete this attachment." + } + ++ Response 422 (application/hal+json) + + 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 + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation", + "message": "File is too large (maximum size is 5242880 Bytes)." + } + + + ## Attachment [/api/v3/attachments/{id}] + Model @@ -79,7 +171,7 @@ container (e.g. a work package or a board message). 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 + **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.* @@ -112,7 +204,7 @@ Permanently deletes the specified attachment. Returned if the client does not have sufficient permissions. - **Required permission:** edit permission for the container of the attachment + **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.* @@ -129,7 +221,7 @@ Permanently deletes the specified attachment. 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 + **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.* @@ -142,6 +234,569 @@ Permanently deletes the specified attachment. "message": "The specified attachment does not exist." } + +# Attachments by post [/api/v3/posts/{id}/attachments] + ++ Model + + Body + + { + "_type": "Collection", + "total": 1, + "count": 1, + "_embedded": { + "elements": [ + { + "_type": "Attachment", + "id": 376, + "fileName": "some.gif", + "fileSize": 3521772, + "description": { + "format": "plain", + "raw": "", + "html": "" + }, + "contentType": "image/gif", + "digest": { + "algorithm": "md5", + "hash": "7ac9c97ef73d47127f590788b84c0c1c" + }, + "createdAt": "2018-06-01T07:24:19Z", + "_links": { + "self": { + "href": "/api/v3/attachments/376", + "title": "200.gif" + }, + "author": { + "href": "/api/v3/users/1", + "title": "OpenProject Admin" + }, + "container": { + "href": "/api/v3/posts/72", + "title": "wiki" + }, + "downloadLocation": { + "href": "/api/v3/attachments/376/content" + }, + "delete": { + "href": "/api/v3/attachments/376", + "method": "delete" + } + } + } + ] + }, + "_links": { + "self": { + "href": "/api/v3/posts/72/attachments" + } + } + } + +## List attachments [GET] + ++ Parameters + + id (required, integer, `1`) ... ID of the post whose attachments will be listed + ++ Response 200 (application/hal+json) + + [Attachments by post][] + ++ Response 404 (application/hal+json) + + 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.* + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", + "message": "The requested resource could not be found." + } + +## Add attachment [POST] + +Adds an attachment with the post as it's container. + +See [the general specification for uploading attachments](#attachments-attachments) for details. + ++ Parameters + + id (required, integer, `1`) ... ID of the post to receive the attachment + ++ Request (multipart/form-data) + + --boundary-delimiter + Content-Disposition: form-data; name="metadata" + Content-Type: application/json; charset=UTF-8 + + { + "fileName": "cute-cat.png", + "description": { + "raw": "A cute kitty, cuddling with its friends!" + } + } + + --boundary-delimiter + Content-Disposition: form-data; name="file"; filename="attachment" + Content-Type: image/png + + PNG file data + --boundary-delimiter-- + ++ Response 200 (application/hal+json) + + + Body + + { + "_embedded": { + "author": { + "_type": "User", + "id": 1, + "name": "OpenProject Admin", + "createdAt": "2015-03-20T12:56:52Z", + "updatedAt": "2018-05-29T13:57:44Z", + "login": "admin", + "admin": true, + "firstName": "OpenProject", + "lastName": "Admin", + "email": null, + "avatar": "", + "status": "active", + "identityUrl": null, + "_links": { + "self": { + "href": "/api/v3/users/1", + "title": "OpenProject Admin" + }, + "showUser": { + "href": "/users/1", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/1", + "title": "Update admin", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/1/lock", + "title": "Set lock on admin", + "method": "post" + } + } + }, + "container": { + "_type": "Post", + "id": 150, + "subject": "sfsdfsdfsdfsdf", + "_links": { + "self": { + "href": "/api/v3/posts/150" + }, + "attachments": { + "href": "/api/v3/posts/150/attachments" + }, + "addAttachment": { + "href": "/api/v3/posts/150/attachments", + "method": "post" + }, + "project": { + "href": "/api/v3/projects/12", + "title": "Demo project" + } + } + } + }, + "_type": "Attachment", + "id": 377, + "fileName": "some.gif", + "fileSize": 3521772, + "description": { + "format": "plain", + "raw": "", + "html": "" + }, + "contentType": "image/gif", + "digest": { + "algorithm": "md5", + "hash": "7ac9c97ef73d47127f590788b84c0c1c" + }, + "createdAt": "2018-06-01T07:53:36Z", + "_links": { + "self": { + "href": "/api/v3/attachments/377", + "title": "200.gif" + }, + "author": { + "href": "/api/v3/users/1", + "title": "OpenProject Admin" + }, + "container": { + "href": "/api/v3/posts/150", + "title": "sfsdfsdfsdfsdf" + }, + "downloadLocation": { + "href": "/api/v3/attachments/377/content" + }, + "delete": { + "href": "/api/v3/attachments/377", + "method": "delete" + } + } + } + ++ Response 400 (application/hal+json) + + 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 + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody", + "message": "The request could not be parsed as JSON." + } + ++ Response 403 (application/hal+json) + + 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* + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission", + "message": "You are not allowed to delete this attachment." + } + ++ Response 404 (application/hal+json) + + 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.* + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", + "message": "The requested resource could not be found." + } + ++ Response 422 (application/hal+json) + + 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 + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation", + "message": "File is too large (maximum size is 5242880 Bytes)." + } + + +# Attachments by wiki page [/api/v3/wiki_pages/{id}/attachments] + ++ Model + + Body + + { + "_type": "Collection", + "total": 1, + "count": 1, + "_embedded": { + "elements": [ + { + "_type": "Attachment", + "id": 376, + "fileName": "some.gif", + "fileSize": 3521772, + "description": { + "format": "plain", + "raw": "", + "html": "" + }, + "contentType": "image/gif", + "digest": { + "algorithm": "md5", + "hash": "7ac9c97ef73d47127f590788b84c0c1c" + }, + "createdAt": "2018-06-01T07:24:19Z", + "_links": { + "self": { + "href": "/api/v3/attachments/376", + "title": "200.gif" + }, + "author": { + "href": "/api/v3/users/1", + "title": "OpenProject Admin" + }, + "container": { + "href": "/api/v3/wiki_pages/72", + "title": "wiki" + }, + "downloadLocation": { + "href": "/api/v3/attachments/376/content" + }, + "delete": { + "href": "/api/v3/attachments/376", + "method": "delete" + } + } + } + ] + }, + "_links": { + "self": { + "href": "/api/v3/wiki_pages/72/attachments" + } + } + } + +## List attachments [GET] + ++ Parameters + + id (required, integer, `1`) ... ID of the wiki page whose attachments will be listed + ++ Response 200 (application/hal+json) + + [Attachments by wiki page][] + ++ Response 404 (application/hal+json) + + 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.* + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", + "message": "The requested resource could not be found." + } + +## Add attachment [POST] + +Adds an attachment with the wiki page as it's container. + +See [the general specification for uploading attachments](#attachments-attachments) for details. + ++ Parameters + + id (required, integer, `1`) ... ID of the wiki page to receive the attachment + ++ Request (multipart/form-data) + + --boundary-delimiter + Content-Disposition: form-data; name="metadata" + Content-Type: application/json; charset=UTF-8 + + { + "fileName": "cute-cat.png", + "description": { + "raw": "A cute kitty, cuddling with its friends!" + } + } + + --boundary-delimiter + Content-Disposition: form-data; name="file"; filename="attachment" + Content-Type: image/png + + PNG file data + --boundary-delimiter-- + ++ Response 200 (application/hal+json) + + + Body + + { + "_embedded": { + "author": { + "_type": "User", + "id": 1, + "name": "OpenProject Admin", + "createdAt": "2015-03-20T12:56:52Z", + "updatedAt": "2018-05-29T13:57:44Z", + "login": "admin", + "admin": true, + "firstName": "OpenProject", + "lastName": "Admin", + "email": null, + "avatar": "", + "status": "active", + "identityUrl": null, + "_links": { + "self": { + "href": "/api/v3/users/1", + "title": "OpenProject Admin" + }, + "showUser": { + "href": "/users/1", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/1", + "title": "Update admin", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/1/lock", + "title": "Set lock on admin", + "method": "post" + } + } + }, + "container": { + "_type": "WikiPage", + "id": 72, + "title": "wiki", + "_links": { + "self": { + "href": "/api/v3/wiki_pages/72" + }, + "attachments": { + "href": "/api/v3/wiki_pages/72/attachments" + }, + "addAttachment": { + "href": "/api/v3/wiki_pages/72/attachments", + "method": "post" + }, + "project": { + "href": "/api/v3/projects/12", + "title": "Demo project" + } + } + } + }, + "_type": "Attachment", + "id": 376, + "fileName": "some.gif", + "fileSize": 3521772, + "description": { + "format": "plain", + "raw": "", + "html": "" + }, + "contentType": "image/gif", + "digest": { + "algorithm": "md5", + "hash": "7ac9c97ef73d47127f590788b84c0c1c" + }, + "createdAt": "2018-06-01T07:24:19Z", + "_links": { + "self": { + "href": "/api/v3/attachments/376", + "title": "200.gif" + }, + "author": { + "href": "/api/v3/users/1", + "title": "OpenProject Admin" + }, + "container": { + "href": "/api/v3/wiki_pages/72", + "title": "wiki" + }, + "downloadLocation": { + "href": "/api/v3/attachments/376/content" + }, + "delete": { + "href": "/api/v3/attachments/376", + "method": "delete" + } + } + } + + ++ Response 400 (application/hal+json) + + 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 + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody", + "message": "The request could not be parsed as JSON." + } + ++ Response 403 (application/hal+json) + + 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* + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission", + "message": "You are not allowed to delete this attachment." + } + ++ Response 404 (application/hal+json) + + 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.* + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", + "message": "The requested resource could not be found." + } + ++ Response 422 (application/hal+json) + + 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 + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation", + "message": "File is too large (maximum size is 5242880 Bytes)." + } + # Attachments by work package [/api/v3/work_packages/{id}/attachments] + Model diff --git a/docs/api/apiv3/endpoints/posts.apib b/docs/api/apiv3/endpoints/posts.apib new file mode 100644 index 0000000000..ce7d4ce0e5 --- /dev/null +++ b/docs/api/apiv3/endpoints/posts.apib @@ -0,0 +1,86 @@ +# Group Posts + +Represents a post in a board. Posts are also referred to as messages in the application. + +*This resource is currently a stub* + +## Actions + +| Link | Description | Condition | +|:-------------------:|--------------------------------| --------------------------------------- | +| addAttachment | Attach a file to the post | **Permission**: edit messages | + +## Linked Properties + +| Property | Description | Type | Constraints | Supported operations | +| :--------------: | ------------------------------------------------------ | ----------- | -------------- | -------------------- | +| self | This post | Post | not null | READ | +| attachments | The files attached to this post | Collection | | READ | +| project | The project the post belongs to | Project | not null | READ | + +## Local Properties + +| Property | Description | Type | Constraints | Supported operations | +| :--------------: | ------------------------------------------- | ----------- | ------------------------------------ | -------------------- | +| id | Identifier of this post | Integer | x > 0 | READ | +| subject | The posts's subject | String | not null | READ | + +## Post [/api/v3/posts/{id}] + ++ Model + + Body + + { + "_type": "Post", + "id": 1, + "subject": "A post with a subject", + "_embedded": { + "project": { + "_type": "Project", + "id": 1, + <-- abbreviated --> + } + } + }, + "_links": { + "self": { + "href": "/api/v3/posts/1" + }, + "attachments": { + "href": "/api/v3/posts/1/attachments" + }, + "addAttachment": { + "href": "/api/v3/posts/1/attachments", + "method": "post" + }, + "project": { + "href": "/api/v3/projects/1", + "title": "A project with a title" + } + } + } + +## View Post [GET] + +Retrieve an individual post as identified by the id parameter + ++ Parameters + + id (required, integer, `1`) ... Post's identifier + ++ Response 200 (application/hal+json) + + [Post][] + ++ Response 404 (application/hal+json) + + Returned if the post does not exist or the client does not have sufficient permissions to see it. + + **Required permission:** view messages in the post's project + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", + "message": "The requested resource could not be found." + } diff --git a/docs/api/apiv3/endpoints/wiki_pages.apib b/docs/api/apiv3/endpoints/wiki_pages.apib new file mode 100644 index 0000000000..bdb6299697 --- /dev/null +++ b/docs/api/apiv3/endpoints/wiki_pages.apib @@ -0,0 +1,86 @@ +# Group Wiki Pages + +Represents an individual page in a project's wiki. + +*This resource is currently a stub* + +## Actions + +| Link | Description | Condition | +|:-------------------:|----------------------------------------------------------------------| --------------------------------------- | +| addAttachment | Attach a file to the wiki page | **Permission**: edit wiki page | + +## Linked Properties + +| Property | Description | Type | Constraints | Supported operations | +| :--------------: | ------------------------------------------------------ | ----------- | -------------- | -------------------- | +| self | This wiki page | WikiPage | not null | READ | +| attachments | The files attached to this wiki page | Collection | | READ | +| project | The project the wiki page belongs to | Project | not null | READ | + +## Local Properties + +| Property | Description | Type | Constraints | Supported operations | +| :--------------: | ------------------------------------------- | ----------- | ------------------------------------ | -------------------- | +| id | Identifier of this wiki page | Integer | x > 0 | READ | +| title | The wiki page's title | String | not null | READ | + +## Wiki Page [/api/v3/wiki_pages/{id}] + ++ Model + + Body + + { + "_type": "WikiPage", + "id": 72, + "title": "A wiki page with a name", + "_embedded": { + "project": { + "_type": "Project", + "id": 12, + <-- abbreviated --> + } + } + }, + "_links": { + "self": { + "href": "/api/v3/wiki_pages/72" + }, + "attachments": { + "href": "/api/v3/wiki_pages/72/attachments" + }, + "addAttachment": { + "href": "/api/v3/wiki_pages/72/attachments", + "method": "post" + }, + "project": { + "href": "/api/v3/projects/12", + "title": "some project" + } + } + } + +## View Wiki Page [GET] + +Retrieve an individual wiki page as identified by the id parameter + ++ Parameters + + id (required, integer, `1`) ... Wiki page identifier + ++ Response 200 (application/hal+json) + + [Wiki Page][] + ++ Response 404 (application/hal+json) + + Returned if the wiki page does not exist or the client does not have sufficient permissions to see it. + + **Required permission:** view wiki page in the page's project + + + Body + + { + "_type": "Error", + "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", + "message": "The requested resource could not be found." + } diff --git a/docs/api/apiv3/endpoints/work-packages.apib b/docs/api/apiv3/endpoints/work-packages.apib index 598b5f6abd..0ddc15ab80 100644 --- a/docs/api/apiv3/endpoints/work-packages.apib +++ b/docs/api/apiv3/endpoints/work-packages.apib @@ -23,7 +23,7 @@ | self | This work package | WorkPackage | not null | READ | | | schema | The schema of this work package | Schema | not null | READ | | | ancestors | Array of all visible ancestors of the work package, with the root node being the first element | Collection | not null | READ | **Permission** view work packages | -| attachments | The files attached to this work package | Collection | not null | READ | | +| attachments | The files attached to this work package | Collection | not null | READ / WRITE | | | author | The person that created the work package | User | not null | READ | | | assignee | The person that is intended to work on the work package | User | | READ / WRITE | | | availableWatchers | All users that can be added to the work package as watchers. | User | | READ | **Permission** add work package watchers | @@ -59,23 +59,27 @@ | createdAt | Time of creation | DateTime | | READ | | | updatedAt | Time of the most recent change to the work package | DateTime | | READ | | -*Note that the properties listed here only cover the built-in properties of the OpenProject Core. +Note that the properties listed here only cover the built-in properties of the OpenProject Core. Using plug-ins and custom fields a work package might contain various additional properties. A client can consult the schema information to which the work package links. The schema will contain information about -all properties of the linking work package, including properties added by plug-ins and custom fields.* +all properties of the linking work package, including properties added by plug-ins and custom fields. -*Custom fields are identified by a key in the form of `customFieldN`, where `N` is an integer. Depending on their type, +Custom fields are identified by a key in the form of `customFieldN`, where `N` is an integer. Depending on their type, they can occur as properties or as linked properties. A client has to consult the schema to resolve -the human readable name of custom fields.* +the human readable name of custom fields. -*Properties that cannot be set directly on parent work packages are inferred from their children instead:* +Properties that cannot be set directly on parent work packages are inferred from their children instead: * `startDate` is the earliest start date from its children * `dueDate` is the latest due date from its children * `estimatedTime` is the sum of estimated times from its children * `percentageDone` is the weighted average of the sum of its children percentages done. The weight is given by the average of its children estimatedHours. However, if the percentage done is given by a work package's status, then only the status matters and no value is inferred. -*Start date can also not be earlier than a due date of any predecessor.* +Start date can also not be earlier than a due date of any predecessor. + +While attachments are returned as a link which's content is to be fetched separately, clients can choose to +replace the work package's attachments by providing an array of already uploaded [Attachment resources](#attachments) on [create](#work-packages-work-packages-post) +and [update](#work-packages-work-package-patch). The attachments the work package has had prior to the request will be removed. ## Work Package [/api/v3/work_packages/{id}{?notify}]