API: Forms

This API provides forms as a concept to aid in editing or creating resources. The goal of forms is to:

  • make writable properties of a resource discoverable

  • show to which values a property can be set

  • validate changes to a resource and indicate validation errors

These benefits aside, a client can freely choose to immediately edit a resource without prior validation by a form. In the case of an invalid request the edit will fail and return appropriate errors nevertheless.

A form is associated to a single resource and aids in performing changes on that resource. When posting to a form endpoint with an empty request body or an empty JSON object, you will receive an initial form for the associated resource. Subsequent calls to the form should contain a single JSON object as described by the form.

Actions

Link Description Condition
validate Validate changes, show errors and allowed values for changed resource
commit Actually perform changes to the resource form content is valid
previewMarkup Post markup (e.g. markdown) here to receive an HTML-rendered response

Linked Properties

Link Description Type Nullable Supported operations
self This form Form READ

Embedded Properties:

Apart from the linked properties, forms contain always three other embedded properties:

  • payload

  • schema

  • validationErrors

Their purpose is explained below.

Payload

The payload contains an edited version of the resource that will be modified when committing the form. This representation contains all writable properties of the resource and reflects all changes that the latest call to validate included, thereby acting as a preview for the changes.

In case the client tries to set the value to something invalid, the invalid change is also reflected here. However a validation error (see below) indicates that a commit of this payload would fail.

It might happen that setting one property affects the allowed values for another property. Thus by changing a property A the current value of another property B might become invalid. If the client did not yet touch the value of B, the payload will contain a default value for that property. Nevertheless the client will also receive an appropriate validation error for value B.

The content of this element can be used as a template for the request body of a call to validate or commit.

A call to validate and commit does not need to include all properties that were defined in the payload section. It is only necessary to include the properties that you want to change, as well as the lockVersion if one is present. However you may include all the properties sent in the payload section.

Schema

The schema embedded in a form is a normal schema describing the underlying resource. However, the embedded schema can change with each revalidation of the form. For example it might be possible, that changing the type of a work package affects its available properties, as well as possible values for certain properties. As this makes the embedded schema very dynamic, it is not included as a static link.

Validation Errors

Like a schema the validation errors build a dictionary where the key is a property name. Each value is an error object that indicates the error that occurred validating the corresponding property. There are only key value pairs for properties that failed validation, the element is empty if all validations succeeded.

However note that even in the case of validation errors, the response you receive from the form endpoint will be an HTTP 200. That is because the main purpose of a form is helping the client to sort out validation errors.

Meta object

Form resources may have an additional _meta object that contains parameters to be sent together with the resource, but that do not belong to the resource itself. For example, parameters on if and how to send notifications for the action performed with the API request can be sent.

Each individual endpoint will describe their meta properties, if available.

Methods

Show or validate form

This is an example of how a form might look like. Note that this endpoint does not exist in the actual implementation.

No parameters
{
  "_type": "Example",
  "lockVersion": 5,
  "subject": "An example title"
}
{
  "_type": {
    "type": "string"
  },
  "lockVersion": {
    "type": "number"
  },
  "subject": {
    "type": "string"
  }
}

200

OK

{
  "_embedded": {
    "payload": {
      "_links": {
        "status": {
          "href": "/api/v3/statuses/1"
        }
      },
      "_type": "Example",
      "lockVersion": 5,
      "subject": "An example title"
    },
    "schema": {
      "_links": {
        "self": {
          "href": "/api/v3/example/schema"
        }
      },
      "_type": "Schema",
      "lockVersion": {
        "type": "Integer",
        "writable": false
      },
      "status": {
        "_embedded": {
          "allowedValues": [
            {
              "_links": {
                "self": {
                  "href": "/api/v3/statuses/1"
                }
              },
              "_type": "Status",
              "createdAt": "2014-05-21T08:51:20.396Z",
              "defaultDoneRatio": 0,
              "id": 1,
              "isClosed": false,
              "isDefault": true,
              "name": "New",
              "position": 1,
              "updatedAt": "2014-05-21T09:12:00.647Z"
            },
            {
              "_links": {
                "self": {
                  "href": "/api/v3/statuses/2"
                }
              },
              "_type": "Status",
              "createdAt": "2014-05-21T08:51:20.396Z",
              "defaultDoneRatio": 100,
              "id": 2,
              "isClosed": true,
              "isDefault": false,
              "name": "Closed",
              "position": 2,
              "updatedAt": "2014-05-21T09:12:00.647Z"
            }
          ]
        },
        "_links": {
          "allowedValues": [
            {
              "href": "/api/v3/statuses/1",
              "title": "New"
            },
            {
              "href": "/api/v3/statuses/2",
              "title": "Closed"
            }
          ]
        },
        "type": "Status"
      },
      "subject": {
        "maxLength": 255,
        "minLength": 1,
        "type": "String"
      }
    },
    "validationErrors": {
      "subject": {
        "_type": "Error",
        "errorIdentifier": "urn:openproject-org:api:v3:errors:BadExampleError",
        "message": "For the purpose of this example we need a validation error. The remainder of the response pretends there were no errors."
      }
    }
  },
  "_links": {
    "commit": {
      "href": "/api/v3/example",
      "method": "PATCH"
    },
    "previewMarkup": {
      "href": "/api/v3/render/markdown",
      "method": "POST"
    },
    "self": {
      "href": "/api/v3/example/form"
    },
    "validate": {
      "href": "/api/v3/example/form",
      "method": "POST"
    }
  },
  "_type": "Form"
}
Example_FormModel
{
  "type": "object",
  "example": {
    "_links": {
      "self": {
        "href": "/api/v3/example/form"
      },
      "validate": {
        "href": "/api/v3/example/form",
        "method": "POST"
      },
      "previewMarkup": {
        "href": "/api/v3/render/markdown",
        "method": "POST"
      },
      "commit": {
        "href": "/api/v3/example",
        "method": "PATCH"
      }
    },
    "_type": "Form",
    "_embedded": {
      "payload": {
        "_links": {
          "status": {
            "href": "/api/v3/statuses/1"
          }
        },
        "_type": "Example",
        "lockVersion": 5,
        "subject": "An example title"
      },
      "schema": {
        "_type": "Schema",
        "_links": {
          "self": {
            "href": "/api/v3/example/schema"
          }
        },
        "lockVersion": {
          "type": "Integer",
          "writable": false
        },
        "subject": {
          "type": "String",
          "minLength": 1,
          "maxLength": 255
        },
        "status": {
          "_links": {
            "allowedValues": [
              {
                "href": "/api/v3/statuses/1",
                "title": "New"
              },
              {
                "href": "/api/v3/statuses/2",
                "title": "Closed"
              }
            ]
          },
          "type": "Status",
          "_embedded": {
            "allowedValues": [
              {
                "_links": {
                  "self": {
                    "href": "/api/v3/statuses/1"
                  }
                },
                "_type": "Status",
                "id": 1,
                "name": "New",
                "position": 1,
                "isDefault": true,
                "isClosed": false,
                "defaultDoneRatio": 0,
                "createdAt": "2014-05-21T08:51:20.759Z",
                "updatedAt": "2014-05-21T09:12:00.237Z"
              },
              {
                "_links": {
                  "self": {
                    "href": "/api/v3/statuses/2"
                  }
                },
                "_type": "Status",
                "id": 2,
                "name": "Closed",
                "position": 2,
                "isDefault": false,
                "isClosed": true,
                "defaultDoneRatio": 100,
                "createdAt": "2014-05-21T08:51:20.759Z",
                "updatedAt": "2014-05-21T09:12:00.237Z"
              }
            ]
          }
        }
      },
      "validationErrors": {
        "subject": {
          "_type": "Error",
          "errorIdentifier": "urn:openproject-org:api:v3:errors:BadExampleError",
          "message": "For the purpose of this example we need a validation error. The remainder of the response pretends there were no errors."
        }
      }
    }
  }
}

400

Occurs when the client did not send a valid JSON object in the request body and the request body was not empty.

Note that this error only occurs when the content is not at all a single JSON object. It does not occur for requests containing undefined properties or invalid property values.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
  "message": "The request body was neither empty, nor did it contain a single JSON object."
}
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 to modify the associated resource.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
  "message": "You are not allowed to edit example resources."
}
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"
}

409

Returned if underlying resource was changed since the client requested the form. This is determined using the lockVersion property.

{
  "_type": "Error",
  "errorIdentifier": "urn:openproject-org:api:v3:errors:UpdateConflict",
  "message": "The resource you are about to edit was changed in the meantime."
}
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."
    }
  }
}

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."
    }
  }
}