{
  "openapi": "3.1.0",
  "info": {
    "title": "EthicsPortal API",
    "version": "1.1.0",
    "description": "Report metadata, organisation and whistleblowing channel setup, administration, product feedback and outbound webhooks. Each token carries the scopes chosen at issuance; write scopes are opt-in. Report content, attachments and their metadata, messages, notes, category, closure outcome, retaliation, reporter identity and access codes are always excluded. Billing and agreement acceptance require a browser session."
  },
  "servers": [
    {
      "url": "https://secure.ethicsportal.eu",
      "description": "EthicsPortal application"
    }
  ],
  "security": [
    {
      "personalToken": []
    },
    {
      "organizationToken": []
    }
  ],
  "paths": {
    "/identity.json": {
      "get": {
        "operationId": "getIdentity",
        "security": [
          {
            "personalToken": []
          },
          {
            "organizationToken": []
          }
        ],
        "description": "Return the organisations that an access token can read, with the token's scopes and expiry date. Use it to find organisation IDs. An assistant connection of a person who has no organisation yet returns an empty list.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Metadata only. Dates describe recorded workflow state, not proof of notification delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Identity"
                },
                "example": {
                  "type": "personal",
                  "name": "Case dashboard",
                  "last_four": "x7Qa",
                  "scopes": ["reports:read", "channel:read"],
                  "expires_at": "2027-02-05T10:00:00Z",
                  "last_used_at": "2026-11-09T16:42:00Z",
                  "organizations": [
                    {
                      "id": "gHiJkl",
                      "name": "Example organisation",
                      "reports_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports.json",
                      "channel_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/portal.json",
                      "members_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/members.json",
                      "webhooks_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks.json",
                      "audit_trail_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/audit_logs.json",
                      "documents_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/documents.json",
                      "billing_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/subscriptions.json"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"EthicsPortal\""
              }
            }
          },
          "403": {
            "description": "Required agreement not accepted or permission denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "agreement_required",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing or inaccessible organisation or resource, or feature disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "60 seconds"
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "Discover organisations"
      }
    },
    "/organizations/{organization_id}/whistleblower/portal.json": {
      "get": {
        "operationId": "getChannel",
        "security": [
          {
            "personalToken": ["channel:read"]
          },
          {
            "organizationToken": ["channel:read"]
          }
        ],
        "description": "Return the whistleblowing channel of one organisation: its ID, whether it is active and the URL of its online reporting form. Any member's personal token can read it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Metadata only. Dates describe recorded workflow state, not proof of notification delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channel"
                },
                "example": {
                  "id": "QATEST12",
                  "active": true,
                  "reporting_form_url": "https://secure.ethicsportal.eu/p/QATEST12",
                  "configuration_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/portal/configuration.json",
                  "documents_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/documents.json"
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"EthicsPortal\""
              }
            }
          },
          "403": {
            "description": "Required agreement not accepted or permission denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "agreement_required",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing or inaccessible organisation or resource, or feature disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "60 seconds"
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "Read whistleblowing channel metadata"
      }
    },
    "/organizations/{organization_id}/whistleblower/reports.json": {
      "get": {
        "operationId": "listReports",
        "security": [
          {
            "personalToken": ["reports:read"]
          },
          {
            "organizationToken": ["reports:read"]
          }
        ],
        "description": "List report metadata for one organisation: status, assignment, deadlines and overdue flags. Filter by status or creation date and page with a cursor. A personal token lists only the reports its user can open: every report for administrators and viewers, and for members only the reports assigned to them or in which they participate. An organisation token lists every report.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["received", "acknowledged", "feedback_given", "closed"]
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Exclusive lower receipt-time bound."
          },
          {
            "name": "created_before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Exclusive upper receipt-time bound."
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Metadata only. Dates describe recorded workflow state, not proof of notification delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportList"
                },
                "example": {
                  "data": [
                    {
                      "id": "aBcDef",
                      "organization_id": "gHiJkl",
                      "status": "received",
                      "assigned": false,
                      "created_at": "2026-11-07T10:00:00Z",
                      "acknowledged_at": null,
                      "feedback_given_at": null,
                      "closed_at": null,
                      "acknowledgment_due_at": "2026-11-14T10:00:00Z",
                      "feedback_due_at": "2027-02-14T10:00:00Z",
                      "acknowledgment_overdue": false,
                      "feedback_overdue": false,
                      "url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports/aBcDef.json",
                      "web_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports/aBcDef",
                      "audit_trail_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports/aBcDef/audit-log.json"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"EthicsPortal\""
              }
            }
          },
          "403": {
            "description": "Required agreement not accepted or permission denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "agreement_required",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing or inaccessible organisation or resource, or feature disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "60 seconds"
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List report metadata"
      }
    },
    "/organizations/{organization_id}/whistleblower/reports/{id}.json": {
      "get": {
        "operationId": "getReport",
        "security": [
          {
            "personalToken": ["reports:read"]
          },
          {
            "organizationToken": ["reports:read"]
          }
        ],
        "description": "Read the metadata of one report: status, assignment, acknowledgement and feedback deadlines, and overdue flags. Report content is never returned. A personal token reads only a report its user can open; any other report returns 404. An organisation token reads every report.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/ReportId"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Metadata only. Dates describe recorded workflow state, not proof of notification delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                },
                "example": {
                  "id": "aBcDef",
                  "organization_id": "gHiJkl",
                  "status": "received",
                  "assigned": false,
                  "created_at": "2026-11-07T10:00:00Z",
                  "acknowledged_at": null,
                  "feedback_given_at": null,
                  "closed_at": null,
                  "acknowledgment_due_at": "2026-11-14T10:00:00Z",
                  "feedback_due_at": "2027-02-14T10:00:00Z",
                  "acknowledgment_overdue": false,
                  "feedback_overdue": false,
                  "url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports/aBcDef.json",
                  "web_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports/aBcDef",
                  "audit_trail_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports/aBcDef/audit-log.json"
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer realm=\"EthicsPortal\""
              }
            }
          },
          "403": {
            "description": "Required agreement not accepted or permission denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "agreement_required",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing or inaccessible organisation or resource, or feature disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "60 seconds"
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "Read report metadata"
      }
    },
    "/organizations.json": {
      "post": {
        "operationId": "createOrganization",
        "security": [
          {
            "personalToken": ["organizations:create"]
          }
        ],
        "summary": "Create an organisation",
        "description": "Create an organisation. Requires organizations:create, which only personal tokens can hold, current user terms, and at least one active membership. An assistant connection is the exception: a person with no organisation can create their first one through it. The owner and an administrator membership are assigned from the token user. Creates the default inactive whistleblowing channel. No billing or DPA acceptance is performed: the owner accepts the DPA in a browser, and until then the organisation's operations answer agreement_required. Limited to 10 creation attempts per user per minute, across tokens. Send an Idempotency-Key so a retry after a lost response returns the first response instead of creating a second organisation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrganizationInput"
              },
              "example": {
                "organization": {
                  "name": "Example organisation"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Organisation and owner membership created, with a default channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedOrganization"
                },
                "example": {
                  "id": "gHiJkl",
                  "name": "Example organisation",
                  "membership_id": "mAbCde",
                  "channel": {
                    "id": "AB12CD34",
                    "active": false
                  },
                  "reports_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/reports.json",
                  "channel_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/portal.json",
                  "members_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/members.json",
                  "webhooks_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks.json",
                  "audit_trail_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/audit_logs.json",
                  "documents_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/documents.json",
                  "billing_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/subscriptions.json"
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Organisation web page; requires a separate authenticated browser session.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body, attributes or types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unusable token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required scope missing, a personal token whose user is not a current administrator, or agreement_required until browser acceptance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource inaccessible, or API access suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Model validation failed. Fields are attribute names only; values and personal data are never reflected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["name"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/organizations/{organization_id}/whistleblower/portal/configuration.json": {
      "get": {
        "operationId": "getChannelConfiguration",
        "security": [
          {
            "personalToken": ["channel:write"]
          },
          {
            "organizationToken": ["channel:write"]
          }
        ],
        "summary": "Read channel configuration",
        "description": "Read the channel's company details, settings and compliance officer. Requires channel:write. A personal token also needs its user to be a current administrator; an organisation token needs only the scope. This privileged resource includes company details and the compliance officer membership ID; the ordinary channel metadata endpoint remains unchanged. Organisation integrations and the current DPA must be enabled/accepted.",
        "parameters": [
          {
            "name": "organization_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Obfuscated organisation ID."
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Current settings and derived readiness.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelConfiguration"
                },
                "example": {
                  "legal_name": "Example company",
                  "country_code": "FR",
                  "registration_number": "EXAMPLE-123",
                  "address": "1 Example Street",
                  "employee_range": "1-49",
                  "industry": "professional_scientific_services",
                  "website": null,
                  "phone_number": null,
                  "compliance_officer_id": "mAbCde",
                  "brand_color": "#123456",
                  "retention_months": 60,
                  "social_dialogue_consulted": false,
                  "voice_recording_enabled": false,
                  "email_intake_enabled": false,
                  "id": "AB12CD34",
                  "active": false,
                  "verified": false,
                  "reporting_form_url": "https://secure.ethicsportal.eu/p/AB12CD34",
                  "missing_fields": []
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid JSON body, attributes or types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unusable token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required scope missing, a personal token whose user is not a current administrator, or agreement_required until browser acceptance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource inaccessible, or API access suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        }
      },
      "patch": {
        "operationId": "updateChannelConfiguration",
        "security": [
          {
            "personalToken": ["channel:write"]
          },
          {
            "organizationToken": ["channel:write"]
          }
        ],
        "summary": "Configure a whistleblowing channel",
        "description": "Change the channel's company details and settings. Requires channel:write. A personal token also needs its user to be a current administrator; an organisation token needs only the scope. Send application/json with a nonempty portal object; only supplied fields change. Unknown fields, query parameters and incorrect JSON types are rejected. Verified company details remain locked. Retention minimums, active administrator appointments and readiness rules are the same as the web application. active, verified, slug, billing and organisation flags cannot be set.",
        "parameters": [
          {
            "name": "organization_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Obfuscated organisation ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelConfigurationInput"
              },
              "example": {
                "portal": {
                  "legal_name": "Example company",
                  "country_code": "FR",
                  "registration_number": "EXAMPLE-123",
                  "address": "1 Example Street",
                  "employee_range": "1-49",
                  "industry": "professional_scientific_services",
                  "website": null,
                  "phone_number": null,
                  "compliance_officer_id": "mAbCde",
                  "brand_color": "#123456",
                  "retention_months": 60,
                  "social_dialogue_consulted": false,
                  "voice_recording_enabled": false,
                  "email_intake_enabled": false
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validated settings saved. active is derived, never a promise of activation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelConfiguration"
                },
                "example": {
                  "legal_name": "Example company",
                  "country_code": "FR",
                  "registration_number": "EXAMPLE-123",
                  "address": "1 Example Street",
                  "employee_range": "1-49",
                  "industry": "professional_scientific_services",
                  "website": null,
                  "phone_number": null,
                  "compliance_officer_id": "mAbCde",
                  "brand_color": "#123456",
                  "retention_months": 60,
                  "social_dialogue_consulted": false,
                  "voice_recording_enabled": false,
                  "email_intake_enabled": false,
                  "id": "AB12CD34",
                  "active": false,
                  "verified": false,
                  "reporting_form_url": "https://secure.ethicsportal.eu/p/AB12CD34",
                  "missing_fields": []
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body, attributes or types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unusable token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required scope missing, a personal token whose user is not a current administrator, or agreement_required until browser acceptance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource inaccessible, or API access suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Model validation failed. Fields are attribute names only; values and personal data are never reflected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["retention_months"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/organizations/{organization_id}/webhooks.json": {
      "get": {
        "operationId": "listWebhooks",
        "security": [
          {
            "personalToken": ["webhooks:read"]
          },
          {
            "organizationToken": ["webhooks:read"]
          }
        ],
        "description": "List the organisation's webhooks. Secrets are never included. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "The organisation's webhooks (at most ten).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                },
                "example": {
                  "data": [
                    {
                      "id": "pQrStu",
                      "name": "Case dashboard",
                      "url": "https://receiver.example/ethicsportal",
                      "enabled": true,
                      "events": ["report.created", "report.overdue"],
                      "failing_since": null,
                      "created_at": "2026-11-09T10:00:00Z",
                      "updated_at": "2026-11-09T10:00:00Z",
                      "deliveries_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks/pQrStu/deliveries.json"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List webhooks"
      },
      "post": {
        "operationId": "createWebhook",
        "security": [
          {
            "personalToken": ["webhooks:write"]
          },
          {
            "organizationToken": ["webhooks:write"]
          }
        ],
        "description": "Register a public HTTPS destination for the selected events. The response contains the signing secret once. A personal token also needs its user to be a current administrator of the organisation. Each organisation can have up to ten webhooks.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Webhook created; store the secret now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                },
                "example": {
                  "id": "pQrStu",
                  "name": "Case dashboard",
                  "url": "https://receiver.example/ethicsportal",
                  "enabled": true,
                  "events": ["report.created", "report.overdue"],
                  "failing_since": null,
                  "created_at": "2026-11-09T10:00:00Z",
                  "updated_at": "2026-11-09T10:00:00Z",
                  "secret": "ep_whsec_example",
                  "deliveries_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks/pQrStu/deliveries.json"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation failed; fields are named, values are not echoed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["url"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Create a webhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreateInput"
              },
              "example": {
                "webhook": {
                  "name": "Case dashboard",
                  "url": "https://receiver.example/ethicsportal",
                  "events": ["report.created", "report.overdue"]
                }
              }
            }
          }
        }
      }
    },
    "/organizations/{organization_id}/webhooks/{webhook_id}.json": {
      "get": {
        "operationId": "getWebhook",
        "security": [
          {
            "personalToken": ["webhooks:read"]
          },
          {
            "organizationToken": ["webhooks:read"]
          }
        ],
        "description": "Read one webhook without its secret. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "pQrStu",
                  "name": "Case dashboard",
                  "url": "https://receiver.example/ethicsportal",
                  "enabled": true,
                  "events": ["report.created", "report.overdue"],
                  "failing_since": null,
                  "created_at": "2026-11-09T10:00:00Z",
                  "updated_at": "2026-11-09T10:00:00Z",
                  "deliveries_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks/pQrStu/deliveries.json"
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "Read a webhook"
      },
      "patch": {
        "operationId": "updateWebhook",
        "security": [
          {
            "personalToken": ["webhooks:write"]
          },
          {
            "organizationToken": ["webhooks:write"]
          }
        ],
        "description": "Change the name, URL, enabled state or events; only supplied fields change. Changing the URL, events or enabled state cancels pending deliveries from the previous configuration. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "pQrStu",
                  "name": "Case dashboard",
                  "url": "https://receiver.example/ethicsportal",
                  "enabled": false,
                  "events": ["report.created", "report.overdue"],
                  "failing_since": null,
                  "created_at": "2026-11-09T10:00:00Z",
                  "updated_at": "2026-11-09T10:00:00Z",
                  "deliveries_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks/pQrStu/deliveries.json"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation failed; fields are named, values are not echoed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["events"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Update a webhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdateInput"
              },
              "example": {
                "webhook": {
                  "enabled": false
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "security": [
          {
            "personalToken": ["webhooks:write"]
          },
          {
            "organizationToken": ["webhooks:write"]
          }
        ],
        "description": "Delete the webhook and its delivery history. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Delete a webhook"
      }
    },
    "/organizations/{organization_id}/webhooks/{webhook_id}/rotate.json": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "security": [
          {
            "personalToken": ["webhooks:write"]
          },
          {
            "organizationToken": ["webhooks:write"]
          }
        ],
        "description": "Replace the signing secret immediately; there is no overlap period. Pending deliveries from the previous secret are cancelled. The response contains the new secret once. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook with its new secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                },
                "example": {
                  "id": "pQrStu",
                  "name": "Case dashboard",
                  "url": "https://receiver.example/ethicsportal",
                  "enabled": true,
                  "events": ["report.created", "report.overdue"],
                  "failing_since": null,
                  "created_at": "2026-11-09T10:00:00Z",
                  "updated_at": "2026-11-09T10:00:00Z",
                  "secret": "ep_whsec_example",
                  "deliveries_url": "https://secure.ethicsportal.eu/organizations/gHiJkl/webhooks/pQrStu/deliveries.json"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Rotate a webhook's signing secret"
      }
    },
    "/organizations/{organization_id}/webhooks/{webhook_id}/test.json": {
      "post": {
        "operationId": "testWebhook",
        "security": [
          {
            "personalToken": ["webhooks:write"]
          },
          {
            "organizationToken": ["webhooks:write"]
          }
        ],
        "description": "Queue a webhook.test event containing only the organisation ID. A queued delivery is not proof of receipt; check its status. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Test delivery queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                },
                "example": {
                  "id": "vWxYza",
                  "event_id": "bCdEfg",
                  "event_type": "webhook.test",
                  "status": "pending",
                  "attempts": 0,
                  "response_code": null,
                  "error_code": null,
                  "created_at": "2026-11-09T10:05:00Z",
                  "delivered_at": null,
                  "next_attempt_at": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Send a test event"
      }
    },
    "/organizations/{organization_id}/webhooks/{webhook_id}/deliveries.json": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "security": [
          {
            "personalToken": ["webhooks:read"]
          },
          {
            "organizationToken": ["webhooks:read"]
          }
        ],
        "description": "Deliveries retained for 30 days, oldest first. Receiver response bodies are never stored. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryList"
                },
                "example": {
                  "data": [
                    {
                      "id": "vWxYza",
                      "event_id": "bCdEfg",
                      "event_type": "webhook.test",
                      "status": "pending",
                      "attempts": 0,
                      "response_code": null,
                      "error_code": null,
                      "created_at": "2026-11-09T10:05:00Z",
                      "delivered_at": null,
                      "next_attempt_at": null
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List webhook deliveries"
      }
    },
    "/organizations/{organization_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry.json": {
      "post": {
        "operationId": "retryWebhookDelivery",
        "security": [
          {
            "personalToken": ["webhooks:write"]
          },
          {
            "organizationToken": ["webhooks:write"]
          }
        ],
        "description": "Retry a failed delivery with the webhook's current URL and secret, keeping its event ID. Only failed deliveries can be retried. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "$ref": "#/components/parameters/DeliveryId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Retry queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                },
                "example": {
                  "id": "vWxYza",
                  "event_id": "bCdEfg",
                  "event_type": "report.created",
                  "status": "pending",
                  "attempts": 0,
                  "response_code": null,
                  "error_code": null,
                  "created_at": "2026-11-09T10:05:00Z",
                  "delivered_at": null,
                  "next_attempt_at": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The delivery is not failed, the Idempotency-Key was used for a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_retryable",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Retry a failed delivery"
      }
    },
    "/organizations/{organization_id}/whistleblower/audit_logs.json": {
      "get": {
        "operationId": "listAuditTrail",
        "security": [
          {
            "personalToken": ["audit_log:read"]
          },
          {
            "organizationToken": ["audit_log:read"]
          }
        ],
        "description": "The organisation's audit trail: who did what and when. Omits anything a reporter did and entries about messages, notes and retaliation, consistent with the data boundary. Entries carry no change details, IP addresses or email addresses. Oldest first. A personal token needs its user to be a current administrator or viewer. Each request is itself recorded in the trail. Entries appear once they are a minute old, so a collector resuming with created_after cannot skip one that committed late.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Exclusive lower bound; resume a log collector from the last entry it stored."
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Audit trail entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditLogList"
                },
                "example": {
                  "data": [
                    {
                      "id": "hIjKlm",
                      "action": "whistleblower.report.viewed",
                      "actor": {
                        "kind": "member",
                        "name": "Anna Novak",
                        "membership_id": "nOpQrs"
                      },
                      "subject": {
                        "type": "report",
                        "id": "aBcDef"
                      },
                      "created_at": "2026-11-09T10:00:00Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List the audit trail"
      }
    },
    "/organizations/{organization_id}/whistleblower/reports/{id}/audit-log.json": {
      "get": {
        "operationId": "listReportAuditTrail",
        "security": [
          {
            "personalToken": ["audit_log:read"]
          },
          {
            "organizationToken": ["audit_log:read"]
          }
        ],
        "description": "One report's audit trail. Omits anything a reporter did and entries about messages, notes and retaliation, consistent with the data boundary. Entries carry no change details, IP addresses or email addresses. Oldest first. A personal token needs access to the report. Entries appear once they are a minute old, so a collector resuming with created_after cannot skip one that committed late.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/ReportId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Exclusive lower bound; resume a log collector from the last entry it stored."
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Audit trail entries for the report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditLogList"
                },
                "example": {
                  "data": [
                    {
                      "id": "hIjKlm",
                      "action": "whistleblower.report.viewed",
                      "actor": {
                        "kind": "member",
                        "name": "Anna Novak",
                        "membership_id": "nOpQrs"
                      },
                      "subject": {
                        "type": "report",
                        "id": "aBcDef"
                      },
                      "created_at": "2026-11-09T10:00:00Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List a report's audit trail"
      }
    },
    "/organizations/{organization_id}/members.json": {
      "get": {
        "operationId": "listMembers",
        "security": [
          {
            "personalToken": ["members:read"]
          },
          {
            "organizationToken": ["members:read"]
          }
        ],
        "description": "The organisation's members. Every member can list active members; administrators and organisation tokens also see deactivated ones.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Members.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberList"
                },
                "example": {
                  "data": [
                    {
                      "id": "nOpQrs",
                      "name": "Anna Novak",
                      "email": "anna.novak@example.com",
                      "role": "admin",
                      "owner": false,
                      "active": true,
                      "created_at": "2026-10-01T09:00:00Z",
                      "deactivated_at": null
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List members"
      }
    },
    "/organizations/{organization_id}/invitations.json": {
      "get": {
        "operationId": "listInvitations",
        "security": [
          {
            "personalToken": ["members:read"]
          },
          {
            "organizationToken": ["members:read"]
          }
        ],
        "description": "Invitations not yet accepted, declined or expired. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationList"
                },
                "example": {
                  "data": [
                    {
                      "id": "tUvWxy",
                      "email": "new.handler@example.com",
                      "role": "member",
                      "expires_at": "2026-11-23T10:00:00Z",
                      "last_sent_at": "2026-11-09T10:00:00Z",
                      "created_at": "2026-11-09T10:00:00Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List pending invitations"
      },
      "post": {
        "operationId": "createInvitation",
        "security": [
          {
            "personalToken": ["members:write"]
          },
          {
            "organizationToken": ["members:write"]
          }
        ],
        "description": "Email an invitation that is valid for 14 days. The invitee accepts it in EthicsPortal; the API never returns the acceptance link. Up to 30 invitations per organisation per hour. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Invitation sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invitation"
                },
                "example": {
                  "id": "tUvWxy",
                  "email": "new.handler@example.com",
                  "role": "member",
                  "expires_at": "2026-11-23T10:00:00Z",
                  "last_sent_at": "2026-11-09T10:00:00Z",
                  "created_at": "2026-11-09T10:00:00Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation failed; fields are named, values are not echoed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["email"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Invite a member",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvitationInput"
              },
              "example": {
                "invitation": {
                  "email": "new.handler@example.com",
                  "role": "member"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/{organization_id}/invitations/{invitation_id}.json": {
      "delete": {
        "operationId": "revokeInvitation",
        "security": [
          {
            "personalToken": ["members:write"]
          },
          {
            "organizationToken": ["members:write"]
          }
        ],
        "description": "Revoke a pending invitation. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/InvitationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Revoke an invitation"
      }
    },
    "/organizations/{organization_id}/invitations/{invitation_id}/resend.json": {
      "post": {
        "operationId": "resendInvitation",
        "security": [
          {
            "personalToken": ["members:write"]
          },
          {
            "organizationToken": ["members:write"]
          }
        ],
        "description": "Send the invitation email again and restart its 14-day validity. Allowed once every two minutes per invitation; sooner returns 429. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/InvitationId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation resent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invitation"
                },
                "example": {
                  "id": "tUvWxy",
                  "email": "new.handler@example.com",
                  "role": "member",
                  "expires_at": "2026-11-23T10:00:00Z",
                  "last_sent_at": "2026-11-09T10:00:00Z",
                  "created_at": "2026-11-09T10:00:00Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Resend an invitation"
      }
    },
    "/organizations/{organization_id}/profile.json": {
      "patch": {
        "operationId": "updateOrganization",
        "security": [
          {
            "personalToken": ["organizations:write"]
          },
          {
            "organizationToken": ["organizations:write"]
          }
        ],
        "description": "Change the organisation's name. A personal token also needs its user to be a current administrator of the organisation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The renamed organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                },
                "example": {
                  "id": "gHiJkl",
                  "name": "Example organisation"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, body or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation or resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation failed; fields are named, values are not echoed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["name"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Rename the organisation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrganizationUpdateInput"
              },
              "example": {
                "organization": {
                  "name": "Example organisation"
                }
              }
            }
          }
        }
      }
    },
    "/me/invitations.json": {
      "get": {
        "operationId": "listMyInvitations",
        "security": [
          {
            "personalToken": ["invitations:respond"]
          }
        ],
        "description": "Pending invitations addressed to the token's user. The acceptance link is never returned. Personal tokens only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReceivedInvitationList"
                },
                "example": {
                  "data": [
                    {
                      "id": "tUvWxy",
                      "organization": {
                        "id": "gHiJkl",
                        "name": "Example organisation"
                      },
                      "role": "member",
                      "expires_at": "2026-11-23T10:00:00Z",
                      "created_at": "2026-11-09T10:00:00Z",
                      "accept_url": "https://secure.ethicsportal.eu/me/invitations/tUvWxy/accept.json",
                      "decline_url": "https://secure.ethicsportal.eu/me/invitations/tUvWxy/decline.json"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List my invitations"
      }
    },
    "/me/invitations/{invitation_id}/accept.json": {
      "patch": {
        "operationId": "acceptInvitation",
        "security": [
          {
            "personalToken": ["invitations:respond"]
          }
        ],
        "description": "Join the organisation with the invited role. The organisation's own agreements still apply before its data can be read. Personal tokens only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvitationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The new membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                },
                "example": {
                  "id": "nOpQrs",
                  "name": "Anna Novak",
                  "email": "anna.novak@example.com",
                  "role": "member",
                  "owner": false,
                  "active": true,
                  "created_at": "2026-11-09T10:05:00Z",
                  "deactivated_at": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The invitation is no longer valid: expired, already answered, or you are already an active member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invitation_unavailable",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Accept an invitation"
      }
    },
    "/me/invitations/{invitation_id}/decline.json": {
      "patch": {
        "operationId": "declineInvitation",
        "security": [
          {
            "personalToken": ["invitations:respond"]
          }
        ],
        "description": "Decline a pending invitation. Personal tokens only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvitationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Declined."
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The invitation is no longer valid: expired, already answered, or you are already an active member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invitation_unavailable",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Decline an invitation"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents.json": {
      "get": {
        "operationId": "listDocuments",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "The documents the token may download, with their download URLs. A personal token sees compliance evidence only if its user is an administrator or viewer.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Documents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentList"
                },
                "example": {
                  "data": [
                    {
                      "type": "poster",
                      "content_type": "application/pdf",
                      "available": true,
                      "url": "https://secure.ethicsportal.eu/organizations/gHiJkl/whistleblower/documents/poster.pdf"
                    }
                  ],
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "List documents"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents/compliance_report.pdf": {
      "get": {
        "operationId": "downloadComplianceReport",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "Evidence of the channel's configuration and handling, for auditors. Administrators and viewers only. Generated on request from the channel's current settings.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The compliance report as PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The whistleblowing channel is not active yet, so its documents cannot be generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "channel_inactive",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Download the compliance report"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents/compliance_certificate.pdf": {
      "get": {
        "operationId": "downloadComplianceCertificate",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "Certificate of an active whistleblowing channel. Administrators and viewers only. Generated on request from the channel's current settings.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The compliance certificate as PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The whistleblowing channel is not active yet, so its documents cannot be generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "channel_inactive",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Download the compliance certificate"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents/whistleblower_policy.docx": {
      "get": {
        "operationId": "downloadWhistleblowerPolicy",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "Editable policy for staff, filled with the channel's details. Generated on request from the channel's current settings. Any member's personal token can download it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The whistleblowing policy as DOCX.",
            "content": {
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The whistleblowing channel is not active yet, so its documents cannot be generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "channel_inactive",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Download the whistleblowing policy"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents/privacy_notice.docx": {
      "get": {
        "operationId": "downloadPrivacyNotice",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "Editable privacy notice for the whistleblowing channel. Generated on request from the channel's current settings. Any member's personal token can download it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The privacy notice as DOCX.",
            "content": {
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The whistleblowing channel is not active yet, so its documents cannot be generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "channel_inactive",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Download the privacy notice"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents/poster.pdf": {
      "get": {
        "operationId": "downloadPoster",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "Poster with the online reporting form address and QR code. Generated on request from the channel's current settings. Any member's personal token can download it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The poster as PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The whistleblowing channel is not active yet, so its documents cannot be generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "channel_inactive",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Download the poster"
      }
    },
    "/organizations/{organization_id}/whistleblower/documents/case_handler_manual.pdf": {
      "get": {
        "operationId": "downloadCaseHandlerManual",
        "security": [
          {
            "personalToken": ["documents:read"]
          },
          {
            "organizationToken": ["documents:read"]
          }
        ],
        "description": "Manual for the people who handle reports. Generated on request from the channel's current settings. Any member's personal token can download it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The case handler manual as PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The whistleblowing channel is not active yet, so its documents cannot be generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "channel_inactive",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        },
        "summary": "Download the case handler manual"
      }
    },
    "/organizations/{organization_id}/subscriptions.json": {
      "get": {
        "operationId": "getBillingStatus",
        "security": [
          {
            "personalToken": ["billing:read"]
          },
          {
            "organizationToken": ["billing:read"]
          }
        ],
        "description": "Access and subscription status, read from EthicsPortal's synced billing records. Subscribing, changing plan and cancelling stay in the web application. A personal token also needs its user to be a current administrator.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Billing status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingStatus"
                },
                "example": {
                  "access": true,
                  "complimentary": false,
                  "subscription": {
                    "status": "active",
                    "interval": "year",
                    "current_period_end": "2027-11-09T10:00:00Z",
                    "ends_at": null,
                    "trial_ends_at": null
                  }
                }
              }
            },
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            }
          },
          "400": {
            "description": "Invalid query or parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource missing or inaccessible, or API access suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match. No body."
          }
        },
        "summary": "Read billing status"
      }
    },
    "/organizations/{organization_id}/subscriptions/checkout.json": {
      "post": {
        "operationId": "createCheckoutLink",
        "security": [
          {
            "personalToken": ["billing:write"]
          },
          {
            "organizationToken": ["billing:write"]
          }
        ],
        "summary": "Create a subscription checkout link",
        "description": "Return a Stripe Checkout link for the monthly or yearly plan. A person opens it and pays; Stripe handles strong customer authentication, tax and VAT ID collection. Access starts when Stripe confirms the payment, whether or not the payer is signed in to EthicsPortal. Send an Idempotency-Key so a retry returns the same link. A personal token also needs its user to be a current administrator.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutInput"
              },
              "example": {
                "subscription": {
                  "interval": "year"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout link created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingLink"
                },
                "example": {
                  "url": "https://checkout.stripe.com/c/pay/cs_live_example",
                  "expires_at": "2026-11-10T10:00:00Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation missing or inaccessible, API access suspended, or billing not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The organisation already has a subscription (use the billing portal link), or an Idempotency-Key conflict (This Idempotency-Key was already used with a different request, or its first request is still running)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "already_subscribed",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Unknown interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["interval"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/organizations/{organization_id}/subscriptions/billing_portal.json": {
      "post": {
        "operationId": "createBillingPortalLink",
        "security": [
          {
            "personalToken": ["billing:write"]
          },
          {
            "organizationToken": ["billing:write"]
          }
        ],
        "summary": "Create a billing portal link",
        "description": "Return a Stripe customer portal link where a person changes the plan, updates the payment method, downloads invoices or cancels. The link manages the subscription for whoever holds it until it expires, so treat it like a credential; each link is recorded in the audit trail. A personal token also needs its user to be a current administrator.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Billing portal link created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingLink"
                },
                "example": {
                  "url": "https://billing.stripe.com/p/session/live_example",
                  "expires_at": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the required scope (WWW-Authenticate names it), the user's role does not allow the action, or an agreement must be accepted in the web application",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation missing or inaccessible, API access suspended, or billing not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry after the Retry-After interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The organisation has no Stripe customer yet (create a checkout link first), or an Idempotency-Key conflict (This Idempotency-Key was already used with a different request, or its first request is still running)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "no_subscription",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/me/feedback.json": {
      "post": {
        "operationId": "submitFeedback",
        "security": [
          {
            "personalToken": []
          },
          {
            "organizationToken": []
          }
        ],
        "summary": "Send product feedback",
        "description": "File a bug report, feature request or other feedback to EthicsPortal, as the in-app feedback form does. Any valid token may send it, without a scope; every error response links here as `error.feedback_url`. Feedback is attributed to the token's user, or to the organisation token itself. Limited to 10 submissions per user or organisation per minute. Do not include report content or personal data about reporters.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FeedbackInput"
              },
              "example": {
                "feedback": {
                  "kind": "bug",
                  "message": "The yearly report export times out for large organisations."
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Feedback filed for the EthicsPortal team.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Feedback"
                },
                "example": {
                  "kind": "bug",
                  "message": "The yearly report export times out for large organisations.",
                  "status": "open",
                  "created_at": "2026-11-09T10:05:00Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body, attributes or types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unusable token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_token",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required scope missing, a personal token whose user is not a current administrator, or agreement_required until browser acceptance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource inaccessible, or API access suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Model validation failed. Fields are attribute names only; values and personal data are never reflected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "fields": ["message"],
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          },
          "409": {
            "description": "This Idempotency-Key was already used with a different request, or its first request is still running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "feedback_url": "https://secure.ethicsportal.eu/me/feedback.json"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "reportEvent": {
      "post": {
        "operationId": "receiveReportEvent",
        "security": [],
        "description": "EthicsPortal sends a signed POST request to your HTTPS endpoint when a report is created or changes, or when a deadline is close or has passed. The destination must be public. Verify HMAC-SHA256(timestamp + \".\" + raw request body), timestamp freshness and event ID. At-least-once delivery; no ordering guarantee.",
        "parameters": [
          {
            "name": "X-EthicsPortal-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-EthicsPortal-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<Unix seconds>,v1=<hex HMAC-SHA256>`"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/LifecycleEvent"
                  },
                  {
                    "$ref": "#/components/schemas/DeadlineEvent"
                  },
                  {
                    "$ref": "#/components/schemas/TestEvent"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Receipt accepted. Process asynchronously and deduplicate by event ID."
          },
          "default": {
            "description": "Non-2xx responses are retried; redirects are never followed."
          }
        },
        "summary": "Receive a webhook event"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "personalToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Personal token, prefixed ep_pat_. It belongs to a user and acts with that user's current role in every organisation they belong to, so some operations also need a role. Only personal tokens can create organisations, and answer the user's own invitations. Send it as a bearer token. It expires after 90 days and carries fixed scopes chosen at issuance; each operation lists the scope it requires."
      },
      "organizationToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Organisation token, prefixed ep_oat_. It belongs to one organisation, is managed by its administrators and keeps working when staff change. It has no role: its scopes alone decide what it can do. Send it as a bearer token. It expires after 90 days and carries fixed scopes chosen at issuance; each operation lists the scope it requires."
      }
    },
    "parameters": {
      "OrganizationId": {
        "name": "organization_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Obfuscated organisation ID returned by identity."
      },
      "ReportId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Obfuscated report ID; not the reporter access code."
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Follow the Link response header unchanged. Signed cursor expires after one hour."
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "pattern": "^[\\x21-\\x7e]+$"
        },
        "description": "Recommended on every POST. Retrying with the same key and the same request within 24 hours returns the first response (with Idempotent-Replayed: true) instead of acting twice. Only successful responses and validation errors are stored; any other outcome releases the key. Reusing a key with a different request returns 409 idempotency_key_reused; a retry while the first request is still running returns 409 idempotency_key_in_progress. Keys are scoped to the token."
      },
      "WebhookId": {
        "name": "webhook_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "DeliveryId": {
        "name": "delivery_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "InvitationId": {
        "name": "invitation_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "IfNoneMatch": {
        "name": "If-None-Match",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "The ETag of a response you already hold. If nothing changed, the response is 304 Not Modified with no body. It still counts toward rate limits and, where reads are audited, the audit trail."
      }
    },
    "schemas": {
      "ReportSnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "organization_id",
          "status",
          "assigned",
          "created_at",
          "acknowledged_at",
          "feedback_given_at",
          "closed_at",
          "acknowledgment_due_at",
          "feedback_due_at",
          "acknowledgment_overdue",
          "feedback_overdue"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["received", "acknowledged", "feedback_given", "closed"]
          },
          "assigned": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "acknowledged_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "feedback_given_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "closed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "acknowledgment_due_at": {
            "type": "string",
            "format": "date-time"
          },
          "feedback_due_at": {
            "type": "string",
            "format": "date-time"
          },
          "acknowledgment_overdue": {
            "type": "boolean"
          },
          "feedback_overdue": {
            "type": "boolean"
          }
        }
      },
      "Report": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "organization_id",
          "status",
          "assigned",
          "created_at",
          "acknowledged_at",
          "feedback_given_at",
          "closed_at",
          "acknowledgment_due_at",
          "feedback_due_at",
          "acknowledgment_overdue",
          "feedback_overdue",
          "url",
          "web_url",
          "audit_trail_url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["received", "acknowledged", "feedback_given", "closed"]
          },
          "assigned": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "acknowledged_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "feedback_given_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "closed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "acknowledgment_due_at": {
            "type": "string",
            "format": "date-time"
          },
          "feedback_due_at": {
            "type": "string",
            "format": "date-time"
          },
          "acknowledgment_overdue": {
            "type": "boolean"
          },
          "feedback_overdue": {
            "type": "boolean"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "web_url": {
            "type": "string",
            "format": "uri"
          },
          "audit_trail_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Identity": {
        "type": "object",
        "additionalProperties": false,
        "required": ["type", "name", "last_four", "scopes", "expires_at", "last_used_at", "organizations", "next_cursor"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["personal", "organization"],
            "description": "personal: acts as its user, with that user's current role, in each of their organisations. organization: belongs to one organisation and ignores roles; its scopes alone apply."
          },
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "The name given to the token when it was created, as shown on the token list."
          },
          "last_four": {
            "type": "string",
            "minLength": 4,
            "maxLength": 4,
            "description": "The token's last four characters, as shown on the token list."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "reports:read",
                "channel:read",
                "channel:write",
                "webhooks:read",
                "webhooks:write",
                "audit_log:read",
                "members:read",
                "members:write",
                "documents:read",
                "billing:read",
                "billing:write",
                "organizations:write",
                "organizations:create",
                "invitations:respond"
              ]
            },
            "maxItems": 14,
            "uniqueItems": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "When the token was last used before this request, recorded to the minute; null if this is its first use."
          },
          "organizations": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "id",
                "name",
                "reports_url",
                "channel_url",
                "members_url",
                "webhooks_url",
                "audit_trail_url",
                "documents_url",
                "billing_url"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "reports_url": {
                  "type": "string",
                  "format": "uri"
                },
                "channel_url": {
                  "type": "string",
                  "format": "uri"
                },
                "members_url": {
                  "type": "string",
                  "format": "uri"
                },
                "webhooks_url": {
                  "type": "string",
                  "format": "uri"
                },
                "audit_trail_url": {
                  "type": "string",
                  "format": "uri"
                },
                "documents_url": {
                  "type": "string",
                  "format": "uri"
                },
                "billing_url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            },
            "maxItems": 100
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass as the cursor parameter for the next page; null on the last page. The Link header carries the same next page."
          }
        }
      },
      "Channel": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "active", "reporting_form_url", "configuration_url", "documents_url"],
        "properties": {
          "id": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "reporting_form_url": {
            "type": "string",
            "format": "uri"
          },
          "configuration_url": {
            "type": "string",
            "format": "uri"
          },
          "documents_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "feedback_url"],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_token",
                  "forbidden",
                  "insufficient_scope",
                  "not_found",
                  "invalid_request",
                  "agreement_required",
                  "rate_limited",
                  "idempotency_key_reused",
                  "idempotency_key_in_progress",
                  "not_retryable",
                  "channel_inactive",
                  "invitation_unavailable",
                  "already_subscribed",
                  "no_subscription"
                ]
              },
              "feedback_url": {
                "type": "string",
                "format": "uri",
                "description": "Where to tell EthicsPortal what went wrong: POST product feedback here with the same token."
              }
            }
          }
        }
      },
      "LifecycleEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "type", "created_at", "data"],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "report.created",
              "report.acknowledged",
              "report.feedback_given",
              "report.closed",
              "report.reopened",
              "report.assignment_changed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/ReportSnapshot"
          }
        },
        "title": "Report lifecycle event"
      },
      "DeadlineEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "type", "created_at", "data"],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": ["report.due_soon", "report.overdue"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["report_id", "organization_id", "deadline_type", "deadline_at"],
            "properties": {
              "report_id": {
                "type": "string"
              },
              "organization_id": {
                "type": "string"
              },
              "deadline_type": {
                "type": "string",
                "enum": ["acknowledgment", "feedback"]
              },
              "deadline_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        },
        "title": "Deadline event"
      },
      "TestEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "type", "created_at", "data"],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": ["webhook.test"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["organization_id"],
            "properties": {
              "organization_id": {
                "type": "string"
              }
            }
          }
        },
        "title": "Test event"
      },
      "OrganizationInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["organization"],
        "properties": {
          "organization": {
            "type": "object",
            "additionalProperties": false,
            "required": ["name"],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              }
            }
          }
        }
      },
      "CreatedOrganization": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name",
          "membership_id",
          "channel",
          "reports_url",
          "channel_url",
          "members_url",
          "webhooks_url",
          "audit_trail_url",
          "documents_url",
          "billing_url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "membership_id": {
            "type": "string",
            "description": "The creating user’s administrator membership, as an obfuscated ID. Use as compliance_officer_id to appoint yourself."
          },
          "channel": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "active"],
            "properties": {
              "id": {
                "type": "string"
              },
              "active": {
                "type": "boolean"
              }
            }
          },
          "reports_url": {
            "type": "string",
            "format": "uri"
          },
          "channel_url": {
            "type": "string",
            "format": "uri"
          },
          "members_url": {
            "type": "string",
            "format": "uri"
          },
          "webhooks_url": {
            "type": "string",
            "format": "uri"
          },
          "audit_trail_url": {
            "type": "string",
            "format": "uri"
          },
          "documents_url": {
            "type": "string",
            "format": "uri"
          },
          "billing_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ChannelConfigurationInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["portal"],
        "properties": {
          "portal": {
            "type": "object",
            "additionalProperties": false,
            "minProperties": 1,
            "properties": {
              "legal_name": {
                "type": ["string", "null"],
                "maxLength": 255
              },
              "country_code": {
                "type": ["string", "null"],
                "description": "A supported ISO 3166-1 alpha-2 country code. Country-specific retention minimums apply."
              },
              "registration_number": {
                "type": ["string", "null"]
              },
              "address": {
                "type": ["string", "null"]
              },
              "employee_range": {
                "type": ["string", "null"],
                "enum": ["1-49", "50-249", "250+", null, ""]
              },
              "industry": {
                "type": ["string", "null"],
                "enum": [
                  "agriculture_forestry",
                  "mining_quarrying",
                  "manufacturing",
                  "energy_utilities",
                  "water_waste_management",
                  "construction",
                  "wholesale_retail_trade",
                  "transportation_logistics",
                  "hospitality_food_services",
                  "information_communication",
                  "financial_services_insurance",
                  "real_estate",
                  "professional_scientific_services",
                  "administrative_support_services",
                  "public_administration_defence",
                  "education",
                  "healthcare_social_work",
                  "arts_entertainment_recreation",
                  "other",
                  null,
                  ""
                ]
              },
              "website": {
                "type": ["string", "null"]
              },
              "phone_number": {
                "type": ["string", "null"]
              },
              "compliance_officer_id": {
                "type": ["string", "null"],
                "description": "An active administrator membership in this organisation, identified by its obfuscated ID. Creation returns your membership_id. Null removes the appointment and can make the channel inactive. Raw database IDs are rejected."
              },
              "brand_color": {
                "type": ["string", "null"]
              },
              "retention_months": {
                "type": "integer",
                "enum": [12, 24, 36, 48, 60],
                "description": "Must also meet the selected country’s minimum. Defaults to 60 months. A country change and its retention adjustment are validated together."
              },
              "social_dialogue_consulted": {
                "type": "boolean"
              },
              "voice_recording_enabled": {
                "type": "boolean"
              },
              "email_intake_enabled": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "ChannelConfiguration": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "legal_name",
          "country_code",
          "registration_number",
          "address",
          "employee_range",
          "industry",
          "website",
          "phone_number",
          "compliance_officer_id",
          "brand_color",
          "retention_months",
          "social_dialogue_consulted",
          "voice_recording_enabled",
          "email_intake_enabled",
          "id",
          "active",
          "verified",
          "reporting_form_url",
          "missing_fields"
        ],
        "properties": {
          "legal_name": {
            "type": ["string", "null"],
            "maxLength": 255
          },
          "country_code": {
            "type": ["string", "null"],
            "description": "A supported ISO 3166-1 alpha-2 country code. Country-specific retention minimums apply."
          },
          "registration_number": {
            "type": ["string", "null"]
          },
          "address": {
            "type": ["string", "null"]
          },
          "employee_range": {
            "type": ["string", "null"],
            "enum": ["1-49", "50-249", "250+", null, ""]
          },
          "industry": {
            "type": ["string", "null"],
            "enum": [
              "agriculture_forestry",
              "mining_quarrying",
              "manufacturing",
              "energy_utilities",
              "water_waste_management",
              "construction",
              "wholesale_retail_trade",
              "transportation_logistics",
              "hospitality_food_services",
              "information_communication",
              "financial_services_insurance",
              "real_estate",
              "professional_scientific_services",
              "administrative_support_services",
              "public_administration_defence",
              "education",
              "healthcare_social_work",
              "arts_entertainment_recreation",
              "other",
              null,
              ""
            ]
          },
          "website": {
            "type": ["string", "null"]
          },
          "phone_number": {
            "type": ["string", "null"]
          },
          "compliance_officer_id": {
            "type": ["string", "null"],
            "description": "An active administrator membership in this organisation, identified by its obfuscated ID. Creation returns your membership_id. Null removes the appointment and can make the channel inactive. Raw database IDs are rejected."
          },
          "brand_color": {
            "type": ["string", "null"]
          },
          "retention_months": {
            "type": "integer",
            "enum": [12, 24, 36, 48, 60],
            "description": "Must also meet the selected country’s minimum. Defaults to 60 months. A country change and its retention adjustment are validated together."
          },
          "social_dialogue_consulted": {
            "type": "boolean"
          },
          "voice_recording_enabled": {
            "type": "boolean"
          },
          "email_intake_enabled": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "verified": {
            "type": "boolean"
          },
          "reporting_form_url": {
            "type": "string",
            "format": "uri"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["legal_name", "country_code", "registration_number", "address", "employee_range", "industry", "compliance_officer_id"]
            },
            "uniqueItems": true
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "fields", "feedback_url"],
            "properties": {
              "code": {
                "const": "validation_failed"
              },
              "fields": {
                "type": "array",
                "minItems": 1,
                "uniqueItems": true,
                "items": {
                  "type": "string"
                }
              },
              "feedback_url": {
                "type": "string",
                "format": "uri",
                "description": "Where to tell EthicsPortal what went wrong: POST product feedback here with the same token."
              }
            }
          }
        }
      },
      "ReportList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Report"
            }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass as the cursor parameter for the next page; null on the last page. The Link header carries the same next page."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "name", "url", "enabled", "events", "failing_since", "created_at", "updated_at", "deliveries_url"],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The destination EthicsPortal sends events to, as in Stripe's API. Other *_url fields are links within this API."
          },
          "enabled": {
            "type": "boolean"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "report.created",
                "report.acknowledged",
                "report.feedback_given",
                "report.closed",
                "report.reopened",
                "report.assignment_changed",
                "report.due_soon",
                "report.overdue"
              ]
            }
          },
          "failing_since": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Set when a delivery failed permanently or exhausted its retries; cleared by a later success or a configuration change."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deliveries_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "WebhookWithSecret": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "name", "url", "enabled", "events", "failing_since", "created_at", "updated_at", "secret", "deliveries_url"],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The destination EthicsPortal sends events to, as in Stripe's API. Other *_url fields are links within this API."
          },
          "enabled": {
            "type": "boolean"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "report.created",
                "report.acknowledged",
                "report.feedback_given",
                "report.closed",
                "report.reopened",
                "report.assignment_changed",
                "report.due_soon",
                "report.overdue"
              ]
            }
          },
          "failing_since": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Set when a delivery failed permanently or exhausted its retries; cleared by a later success or a configuration change."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "secret": {
            "type": "string",
            "description": "Signing secret. Returned only when created or rotated; store it immediately."
          },
          "deliveries_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "next_cursor": {
            "type": "null"
          }
        }
      },
      "WebhookCreateInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["webhook"],
        "properties": {
          "webhook": {
            "type": "object",
            "additionalProperties": false,
            "required": ["name", "url", "events"],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "url": {
                "type": "string",
                "description": "Public HTTPS URL on port 443. Redirects are not followed."
              },
              "enabled": {
                "type": "boolean"
              },
              "events": {
                "type": "array",
                "minItems": 1,
                "uniqueItems": true,
                "items": {
                  "type": "string",
                  "enum": [
                    "report.created",
                    "report.acknowledged",
                    "report.feedback_given",
                    "report.closed",
                    "report.reopened",
                    "report.assignment_changed",
                    "report.due_soon",
                    "report.overdue"
                  ]
                }
              }
            }
          }
        }
      },
      "WebhookUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["webhook"],
        "properties": {
          "webhook": {
            "type": "object",
            "additionalProperties": false,
            "minProperties": 1,
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "url": {
                "type": "string",
                "description": "Public HTTPS URL on port 443. Redirects are not followed."
              },
              "enabled": {
                "type": "boolean"
              },
              "events": {
                "type": "array",
                "minItems": 1,
                "uniqueItems": true,
                "items": {
                  "type": "string",
                  "enum": [
                    "report.created",
                    "report.acknowledged",
                    "report.feedback_given",
                    "report.closed",
                    "report.reopened",
                    "report.assignment_changed",
                    "report.due_soon",
                    "report.overdue"
                  ]
                }
              }
            }
          }
        }
      },
      "Delivery": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "event_id",
          "event_type",
          "status",
          "attempts",
          "response_code",
          "error_code",
          "created_at",
          "delivered_at",
          "next_attempt_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "event_id": {
            "type": "string",
            "description": "Matches the payload id and the X-EthicsPortal-Event-Id header."
          },
          "event_type": {
            "type": "string",
            "enum": [
              "report.created",
              "report.acknowledged",
              "report.feedback_given",
              "report.closed",
              "report.reopened",
              "report.assignment_changed",
              "report.due_soon",
              "report.overdue",
              "webhook.test"
            ]
          },
          "status": {
            "type": "string",
            "enum": ["pending", "delivering", "delivered", "failed", "cancelled"]
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "response_code": {
            "type": ["integer", "null"]
          },
          "error_code": {
            "type": ["string", "null"],
            "enum": ["connection_failed", "unsafe_destination", null]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "delivered_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "next_attempt_at": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Earliest time of the next scheduled attempt for a pending delivery of the current configuration; queue delays can postpone it."
          }
        }
      },
      "DeliveryList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Delivery"
            }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass as the cursor parameter for the next page; null on the last page. The Link header carries the same next page."
          }
        }
      },
      "AuditLogEntry": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "action", "actor", "subject", "created_at"],
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "description": "Stable action name, such as whistleblower.report.viewed or invitation.created."
          },
          "actor": {
            "type": "object",
            "additionalProperties": false,
            "required": ["kind", "name", "membership_id"],
            "properties": {
              "kind": {
                "type": "string",
                "enum": ["member", "system", "scim", "sso", "api", "mcp", "admin"]
              },
              "name": {
                "type": ["string", "null"]
              },
              "membership_id": {
                "type": ["string", "null"]
              }
            }
          },
          "subject": {
            "type": ["object", "null"],
            "additionalProperties": false,
            "required": ["type", "id"],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "report",
                  "channel",
                  "organization",
                  "membership",
                  "invitation",
                  "webhook",
                  "organization_token",
                  "personal_token",
                  "data_export",
                  "other"
                ]
              },
              "id": {
                "type": "string"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AuditLogList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/AuditLogEntry"
            }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass as the cursor parameter for the next page; null on the last page. The Link header carries the same next page."
          }
        }
      },
      "Member": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "name", "email", "role", "owner", "active", "created_at", "deactivated_at"],
        "properties": {
          "id": {
            "type": "string",
            "description": "Membership ID; use as compliance_officer_id when the member is an administrator."
          },
          "name": {
            "type": ["string", "null"]
          },
          "email": {
            "type": ["string", "null"],
            "description": "Null for an erased account."
          },
          "role": {
            "type": "string",
            "enum": ["admin", "member", "viewer"]
          },
          "owner": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "deactivated_at": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      },
      "MemberList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Member"
            }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass as the cursor parameter for the next page; null on the last page. The Link header carries the same next page."
          }
        }
      },
      "Invitation": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "email", "role", "expires_at", "last_sent_at", "created_at"],
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": ["admin", "member", "viewer"]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_sent_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvitationList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Invitation"
            }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass as the cursor parameter for the next page; null on the last page. The Link header carries the same next page."
          }
        }
      },
      "InvitationInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["invitation"],
        "properties": {
          "invitation": {
            "type": "object",
            "additionalProperties": false,
            "required": ["email", "role"],
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "role": {
                "type": "string",
                "enum": ["admin", "member", "viewer"]
              }
            }
          }
        }
      },
      "OrganizationUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["organization"],
        "properties": {
          "organization": {
            "type": "object",
            "additionalProperties": false,
            "required": ["name"],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        }
      },
      "Organization": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "name"],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "ReceivedInvitation": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "organization", "role", "expires_at", "created_at", "accept_url", "decline_url"],
        "properties": {
          "id": {
            "type": "string"
          },
          "organization": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "name"],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "role": {
            "type": "string",
            "enum": ["admin", "member", "viewer"]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "accept_url": {
            "type": "string",
            "format": "uri"
          },
          "decline_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ReceivedInvitationList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReceivedInvitation"
            }
          },
          "next_cursor": {
            "type": "null"
          }
        }
      },
      "Document": {
        "type": "object",
        "additionalProperties": false,
        "required": ["type", "content_type", "available", "url"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["compliance_report", "compliance_certificate", "whistleblower_policy", "privacy_notice", "poster", "case_handler_manual"]
          },
          "content_type": {
            "type": "string",
            "enum": ["application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]
          },
          "available": {
            "type": "boolean",
            "description": "False while the whistleblowing channel is inactive; downloads then return 409 channel_inactive."
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "DocumentList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["data", "next_cursor"],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 6,
            "items": {
              "$ref": "#/components/schemas/Document"
            }
          },
          "next_cursor": {
            "type": "null"
          }
        }
      },
      "BillingStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": ["access", "complimentary", "subscription"],
        "properties": {
          "access": {
            "type": "boolean",
            "description": "Whether the organisation currently has paid or granted access. The whistleblowing channel can be active only when this is true."
          },
          "complimentary": {
            "type": "boolean",
            "description": "Access granted by EthicsPortal without a subscription."
          },
          "subscription": {
            "type": ["object", "null"],
            "additionalProperties": false,
            "required": ["status", "interval", "current_period_end", "ends_at", "trial_ends_at"],
            "properties": {
              "status": {
                "type": "string",
                "description": "Stripe subscription status, such as active, trialing, past_due or canceled."
              },
              "interval": {
                "type": ["string", "null"],
                "enum": ["month", "year", null]
              },
              "current_period_end": {
                "type": ["string", "null"],
                "format": "date-time"
              },
              "ends_at": {
                "type": ["string", "null"],
                "format": "date-time",
                "description": "Set when the subscription has been cancelled and access ends at this time."
              },
              "trial_ends_at": {
                "type": ["string", "null"],
                "format": "date-time"
              }
            }
          }
        }
      },
      "BillingLink": {
        "type": "object",
        "additionalProperties": false,
        "required": ["url", "expires_at"],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Stripe-hosted page. Give it to the person who pays or manages billing; it works for whoever holds it until it expires."
          },
          "expires_at": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "When the link stops working. Null for billing portal links, which expire shortly after creation; create one just before use."
          }
        }
      },
      "CheckoutInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["subscription"],
        "properties": {
          "subscription": {
            "type": "object",
            "additionalProperties": false,
            "required": ["interval"],
            "properties": {
              "interval": {
                "type": "string",
                "enum": ["month", "year"]
              }
            }
          }
        }
      },
      "FeedbackInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["feedback"],
        "properties": {
          "feedback": {
            "type": "object",
            "additionalProperties": false,
            "required": ["message"],
            "properties": {
              "kind": {
                "type": "string",
                "enum": ["bug", "feature", "other"],
                "default": "other"
              },
              "message": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        }
      },
      "Feedback": {
        "type": "object",
        "additionalProperties": false,
        "required": ["kind", "message", "status", "created_at"],
        "properties": {
          "kind": {
            "type": "string",
            "enum": ["bug", "feature", "other"]
          },
          "message": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["open", "in_review", "resolved"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "headers": {
      "ETag": {
        "description": "Weak validator of this response body. Send it back as If-None-Match.",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}
