{"openapi":"3.1.1","info":{"title":"Plugin Integrations","description":"Manages plugin integrations and their connections.\n\nA **plugin integration** defines how Productboard communicates with your system when\na user interacts with a push button on an entity. When the button is clicked, Productboard\nsends an **action notification** (POST) to your configured endpoint. Your endpoint responds\nwith a connection state, which controls how the button is rendered.\n\nA **plugin integration connection** represents the relationship between a Productboard\nentity and an entity in the third-party system for a given integration.\n\n**Probe verification**: on integration creation (and on update when enabled), Productboard\nsends a GET probe to your action URL to verify it is reachable. See the `callbacks` section.\n\n**`action` is write-only**: action configuration (including any `headers.authorization`\nsecret) is never returned in API responses.\n","version":"2.0.0","contact":{"name":"Productboard Support","url":"https://support.productboard.com","email":"support@productboard.com"},"license":{"name":"Proprietary","url":"https://www.productboard.com/terms"}},"servers":[{"url":"https://api.productboard.com/v2","description":"Production server"}],"security":[{"BearerAuth":[]},{"OAuth2":["entities:read","write:entities","entities:delete"]}],"tags":[{"name":"plugin-integrations","description":"Operations for managing plugin integrations."},{"name":"plugin-integration-connections","description":"Operations for managing plugin integration connections."}],"paths":{"/plugin-integrations":{"post":{"summary":"Create plugin integration","description":"Creates a new plugin integration. As part of creation, Productboard sends a\n**probe request** (GET) to your configured action URL to verify it is reachable\nand intends to receive action notifications. See the `callbacks` section below.\n\nThe integration is created in the `enabled` state by default. Set\n`fields.integrationStatus` to `disabled` to skip the probe (useful for staged setup).\n\n**`action` is write-only**: the action configuration, including any\n`headers.authorization` secret, is never returned in responses.\n","operationId":"createPluginIntegration","security":[{"BearerAuth":["plugin-integrations:write"]}],"tags":["plugin-integrations"],"requestBody":{"$ref":"#/components/requestBodies/PluginIntegrationCreateRequestBody"},"responses":{"201":{"$ref":"#/components/responses/PluginIntegrationCreateResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}},"callbacks":{"actionCallback":{"{$request.body#/data/fields/action/url}":{"get":{"summary":"Action endpoint probe","security":[{"CallbackAuthorization":[]},{}],"description":"Sent during integration creation (and on PATCH when `enabled` and `action.url`\nchanges) to verify your endpoint is reachable and intends to receive action\nnotifications.\n\n- Response must be `200 OK` with `Content-Type: text/plain`\n- Response body must echo the `validationToken` query parameter **exactly**\n- Timeout: **5 seconds**\n- TLS certificate must be valid and signed by a public authority\n- Failure immediately fails the create/update request with `callback.probeFailed`\n\nThis request is sent with `Accept: text/plain`.\n\n**Security**: set `data.fields.action.headers.authorization` to have\nProductboard include it in every outgoing request to your endpoint.\n","operationId":"pluginIntegrationProbe","parameters":[{"in":"query","name":"validationToken","required":true,"description":"Token to echo verbatim in the response body.","schema":{"type":"string","example":"a-unique-token-for-this-request"}}],"responses":{"200":{"description":"Echo the `validationToken` value as plain text.","content":{"text/plain":{"schema":{"type":"string","example":"a-unique-token-for-this-request"}}}},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}},"post":{"summary":"Action notification","security":[{"CallbackAuthorization":[]},{}],"description":"Sent when a user clicks a plugin integration button in the Productboard UI.\n\n**Trigger types** (from `data.trigger`):\n- `button.push` — user clicked to connect / push the entity\n- `button.unlink` — user clicked to unlink the connected entity\n- `button.dismiss` — user dismissed an error popup\n\nRespond with a `ConnectionState` to indicate the outcome. This is equivalent\nto calling the Configure Connection endpoint with the same state.\n\n**Recommended behavior:**\n- `button.push` → respond with `connected`, or `progress` for async processing\n- `button.unlink` → respond with `initial` to reset the connection\n- `button.dismiss` → respond with `initial` to clear the error\n\n**Timeout: 5 seconds.** For async processing, respond with `progress`\nimmediately and update the connection later via the Configure Connection\nendpoint (`data.links.connection` in the payload).\n\n**Security**: set `data.fields.action.headers.authorization` to authenticate\nincoming requests from Productboard.\n","operationId":"pluginIntegrationActionNotification","requestBody":{"description":"Action notification payload.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ActionPayload"}}}}}},"responses":{"200":{"description":"Respond with the resulting connection state. Any valid `ConnectionState`\nis accepted. Setting `state: initial` resets the connection (equivalent\nto deleting it).\n","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["fields"],"properties":{"fields":{"type":"object","required":["connection"],"properties":{"connection":{"$ref":"#/components/schemas/ConnectionState"}}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}}}}}},"get":{"summary":"List plugin integrations","description":"Returns all plugin integrations for the workspace.\n\nPaginated using cursor-based pagination. Follow `links.next` to fetch the next page.\nWhen `links.next` is `null`, you have reached the last page.\n\n**OAuth2 isolation**: OAuth2 access tokens only see integrations created by the same\nOAuth2 application. Public API access tokens see all workspace integrations.\n","operationId":"listPluginIntegrations","security":[{"BearerAuth":["plugin-integrations:read"]}],"tags":["plugin-integrations"],"parameters":[{"$ref":"#/components/parameters/PageCursor"}],"responses":{"200":{"$ref":"#/components/responses/PluginIntegrationsListResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}}},"/plugin-integrations/{integrationId}":{"get":{"summary":"Get plugin integration","description":"Returns a single plugin integration by ID.\n\n**OAuth2 isolation**: returns `404` if the integration was created by a different\nOAuth2 application or by a public API access token (existence is masked for security).\n","operationId":"getPluginIntegration","security":[{"BearerAuth":["plugin-integrations:read"]}],"tags":["plugin-integrations"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"}],"responses":{"200":{"$ref":"#/components/responses/PluginIntegrationResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}},"patch":{"summary":"Update plugin integration","description":"Partially updates a plugin integration. Fields not included in the request preserve\ntheir current value.\n\nIf the integration is `enabled` and `fields.action.url` is updated, Productboard\nsends a new probe to verify the updated endpoint. No probe is sent when `disabled`.\n\n**`action` is write-only**: never returned in responses.\n","operationId":"updatePluginIntegration","security":[{"BearerAuth":["plugin-integrations:write"]}],"tags":["plugin-integrations"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"}],"requestBody":{"$ref":"#/components/requestBodies/PluginIntegrationPatchRequestBody"},"responses":{"200":{"$ref":"#/components/responses/PluginIntegrationPatchResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}},"delete":{"summary":"Delete plugin integration","description":"Permanently deletes a plugin integration and all its associated connections.\nEntities that previously had connections to this integration will have their\npush buttons removed. This action cannot be undone.\n\n**OAuth2 isolation**: returns `404` if the integration was created by a different\nOAuth2 application or by a public API access token.\n","operationId":"deletePluginIntegration","security":[{"BearerAuth":["plugin-integrations:delete"]}],"tags":["plugin-integrations"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"}],"responses":{"204":{"description":"Plugin integration deleted successfully. No content."},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}}},"/plugin-integrations/{integrationId}/connections":{"get":{"summary":"List plugin integration connections","description":"Returns all connections for a plugin integration, **excluding** those in the\n`initial` state. The `initial` state is the default — it means no connection has\nbeen established for that entity yet.\n\nPaginated using cursor-based pagination. Follow `links.next` to fetch the next page.\nWhen `links.next` is `null`, you have reached the last page.\n","operationId":"listPluginIntegrationConnections","security":[{"BearerAuth":["plugin-integrations:read"]}],"tags":["plugin-integration-connections"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"},{"$ref":"#/components/parameters/PageCursor"},{"$ref":"#/components/parameters/ConnectionStateFilter"}],"responses":{"200":{"$ref":"#/components/responses/PluginIntegrationConnectionsListResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}}},"/plugin-integrations/{integrationId}/connections/search":{"post":{"summary":"Search plugin integration connections","description":"Performs a filtered search over plugin integration connections using a POST request.\nThis endpoint supports the same filtering capabilities as `GET /v2/plugin-integrations/{integrationId}/connections`,\nbut accepts filters in the request body instead of query parameters.\nIt's useful when filtering by a long list of entity IDs or when URL length limits become a problem.\n","operationId":"searchPluginIntegrationConnections","security":[{"BearerAuth":["plugin-integrations:read"]}],"tags":["plugin-integration-connections"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"},{"$ref":"#/components/parameters/PageCursor"}],"requestBody":{"$ref":"#/components/requestBodies/PluginIntegrationConnectionsSearchRequestBody"},"responses":{"200":{"$ref":"#/components/responses/PluginIntegrationConnectionsListResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}}},"/plugin-integrations/{integrationId}/connections/{entityId}":{"get":{"summary":"Get plugin integration connection","description":"Returns the connection for a specific entity within a plugin integration.\n\n**Important**: if no connection exists for the entity, this endpoint returns\na connection with `state: initial` rather than a `404`. The `initial` state\nindicates the entity has not yet been pushed to the third-party system.\n","operationId":"getPluginIntegrationConnection","security":[{"BearerAuth":["plugin-integrations:read"]}],"tags":["plugin-integration-connections"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"},{"$ref":"#/components/parameters/EntityId"}],"responses":{"200":{"$ref":"#/components/responses/GetPluginIntegrationConnectionResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}},"put":{"summary":"Update plugin integration connection state","description":"Creates or replaces the connection state for a specific entity within a plugin\nintegration. This is an **upsert** — if no connection exists it is created; if one\nexists it is replaced.\n\n**Connection states:**\n\n| State | Effect |\n|---|---|\n| `connected` | Entity is linked; push button shows connection details |\n| `error` | Connection failed; push button shows error state |\n| `progress` | Processing asynchronously; push button shows loading indicator |\n| `initial` | Equivalent to deleting the connection; push button resets |\n\n**Async processing flow:**\n1. User clicks push button → your endpoint receives an action notification\n2. Respond immediately with `state: progress` to show a loading indicator\n3. Process the action asynchronously\n4. Call this endpoint with the final state (`connected` or `error`)\n","operationId":"updatePluginIntegrationConnectionState","security":[{"BearerAuth":["plugin-integrations:write"]}],"tags":["plugin-integration-connections"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"},{"$ref":"#/components/parameters/EntityId"}],"requestBody":{"$ref":"#/components/requestBodies/PluginIntegrationConnectionUpdateStateRequestBody"},"responses":{"200":{"$ref":"#/components/responses/PluginIntegrationConnectionUpdateStateResponse"},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}},"delete":{"summary":"Delete plugin integration connection","description":"Permanently deletes the connection between an entity and the third-party system,\nresetting the push button to its initial unconnected state.\n\nThis is equivalent to calling the Configure Connection endpoint with `state: initial`.\n","operationId":"deletePluginIntegrationConnection","security":[{"BearerAuth":["plugin-integrations:write"]}],"tags":["plugin-integration-connections"],"parameters":[{"$ref":"#/components/parameters/IntegrationId"},{"$ref":"#/components/parameters/EntityId"}],"responses":{"204":{"description":"Connection deleted successfully. No content."},"400":{"$ref":"#/components/responses/BadRequestResponse"},"401":{"$ref":"#/components/responses/UnauthorizedResponse"},"403":{"$ref":"#/components/responses/ForbiddenResponse"},"404":{"$ref":"#/components/responses/NotFoundResponse"},"408":{"$ref":"#/components/responses/RequestTimeoutResponse"},"422":{"$ref":"#/components/responses/UnprocessableEntityResponse"},"429":{"$ref":"#/components/responses/TooManyRequestsResponse"},"500":{"$ref":"#/components/responses/InternalServerErrorResponse"}}}}},"components":{"parameters":{"IntegrationId":{"name":"integrationId","in":"path","required":true,"description":"UUID of the plugin integration.","schema":{"type":"string","format":"uuid","example":"00000000-0000-0000-0000-000000000001"}},"EntityId":{"name":"entityId","in":"path","required":true,"description":"Entity identifier.","schema":{"type":"string","format":"uuid","example":"00000000-0000-0000-0000-000000000002"}},"ConnectionStateFilter":{"name":"state[]","in":"query","required":false,"description":"Filter by connection state. Can be specified multiple times to match any of the given states.","schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionStateName"}}},"PageCursor":{"name":"pageCursor","in":"query","required":false,"description":"Cursor for pagination. Use the value from `links.next` to fetch the next page.","schema":{"type":"string"}}},"requestBodies":{"PluginIntegrationCreateRequestBody":{"description":"Plugin integration to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["fields"],"properties":{"fields":{"$ref":"#/components/schemas/PluginIntegrationCreateFields"}}}}},"examples":{"basic":{"summary":"Minimal integration","value":{"data":{"fields":{"type":"com.example.myservice","name":"My Service Integration","initialState":{"label":"Push"},"action":{"url":"https://example.com/productboard-push","version":1}}}}},"withAuthAndDisabled":{"summary":"Integration with authorization header, created disabled","value":{"data":{"fields":{"type":"com.example.myservice","name":"My Service Integration","integrationStatus":"disabled","initialState":{"label":"Connect"},"action":{"url":"https://example.com/productboard-push","version":1,"headers":{"authorization":"Bearer my-secret-token"}}}}}}}}}},"PluginIntegrationPatchRequestBody":{"description":"Plugin integration fields to update.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["fields"],"properties":{"fields":{"$ref":"#/components/schemas/PluginIntegrationUpdateFields"}}}}},"examples":{"disable":{"summary":"Disable the integration","value":{"data":{"fields":{"integrationStatus":"disabled"}}}},"rename":{"summary":"Update the display name","value":{"data":{"fields":{"name":"Updated Integration Name"}}}},"newActionUrl":{"summary":"Update action endpoint (triggers re-probe when enabled)","value":{"data":{"fields":{"action":{"url":"https://new-endpoint.example.com/productboard-push","version":1}}}}}}}}},"PluginIntegrationConnectionUpdateStateRequestBody":{"description":"Connection state to set for this entity.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["fields"],"properties":{"fields":{"type":"object","required":["connection"],"properties":{"connection":{"$ref":"#/components/schemas/ConnectionState"}}}}}}},"examples":{"setConnected":{"summary":"Mark entity as connected","value":{"data":{"fields":{"connection":{"state":"connected","label":"Done","hoverLabel":"Issue 123","tooltip":"Issue 123 - Implement XYZ","color":"green","targetUrl":"https://example.com/issues/123"}}}}},"setError":{"summary":"Report a connection error","value":{"data":{"fields":{"connection":{"state":"error","message":"You don't have permission to create an issue."}}}}},"setProgress":{"summary":"Mark as in-progress (async flow)","value":{"data":{"fields":{"connection":{"state":"progress"}}}}},"reset":{"summary":"Reset connection (equivalent to delete)","value":{"data":{"fields":{"connection":{"state":"initial"}}}}}}}}},"PluginIntegrationConnectionsSearchRequestBody":{"description":"Filter criteria for searching plugin integration connections.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"filter":{"$ref":"#/components/schemas/ConnectionsSearchFilter"}}}}},"examples":{"filterByState":{"summary":"Filter by connection state","value":{"data":{"filter":{"state":["connected","error"]}}}},"filterByEntityIds":{"summary":"Filter by entity IDs","value":{"data":{"filter":{"entityId":["00000000-0000-0000-0000-000000000002","00000000-0000-0000-0000-000000000003"]}}}},"filterByBoth":{"summary":"Filter by both state and entity IDs","value":{"data":{"filter":{"state":["connected"],"entityId":["00000000-0000-0000-0000-000000000002"]}}}}}}}}},"schemas":{"PluginIntegration":{"type":"object","description":"A plugin integration resource.","required":["id","type","createdAt","fields","links"],"properties":{"id":{"type":"string","format":"uuid","readOnly":true,"description":"Unique identifier for this plugin integration.","example":"00000000-0000-0000-0000-000000000001"},"type":{"type":"string","const":"pluginIntegration","readOnly":true,"description":"Resource type identifier.","example":"pluginIntegration"},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"ISO 8601 timestamp when the integration was created.","example":"2024-01-15T10:30:00.000Z"},"fields":{"$ref":"#/components/schemas/PluginIntegrationResponseFields"},"links":{"$ref":"#/components/schemas/PluginIntegrationLinks"}}},"PluginIntegrationCreateFields":{"type":"object","description":"Domain attributes for creating a plugin integration.\n\nNote: `action.headers.authorization` is write-only and never returned in responses.\n","required":["type","name","initialState","action"],"properties":{"type":{"$ref":"#/components/schemas/PluginIntegrationType"},"name":{"$ref":"#/components/schemas/PluginIntegrationName"},"integrationStatus":{"$ref":"#/components/schemas/PluginIntegrationStatus"},"initialState":{"$ref":"#/components/schemas/PluginIntegrationInitialState"},"action":{"$ref":"#/components/schemas/Action"}}},"PluginIntegrationUpdateFields":{"type":"object","description":"Domain attributes for partially updating a plugin integration. All fields are\noptional — fields not included preserve their current value.\n\nNote: `action.headers.authorization` is write-only and never returned in responses.\nIf `action` is updated while the integration is `enabled`, a new probe is sent.\n","properties":{"integrationStatus":{"$ref":"#/components/schemas/PluginIntegrationStatus"},"name":{"$ref":"#/components/schemas/PluginIntegrationName"},"initialState":{"$ref":"#/components/schemas/PluginIntegrationInitialState"},"action":{"$ref":"#/components/schemas/Action"}}},"PluginIntegrationResponseFields":{"type":"object","description":"Domain attributes returned in plugin integration responses.\n\nNote: `action` is intentionally absent — it contains a write-only authorization\nsecret that is never returned by the API.\n","required":["type","name","integrationStatus","initialState"],"properties":{"type":{"$ref":"#/components/schemas/PluginIntegrationType"},"name":{"$ref":"#/components/schemas/PluginIntegrationName"},"integrationStatus":{"$ref":"#/components/schemas/PluginIntegrationStatus"},"initialState":{"$ref":"#/components/schemas/PluginIntegrationInitialState"}}},"PluginIntegrationLinks":{"type":"object","description":"Links for this plugin integration resource.","required":["self","connections"],"properties":{"self":{"type":"string","format":"uri","description":"URL to retrieve this plugin integration.","example":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001"},"connections":{"type":"string","format":"uri","description":"URL to list all connections for this plugin integration.","example":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections"}}},"PluginIntegrationReference":{"type":"object","description":"Minimal reference to a plugin integration, returned in mutation responses.","required":["id","links"],"properties":{"id":{"type":"string","format":"uuid","readOnly":true,"description":"Unique identifier for this plugin integration.","example":"00000000-0000-0000-0000-000000000001"},"links":{"$ref":"#/components/schemas/PluginIntegrationLinks"}}},"PluginIntegrationType":{"type":"string","description":"Type identifier for the integration. Use your reversed domain name as a prefix to\nensure global uniqueness (e.g., `com.example.myservice`). Integrations of the same\ntype share the same icon and settings grouping in Productboard.\n","minLength":1,"maxLength":255,"example":"com.example.myservice"},"PluginIntegrationName":{"type":"string","description":"Human-readable name shown as the column name on the Features board and in Productboard settings.","minLength":1,"maxLength":255,"example":"My Service Integration"},"PluginIntegrationStatus":{"type":"string","description":"Controls whether the integration is active:\n- `enabled` — integration column is visible and the push button is active\n- `disabled` — integration column is hidden from the Features board\n","enum":["enabled","disabled"],"default":"enabled","example":"enabled"},"PluginIntegrationInitialState":{"type":"object","description":"Configuration of the push button before an entity has been connected to the third-party system.","required":["label"],"properties":{"label":{"type":"string","description":"Label shown on the push button in its initial (unconnected) state.","minLength":1,"maxLength":10,"example":"Push"}}},"Action":{"type":"object","description":"Configuration for the action endpoint Productboard calls when a user interacts\nwith a push button (push, unlink, or dismiss).\n","required":["url","version"],"properties":{"url":{"type":"string","format":"uri","pattern":"https://.+","description":"HTTPS endpoint Productboard calls when a user clicks a push button.\n\nRequirements:\n- Must use `https`\n- TLS certificate must be valid and signed by a public authority\n- Must be publicly reachable (no `localhost`, private IPs, or internal addresses)\n","maxLength":1024,"example":"https://example.com/productboard-push"},"version":{"type":"integer","description":"Version of the action payload format. Currently only `1` is supported.","enum":[1],"example":1},"headers":{"$ref":"#/components/schemas/ActionHeaders"}}},"ActionHeaders":{"type":"object","description":"Custom headers included in every outgoing request from Productboard to your action\nendpoint — both the creation probe and all action notifications.\n","properties":{"authorization":{"type":"string","writeOnly":true,"description":"Raw value sent in the `Authorization` header of every outgoing request.\nThis field is **write-only** and never returned in API responses.\n\nSupports any scheme:\n- `Basic aGVsbG86d29ybGQ=`\n- `Bearer <token>`\n- Any custom single-header auth scheme\n","maxLength":2048,"example":"Bearer my-action-secret"}}},"ActionPayload":{"type":"object","description":"Payload Productboard sends to your action endpoint when a user clicks a push button.","required":["integrationId","trigger","feature"],"properties":{"integrationId":{"type":"string","format":"uuid","description":"ID of the plugin integration whose button was clicked.","example":"00000000-0000-0000-0000-000000000001"},"trigger":{"type":"string","description":"Identifies which button the user clicked:\n- `button.push` — clicked to connect / push the entity\n- `button.unlink` — clicked to unlink the connected entity\n- `button.dismiss` — dismissed an error popup\n","enum":["button.push","button.unlink","button.dismiss"],"example":"button.push"},"feature":{"type":"object","description":"The Productboard entity associated with the clicked button.","required":["id","links"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the entity.","example":"00000000-0000-0000-0000-000000000002"},"links":{"type":"object","required":["self","html"],"properties":{"self":{"type":"string","format":"uri","description":"API URL to retrieve the entity.","example":"https://api.productboard.com/features/00000000-0000-0000-0000-000000000002"},"html":{"type":"string","format":"uri","description":"URL to the entity detail page in the Productboard web application.","example":"https://app.productboard.com/feature-board/1/features/42/detail"}}}}},"triggeredByUser":{"type":"object","description":"The Productboard user who triggered the action.","required":["name","email"],"properties":{"name":{"type":"string","description":"Display name of the user.","example":"Jane Doe"},"email":{"type":"string","format":"email","description":"Email address of the user.","example":"jane.doe@example.com"}}},"links":{"type":"object","description":"Relevant links for this action notification.","required":["connection"],"properties":{"connection":{"type":"string","format":"uri","description":"URL of the plugin integration connection for this entity. Use this endpoint\nto update the connection state asynchronously after processing the action.\n","example":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}}}},"PluginIntegrationConnection":{"type":"object","description":"A plugin integration connection resource, representing the relationship between\na Productboard entity and an entity in the third-party system.\n\nThe `id` is the Productboard entity ID. The connection is scoped to a specific\nplugin integration identified in the URL path.\n","required":["id","type","fields","links"],"properties":{"id":{"type":"string","format":"uuid","readOnly":true,"description":"The Productboard entity ID this connection belongs to.","example":"00000000-0000-0000-0000-000000000002"},"type":{"type":"string","const":"pluginIntegrationConnection","readOnly":true,"description":"Resource type identifier.","example":"pluginIntegrationConnection"},"fields":{"$ref":"#/components/schemas/PluginIntegrationConnectionFields"},"links":{"$ref":"#/components/schemas/PluginIntegrationConnectionLinks"}}},"PluginIntegrationConnectionFields":{"type":"object","description":"Domain attributes of a plugin integration connection.","required":["connection"],"properties":{"connection":{"$ref":"#/components/schemas/ConnectionState"}}},"PluginIntegrationConnectionLinks":{"type":"object","description":"Links for this connection resource.","required":["self"],"properties":{"self":{"type":"string","format":"uri","description":"URL to retrieve this connection.","example":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}},"PluginIntegrationConnectionReference":{"type":"object","description":"Minimal reference to a plugin integration connection, returned in mutation responses.","required":["id","links"],"properties":{"id":{"type":"string","format":"uuid","readOnly":true,"description":"The Productboard entity ID this connection belongs to.","example":"00000000-0000-0000-0000-000000000002"},"links":{"$ref":"#/components/schemas/PluginIntegrationConnectionLinks"}}},"ConnectionStateName":{"type":"string","description":"Identifies a connection state value.","enum":["connected","error","progress","initial"]},"ConnectionsSearchFilter":{"type":"object","description":"Filter criteria for searching plugin integration connections.","properties":{"state":{"type":"array","description":"Filter by connection state.","items":{"$ref":"#/components/schemas/ConnectionStateName"}},"entityId":{"type":"array","description":"Filter by entity IDs.","items":{"type":"string","format":"uuid"}}}},"ConnectionState":{"description":"The state of a plugin integration connection. Determined by the `state` property.\n\n| State | Meaning | Push button appearance |\n|---|---|---|\n| `connected` | Entity linked to third-party entity | Shows `label`; click opens `targetUrl` |\n| `error` | Connection attempt failed | Shows error icon with `message` |\n| `progress` | Establishing asynchronously | Shows loading indicator |\n| `initial` | No connection (default) | Shows integration's `initialState.label` |\n","oneOf":[{"$ref":"#/components/schemas/ConnectedState"},{"$ref":"#/components/schemas/ErrorState"},{"$ref":"#/components/schemas/ProgressState"},{"$ref":"#/components/schemas/InitialConnectionState"}]},"ConnectedState":{"type":"object","description":"Represents a successfully established connection. The push button displays custom\nlabel, hover text, tooltip, color, and a link to the connected entity.\n","required":["state","label","hoverLabel","tooltip","color","targetUrl"],"properties":{"state":{"type":"string","enum":["connected"],"description":"Connection state discriminator."},"label":{"type":"string","description":"Text on the push button when connected. Typically the entity's current status.","minLength":1,"maxLength":20,"example":"Done"},"hoverLabel":{"type":"string","description":"Text on the push button on hover (temporarily replaces `label`). Typically shows the linked entity's identifier.","minLength":1,"maxLength":20,"example":"Issue 123"},"tooltip":{"type":"string","description":"Longer tooltip text shown on hover. Ideal for a detailed description of the linked entity.","minLength":1,"maxLength":50,"example":"Issue 123 - Implement XYZ"},"color":{"type":"string","description":"Background color of the push button.","enum":["gray","blue","green","yellow","orange","red","purple"],"example":"green"},"targetUrl":{"type":"string","format":"uri","description":"URL that opens when the user clicks the connected push button. Should link to the connected entity in the third-party system.","minLength":1,"maxLength":1000,"example":"https://example.com/issues/123"}}},"ErrorState":{"type":"object","description":"Represents a failed connection attempt. The push button displays an error icon with the provided message.","required":["state","message"],"properties":{"state":{"type":"string","enum":["error"],"description":"Connection state discriminator."},"message":{"type":"string","description":"Human-readable error message explaining why the connection could not be established. Shown in the error popup in the Productboard UI.","minLength":1,"maxLength":255,"example":"You don't have permission to create an issue."},"debugDetail":{"type":"string","readOnly":true,"description":"Additional debugging information set by Productboard when a push fails on\nProductboard's side. **Not visible in the UI** — for developer use during\nintegration development only. Never set by the partner.\n","maxLength":8000,"example":"JSON decoding error: field 'targetUrl' is missing"}}},"ProgressState":{"type":"object","description":"Represents an in-progress connection attempt. The push button shows a loading\nindicator.\n\nUse when processing is asynchronous: respond with `progress` immediately from your\naction endpoint, then call Configure Connection with the final state once done.\n","required":["state"],"properties":{"state":{"type":"string","enum":["progress"],"description":"Connection state discriminator."}}},"InitialConnectionState":{"type":"object","description":"Represents the default, unconnected state. The push button shows the integration's\n`initialState.label`. Setting a connection to `initial` via Configure Connection\nis equivalent to deleting it.\n","required":["state"],"properties":{"state":{"type":"string","enum":["initial"],"description":"Connection state discriminator."}}},"ListLinks":{"type":"object","required":["next"],"properties":{"next":{"type":["string","null"]}}},"ErrorSource":{"type":"object","description":"An object containing references to the source of the error. It helps with location of the problematic field or parameter.\n","properties":{"pointer":{"type":"string","description":"JSON Pointer [RFC6901] to the associated entity in the request document.\n","example":"/data/fields/status"},"parameter":{"type":"string","description":"Query parameter causing the error.\nUsed when the error is related to a query parameter rather than request body.\n","example":"pageCursor"}}},"Error":{"type":"object","description":"Individual error object containing the details and context.\n","required":["code","title","detail"],"properties":{"code":{"type":"string","description":"A unique, machine-readable and stable code that identifies this error.\nMust be consistent across all services and provide clear categorization.\nFormat: `category.subcategory.specificError`\n","examples":["auth.accessDenied","resource.notFound"]},"title":{"type":"string","description":"A short, human-readable summary of the problem that doesn't change\nfrom occurrence to occurrence of the problem. Should provide immediate\nunderstanding of the error type.\n","maxLength":255,"example":"Missing required field"},"detail":{"type":"string","description":"A human-readable explanation specific to this occurrence of the problem.\nMay include suggestions for resolution but should not expose sensitive information.\n","maxLength":1000,"example":"Field 'status' is required."},"source":{"$ref":"#/components/schemas/ErrorSource"}}},"ErrorResponse":{"type":"object","description":"Standard error response structure.\n","required":["id","errors"],"properties":{"id":{"type":"string","description":"A unique identifier for this specific occurrence of the problem.\nIt carries unique request/response ID which allows client\nand server to reference the exact instance of the error\nin logs or support communication.\n"},"errors":{"type":"array","description":"Array of error objects, multiple errors can be returned.","items":{"$ref":"#/components/schemas/Error"},"minItems":1}}}},"responses":{"PluginIntegrationCreateResponse":{"description":"Plugin integration created successfully","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PluginIntegrationReference"}}},"examples":{"created":{"summary":"Integration created","value":{"data":{"id":"00000000-0000-0000-0000-000000000001","links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001","connections":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections"}}}}}}}},"PluginIntegrationsListResponse":{"description":"A paginated list of plugin integrations","content":{"application/json":{"schema":{"type":"object","required":["data","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PluginIntegration"}},"links":{"$ref":"#/components/schemas/ListLinks"}}},"example":{"data":[{"id":"00000000-0000-0000-0000-000000000001","type":"pluginIntegration","createdAt":"2024-01-15T10:30:00.000Z","fields":{"type":"com.example.myservice","name":"My Service Integration","integrationStatus":"enabled","initialState":{"label":"Push"}},"links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001","connections":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections"}}],"links":{"next":null}}}}},"PluginIntegrationResponse":{"description":"Plugin integration details","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PluginIntegration"}}}}}},"PluginIntegrationPatchResponse":{"description":"Plugin integration updated successfully","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PluginIntegrationReference"}}}}}},"PluginIntegrationConnectionsListResponse":{"description":"A paginated list of plugin integration connections","content":{"application/json":{"schema":{"type":"object","required":["data","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PluginIntegrationConnection"}},"links":{"$ref":"#/components/schemas/ListLinks"}}},"example":{"data":[{"id":"00000000-0000-0000-0000-000000000002","type":"pluginIntegrationConnection","fields":{"connection":{"state":"connected","label":"Done","hoverLabel":"Issue 123","tooltip":"Issue 123 - Implement XYZ","color":"green","targetUrl":"https://example.com/issues/123"}},"links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}],"links":{"next":null}}}}},"GetPluginIntegrationConnectionResponse":{"description":"Plugin integration connection details","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PluginIntegrationConnection"}}},"examples":{"connected":{"summary":"Entity connected to third-party entity","value":{"data":{"id":"00000000-0000-0000-0000-000000000002","type":"pluginIntegrationConnection","fields":{"connection":{"state":"connected","label":"Done","hoverLabel":"Issue 123","tooltip":"Issue 123 - Implement XYZ","color":"green","targetUrl":"https://example.com/issues/123"}},"links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}}},"initial":{"summary":"No connection established yet","value":{"data":{"id":"00000000-0000-0000-0000-000000000002","type":"pluginIntegrationConnection","fields":{"connection":{"state":"initial"}},"links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}}},"error":{"summary":"Connection attempt failed","value":{"data":{"id":"00000000-0000-0000-0000-000000000002","type":"pluginIntegrationConnection","fields":{"connection":{"state":"error","message":"You don't have permission to create an issue."}},"links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}}},"progress":{"summary":"Connection being established asynchronously","value":{"data":{"id":"00000000-0000-0000-0000-000000000002","type":"pluginIntegrationConnection","fields":{"connection":{"state":"progress"}},"links":{"self":"https://api.productboard.com/v2/plugin-integrations/00000000-0000-0000-0000-000000000001/connections/00000000-0000-0000-0000-000000000002"}}}}}}}},"PluginIntegrationConnectionUpdateStateResponse":{"description":"Connection state set successfully","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PluginIntegrationConnectionReference"}}}}}},"BadRequestResponse":{"description":"Bad Request - Invalid input format or malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"schemaInvalid":{"summary":"Request does not satisfy schema","value":{"id":"req-1234567890","errors":[{"code":"request.invalid","title":"Request schema invalid","detail":"The request does not satisfy the required schema. Field 'type' is required but was not provided.","source":{"pointer":"/data/type"}}]}},"fieldInvalid":{"summary":"Invalid field value type","value":{"id":"req-1234567891","errors":[{"code":"request.invalid","title":"Request schema invalid","detail":"Field 'fields/name' must be a string with maximum length of 255 characters, but received a number.","source":{"pointer":"/data/fields/name"}}]}},"malformedJson":{"summary":"Malformed JSON syntax","value":{"id":"req-1234567892","errors":[{"code":"request.malformed","title":"Request malformed","detail":"The request body contains invalid JSON syntax. Expected ',' or '}' at line 5, column 12."}]}},"invalidContentType":{"summary":"Invalid content type","value":{"id":"req-1234567893","errors":[{"code":"request.malformed","title":"Request malformed","detail":"The Content-Type header must be 'application/json'. Received 'text/plain'."}]}}}}}},"UnauthorizedResponse":{"description":"Unauthorized - Missing or invalid authentication credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingCredentials":{"summary":"Missing authentication credentials","value":{"id":"req-2234567890","errors":[{"code":"auth.invalid","title":"Invalid authentication","detail":"Authentication credentials are missing."}]}},"invalidToken":{"summary":"Invalid or expired token","value":{"id":"req-2234567891","errors":[{"code":"auth.invalid","title":"Invalid authentication","detail":"The provided authentication credentials are invalid or have expired. Please obtain new credentials and try again."}]}},"revokedCredentials":{"summary":"Revoked credentials","value":{"id":"req-2234567892","errors":[{"code":"auth.invalid","title":"Invalid authentication","detail":"The authentication credentials have been revoked. Please contact your workspace administrator."}]}}}}}},"ForbiddenResponse":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"permissionDenied":{"summary":"Permission denied for resource","value":{"id":"req-3234567890","errors":[{"code":"auth.accessDenied","title":"Access denied","detail":"You do not have permission to perform this operation on the specified resource. Contact your workspace administrator to request access."}]}},"insufficientScope":{"summary":"Insufficient OAuth scopes","value":{"id":"req-3234567891","errors":[{"code":"auth.accessDenied","title":"Access denied","detail":"Your authentication token lacks the required OAuth scopes to perform this operation. Please re-authenticate with the necessary scopes."}]}},"readOnlyRole":{"summary":"Read-only role restriction","value":{"id":"req-3234567892","errors":[{"code":"auth.accessDenied","title":"Access denied","detail":"Your user role does not permit this operation. Please contact your workspace administrator."}]}}}}}},"RequestTimeoutResponse":{"description":"Request Timeout - The server did not receive a complete request within the allowed time","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"requestTimeout":{"summary":"Request timeout","value":{"id":"req-5234567890","errors":[{"code":"request.timeout","title":"Request timeout","detail":"The request took too long to process and was terminated. Try simplifying your request or try again later."}]}}}}}},"TooManyRequestsResponse":{"description":"Too Many Requests - API rate limit exceeded, reduce request frequency and retry after the indicated time","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"rateLimitExceeded":{"summary":"Rate limit exceeded","value":{"id":"req-7234567890","errors":[{"code":"rate.limitExceeded","title":"Rate limit exceeded","detail":"You have exceeded the allowed number of API requests. Please wait before making additional requests."}]}}}}}},"InternalServerErrorResponse":{"description":"Internal Server Error - An unexpected error occurred on the server, please retry or contact support","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"internalServerError":{"summary":"Unexpected server error","value":{"id":"req-8234567890","errors":[{"code":"internal.serverError","title":"Internal server error","detail":"An unexpected error occurred while processing your request. Please try again later. If the problem persists, contact support with the correlation ID."}]}}}}}},"UnprocessableEntityResponse":{"description":"Unprocessable Entity - Validation failed (e.g., missing required fields, unknown fields)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validationFailed":{"summary":"Validation failed","value":{"id":"req-6234567890","errors":[{"code":"validation.failed","title":"Validation failed","detail":"One or more fields failed validation.","source":{"pointer":"/data/type"}}]}},"validationMultipleFields":{"summary":"Multiple validation errors","value":{"id":"req-6234567891","errors":[{"code":"validation.failed","title":"Validation failed","detail":"The field 'name' cannot be empty.","source":{"pointer":"/data/fields/name"}},{"code":"validation.failed","title":"Validation failed","detail":"The field 'type' has an unsupported value.","source":{"pointer":"/data/fields/type"}}]}},"referenceNotFound":{"summary":"Referenced resource not found","value":{"id":"req-6234567892","errors":[{"code":"validation.referenceNotFound","title":"Referenced resource not found","detail":"The referenced resource could not be found or you do not have access to it.","source":{"pointer":"/data/fields/id"}}]}}}}}},"NotFoundResponse":{"description":"Not Found - The requested resource does not exist or is not accessible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"resourceNotFoundById":{"summary":"Resource not found by ID","value":{"id":"req-4234567890","errors":[{"code":"resource.notFound","title":"Resource not found","detail":"The requested resource could not be found or you do not have permission to access it."}]}},"routeNotFound":{"summary":"API route not found","value":{"id":"req-4234567893","errors":[{"code":"route.notFound","title":"Route not found","detail":"The requested URL path does not match any defined API route. Please check the API documentation for valid endpoints."}]}}}}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"API token authentication. Use your API token as the value of the Authorization header"},"OAuth2":{"type":"oauth2","description":"OAuth 2.0 authentication. Use this for user-authorized access to Productboard API.","flows":{"authorizationCode":{"authorizationUrl":"https://app.productboard.com/oauth/authorize","tokenUrl":"https://app.productboard.com/oauth/token","scopes":{"entities:read":"Read access to entities (features, components, initiatives, etc.)","write:entities":"Write access to create and modify entities","entities:delete":"Delete entities"}}}}}},"x-readme":{"samples-languages":["shell","javascript","ruby"]}}