Using Configuration Endpoints

🚧

Beta notice

Productboard REST API 2.0 is currently in Beta. Use it for experimentation, prototyping, and early integrations. Please note that individual endpoints may still change before the public release. We’re actively looking for Feedback & Help – let us know what works and what doesn’t. For critical production systems, continue using the Productboard REST API v1.0.

Why use the configuration endpoint?

The configuration endpoint provides a way to programmatically access and manage the configuration settings of your Productboard workspace. The API provides 2 configuration endpoints:

The configuration endpoint is a critical part of the Productboard API. Productboard provides a flexible data model and the API allows users to interact with this model in a programmatic way.

In a "traditional" API, you might have fixed responses for specific endpoints. For example, a GET /entities?type=feature endpoint might return a list of features with predefined fields like id, name, and status. This allows for a straightforward interaction with the API, but it can be limiting when the underlying data model is flexible and customizable.

In a "configuration-driven" API like Productboard's, the structure of the data returned by the API can change based on the configuration of the workspace. Taking the same example, to interact with the GET /entities?type=feature endpoint, we first have to call the configuration endpoint to understand what fields are available for features in the current workspace. The configuration endpoint will return a list of fields, their types, and other metadata. This allows clients to dynamically adapt to the structure of the data.

The basic structure of the configuration response

The configuration response typically includes the following elements:

  • type: The type of entity being configured
    • For example, for notes configuration, "type": "simple"
    • For example, for entities configuration, "type": "feature"
  • fields: A list of fields available for the entity, including their names, types, constraints, and a list of operations that can be performed on the field

Working with fields

Fields are the building blocks of the configuration endpoint. Each field has a set of properties that define its characteristics and behavior. Here are some common properties you might encounter:

  • id: A unique identifier for the field
  • name: The name of the field. This is the name users will see within the Productboard UI.
  • schema: The data type of the field
  • constraints: Any constraints that apply to the field, such as whether it is required or whether there are length limits
  • lifecycle: this determines the operations that can be performed on the field.

Example field definition

Assume we called the configuration endpoint for the feature entity type. In the example below, we can determine:

  • The id is name
  • The name is Name
    • This is the field name that will be displayed in the Productboard UI
  • The schema is TextFieldValue
    • This indicates that the field is a text field, and can be mapped to a string in your code
  • The field is required
    • This means that when creating or updating a feature, a value for this field must be provided
    • You can also reasonably assume that this field will always be present when retrieving a feature
  • The field has a maxLength constraint of 255 characters
    • This can be used for validation when working with this field
"fields": {
  "name": {
    "id": "name",
    "name": "Name",
    "path": "/fields/name",
    "schema": "TextFieldValue",
    "lifecycle": {
      "create": {
        "set": true
      },
      "update": {
        "set": true
      },
      "patch": {
        "set": true
      }
    },
    "constraints": {
      "required": true,
      "maxLength": 255
    },
    "links": {
      "self": null
    }
  }
}

Understanding field lifecycle

The lifecycle property of a field defines the operations that can be performed on that field. The lifecycle object can contain the following properties:

  • create - Operations allowed when creating a field
  • update - Operations allowed when updating a field using the simple data.fields approach
  • patch - Operations allowed when updating a field using JSON Patch arrays via data.patch

The difference between update and patch

Both update and patch are used for modifying existing entities, but they differ in how you structure the request:

Update uses the simpler data.fields approach where you send field updates directly in a fields object:

{
  "data": {
    "fields": {
      "name": "Hello World"
    }
  }
}

Patch uses a JSON Patch-like syntax (inspired by RFC 6902) where you send updates as an array of operations via data.patch:

{
  "data": {
    "patch": [
      { "op": "set", "path": "name", "value": "Hello World" }
    ]
  }
}

The lifecycle configuration tells you which approach is allowed for each field and what operations (set, clear, addItems, removeItems) are supported with each approach. Note that the op values in the patch array correspond directly to the lifecycle operations (set, clear, addItems, removeItems), not the standard JSON Patch operations.

Each of these properties (create, update, patch) can contain a list of operations that are allowed for that field. If the operation is allowed, it will be set to true. The operations are:

  • set - Allows setting the value of the field
  • clear - Allows clearing the value of the field
  • addItems - Allows adding items to an array field
  • removeItems - Allows removing items from an array field

Example lifecycle definition

Assume we called the configuration endpoint for the feature entity type. In the example below, we can determine that:

  • When we create, update, or patch a feature, we can set the value of the owner field.
  • When we update or patch a feature, we can also clear the value of the owner field.
"fields": {
  "owner": {
    "id": "owner",
    "name": "Owner",
    "path": "/fields/owner",
    "schema": "MemberFieldValue",
    "lifecycle": {
      "create": {
        "set": true
      },
      "update": {
        "set": true,
        "clear": true
      },
      "patch": {
        "set": true,
        "clear": true
      }
    },
    "links": {
      "self": null
    }
  }
}

Combining configuration data with other endpoints

A typical workflow when using the configuration endpoint involves the following steps:

  1. Call the configuration endpoint to retrieve the current configuration settings for the desired entity type (e.g., features, notes, etc.)
  2. Parse the configuration response to understand the available fields, their types, and the operations that can be performed on them
    1. It might be useful to build classes or data structures in your code to represent the configuration data. For example, you might create a Field class that encapsulates the properties of a field and provides methods for interacting with it.
  3. Make a request to an endpoint that you have a configuration for. For example, if you have the configuration for features, you might make a request to GET /entities?type=feature to retrieve a list of features.
    1. If you're retrieving data, you can validate the response against the data types defined in the configuration.
    2. If you're creating, deleting, or updating data, you can ensure that the data you're sending conforms to the constraints and operations defined in the configuration.

Example: Features export

In the Features export recipe, we demonstrate how to use the configuration endpoint to dynamically adapt to the structure of features in a Productboard workspace. The recipe involves:

  • In the function def fetch_configuration(entity_type), we call the configuration endpoint to retrieve the configuration for features.
  • In the function def generate_csv(configuration, entities, output_file), we use the configuration data to determine which fields to include in the CSV header row.

The advantage of this approach is that if the configuration of features changes (e.g., new fields are added, existing fields are removed), the export functionality will automatically adapt to these changes without requiring hardcoded field definitions.