{
  "openapi": "3.0.3",
  "info": {
    "title": "Woku Client API v1",
    "description": "Public Woku Client API. Switch the version selector at the top to view the v0 reference.\n\nThis spec covers:\n- **Wokus**: create wokus, fetch review data, submit text/voice reviews, share by email.\n- **Companies**: `GET /companies/me` returns the caller company.\n- **Reports**: company-wide and per-tool NPS reports.\n- **External Trackers**: tag your Wokus with identifiers from third-party systems (CRM transaction id, ERP order id, etc.) and look them up later.\n- **Ticket Destinations**: configure the SAC platforms (Zendesk, Salesforce, Slack), custom HTTP service or email addresses that receive AI-triaged support tickets, and the tracker conditions that route a ticket to one.\n- **Action Plan Groups**: define which VoC feedback (tracker conditions) drafts an improvement plan and who is responsible.\n- **Action Plans**: read AI-drafted plans and drive their lifecycle (approve, send to an external tool or work them in woku's managed kanban).\n\n## Authentication\n\nAll endpoints require a Bearer token in the `Authorization` header. Obtain your company API key from the Woku dashboard.\n\n```\nAuthorization: Bearer your_api_key_here\n```\n\n## Base URL\n\nProduction: `https://clientapi.woku.app`\n\n## Model (External Trackers)\n\n- **Tracker definition** (`ExternalTracker`): a per-company catalog entry like `{ name: 'trr', system: 'crm interno', description: '...' }`. Defined by an admin in the Woku dashboard.\n- **Tracker value** (`WokuExternalTrackerValue`): a string value bound to a (Woku, Tracker) pair. Multiple Wokus may share the same value.\n\n## Model (routing / group conditions)\n\n`TicketRoutingCondition` (Ticket Destinations) and `ActionPlanGroupCondition` (Action Plan Groups) share the same shape: a boolean expression of rows over `ExternalTracker` values, AND binding tighter than OR. Each row matches when the feedback carries `trackerId` with a value that, for operator `equals`, equals `value`, or, for operator `any`, is any value at all (the whole tracker; `value` is then omitted). `relationToPrevious` joins a row to the previous one (`AND` same group, `OR` starts a new group); the first row omits it.",
    "version": "1.0.0",
    "contact": {
      "name": "Woku Support",
      "url": "https://woku.app",
      "email": "team@woku.app"
    }
  },
  "servers": [
    {
      "url": "https://clientapi.woku.app",
      "description": "Production server"
    }
  ],
  "tags": [
    {
      "name": "Wokus",
      "description": "Create wokus, fetch review data, submit text and voice reviews, share review links by email."
    },
    {
      "name": "Companies",
      "description": "Caller-company endpoint."
    },
    {
      "name": "Reports",
      "description": "NPS reports for the caller company and individual NPS tools."
    },
    {
      "name": "NPS",
      "description": "Capture NPS scores (company-level or per NPS tool) and fetch NPS tool definitions."
    },
    {
      "name": "CSAT",
      "description": "Capture CSAT (1-5 satisfaction) scores company-level or per CSAT tool, fetch tool definitions and responses, and submit text and voice feedback."
    },
    {
      "name": "CES",
      "description": "Capture CES (effort) scores company-level or per CES tool, fetch tool definitions and responses, and submit text and voice feedback."
    },
    {
      "name": "External Trackers",
      "description": "Tag Wokus with identifiers from external systems and look them up by name + value."
    },
    {
      "name": "Quarantines",
      "description": "Check whether a respondent is currently quarantined before prompting for feedback."
    },
    {
      "name": "Invitations",
      "description": "Survey distribution: send NPS surveys, forms and woku review invitations by email or WhatsApp."
    },
    {
      "name": "Forms",
      "description": "Fetch form definitions and submit responses from your own UI."
    },
    {
      "name": "Flows",
      "description": "Render flow journeys (ordered wokus with an optional NPS) in your own app."
    },
    {
      "name": "Captures",
      "description": "Mobile SDK capture ingestion"
    },
    {
      "name": "Ticket Destinations",
      "description": "Configure where AI-triaged support tickets are delivered (Zendesk, Salesforce, Slack, a custom HTTP service, or plain email) and the tracker-based routing that picks a destination."
    },
    {
      "name": "Action Plan Groups",
      "description": "Define which VoC feedback (tracker conditions) drafts an action plan and who the responsibles are."
    },
    {
      "name": "Action Plans",
      "description": "Read AI-drafted improvement plans and drive their lifecycle: approve, send to Jira/monday.com/ClickUp/Notion or work them inside woku's managed kanban."
    }
  ],
  "paths": {
    "/v1/wokus": {
      "post": {
        "tags": [
          "Wokus"
        ],
        "summary": "Create a Woku with a file URL",
        "description": "Creates a Woku for feedback collection using a remote file URL. The file is downloaded and processed asynchronously.",
        "operationId": "v1CreateWoku",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateWokuRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Woku created"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Wokus"
        ],
        "summary": "List the wokus of the caller company",
        "operationId": "v1ListWokus",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated wokus",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1WokuSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/form-data": {
      "post": {
        "tags": [
          "Wokus"
        ],
        "summary": "Create a Woku with a file upload (multipart)",
        "description": "Creates a Woku and uploads its asset in a single multipart request.",
        "operationId": "v1CreateWokuFormData",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateWokuFormDataRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Woku created"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/{wokuId}/review": {
      "get": {
        "tags": [
          "Wokus"
        ],
        "summary": "Get review data for a Woku",
        "description": "Returns the review payload for the given Woku: company info, woku details, and any existing reviews.",
        "operationId": "v1GetWokuReview",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Woku review payload"
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Woku not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/{wokuId}/textnotes": {
      "post": {
        "tags": [
          "Wokus"
        ],
        "summary": "Create a text review for a Woku",
        "description": "Submits a text review (star rating + text feedback) for a Woku.",
        "operationId": "v1CreateTextnote",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateTextnoteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Textnote created"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Woku not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/{wokuId}/voicemails": {
      "post": {
        "tags": [
          "Wokus"
        ],
        "summary": "Create a voice review for a Woku (multipart)",
        "description": "Submits a voice review for a Woku. The audio file is uploaded as `file` in a multipart request alongside the rating.",
        "operationId": "v1CreateVoicemail",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateVoicemailRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Voicemail created"
          },
          "400": {
            "description": "Validation error or missing audio file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Woku not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/{wokuId}/share": {
      "post": {
        "tags": [
          "Wokus"
        ],
        "summary": "Share a Woku review link via email",
        "description": "Sends the Woku review link to one or more email addresses inviting recipients to leave feedback.",
        "operationId": "v1ShareWokuToEmail",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1ShareWokuRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email(s) sent"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/companies/me": {
      "get": {
        "tags": [
          "Companies"
        ],
        "summary": "Get the caller company",
        "description": "Returns company info, folders and wokus for the company resolved from the Bearer key.",
        "operationId": "v1GetCompanyMe",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Company data retrieved"
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/reports/company-nps": {
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "Get the company-wide NPS report",
        "description": "Returns the aggregated NPS report for the company resolved from the Bearer key.",
        "operationId": "v1GetCompanyNpsReport",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Company NPS report"
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/reports/nps-tool/{npsToolId}": {
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "Get the NPS report for a specific NPS tool",
        "description": "Returns the NPS report for the given tool. Fails with 400 if the tool does not belong to the caller company.",
        "operationId": "v1GetNpsToolReport",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "npsToolId",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the NPS tool.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439011"
          }
        ],
        "responses": {
          "200": {
            "description": "NPS tool report"
          },
          "400": {
            "description": "NPS tool does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "NPS tool or report not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/external-trackers": {
      "get": {
        "tags": [
          "External Trackers"
        ],
        "summary": "List active tracker definitions of the caller company",
        "description": "Returns the paginated catalog of **active** tracker definitions belonging to the company resolved from the Bearer token. Inactive (soft-deleted) trackers are not returned here.",
        "operationId": "listExternalTrackers",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of active trackers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedExternalTrackers"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/external-trackers/wokus/{wokuId}": {
      "get": {
        "tags": [
          "External Trackers"
        ],
        "summary": "List tracker values assigned to a Woku",
        "description": "Returns every tracker value currently bound to the given Woku, scoped to the caller's company.",
        "operationId": "listWokuExternalTrackers",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of values (may be empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WokuExternalTrackerValue"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "External Trackers"
        ],
        "summary": "Assign a tracker value to a Woku by tracker name (upsert)",
        "description": "Idempotent. If `(wokuId, trackerName)` already has a value, it is **overwritten**. The server resolves the tracker by name within the caller's company.\n\nFails with 404 if the tracker name is unknown to the company, 400 if the tracker is inactive, 403 if the Woku belongs to another company.",
        "operationId": "assignWokuExternalTracker",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignExternalTrackerRequest"
              },
              "examples": {
                "trr": {
                  "summary": "Tag a Woku with a CRM transaction id",
                  "value": {
                    "name": "trr",
                    "value": "TX-2026-00043"
                  }
                },
                "sku": {
                  "summary": "Tag a Woku with a product SKU",
                  "value": {
                    "name": "sku",
                    "value": "WK-PRO-2024-BLUE"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Value assigned (created) or updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WokuExternalTrackerValue"
                }
              }
            }
          },
          "400": {
            "description": "Validation error or tracker inactive",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Woku does not belong to the caller's company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Tracker name not found or Woku not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/external-trackers/wokus/{wokuId}/{trackerName}": {
      "delete": {
        "tags": [
          "External Trackers"
        ],
        "summary": "Remove a tracker value from a Woku",
        "description": "Looks up the tracker by name (active or inactive) and removes its value from the Woku. Returns the removed document. **Not idempotent**: returns 404 if no value existed.",
        "operationId": "removeWokuExternalTracker",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WokuIdPath"
          },
          {
            "name": "trackerName",
            "in": "path",
            "required": true,
            "description": "Name of the company-level tracker definition.",
            "schema": {
              "type": "string"
            },
            "example": "trr"
          }
        ],
        "responses": {
          "200": {
            "description": "Value removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WokuExternalTrackerValue"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Tracker name not found, or Woku has no value for that tracker",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/external-trackers/search": {
      "get": {
        "tags": [
          "External Trackers"
        ],
        "summary": "Search Wokus by (tracker name, value)",
        "description": "Returns paginated `WokuExternalTrackerValue` entries whose tracker name + value match within the caller's company. Use `.wokuId` from each result to fetch the full Woku via the v0 endpoints.\n\nLookup includes inactive trackers (historical values remain queryable).",
        "operationId": "searchWokusByTracker",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "description": "External tracker name (e.g. `trr`).",
            "schema": {
              "type": "string"
            },
            "example": "trr"
          },
          {
            "name": "value",
            "in": "query",
            "required": true,
            "description": "Exact value to match.",
            "schema": {
              "type": "string"
            },
            "example": "TX-2026-00043"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of matches",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedWokuExternalTrackerValues"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Tracker name not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps-tool/{id}": {
      "get": {
        "tags": [
          "NPS"
        ],
        "summary": "Get an NPS tool definition",
        "description": "Returns the definition of an NPS tool: its internal name and the message and audience used to build the survey question (\"On a 0 to 10 scale, how likely are you to recommend {npsMessage} to {audienceType}?\"). Fails with 403 if the tool does not belong to the caller company.",
        "operationId": "v1GetNpsTool",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the NPS tool.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439011"
          }
        ],
        "responses": {
          "200": {
            "description": "NPS tool definition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1NpsToolResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or tool belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "NPS tool not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps": {
      "post": {
        "tags": [
          "NPS"
        ],
        "summary": "Capture an NPS score",
        "description": "Submits an NPS score (0 to 10) for the caller company. Provide `npsToolId` to attach the score to a specific NPS tool; omit it for a company-level score. Optionally include the respondent email, or mark the submission anonymous. The detractor (0 to 6) / passive (7 to 8) / promoter (9 to 10) category is derived from the score by Woku.",
        "operationId": "v1CreateNps",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateNpsRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "NPS score created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CreateNpsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or tool belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "NPS"
        ],
        "summary": "List the NPS responses of the caller company",
        "description": "Paginated, most recent first. Filter by tool, date range and whether the response carries feedback. Requires the secret key.",
        "operationId": "v1ListNps",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "npsToolId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive lower bound on createdAt."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive upper bound on createdAt."
          },
          {
            "name": "withFeedback",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "true returns only responses with a text or voice comment."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated NPS responses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1NpsResponse"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps/{id}/textnotes": {
      "post": {
        "tags": [
          "NPS"
        ],
        "summary": "Add a text review to an NPS score",
        "description": "Attaches an optional text review to an existing NPS score. Fails with 403 if the NPS does not belong to the caller company.",
        "operationId": "v1AddNpsTextnote",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the NPS score.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateNpsTextnoteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Text review added"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or NPS belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "NPS not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps/{id}/voicemails": {
      "post": {
        "tags": [
          "NPS"
        ],
        "summary": "Add a voice review to an NPS score",
        "description": "Attaches an optional voice review (audio) to an existing NPS score. The audio is uploaded and transcribed server-side. Fails with 403 if the NPS does not belong to the caller company.",
        "operationId": "v1AddNpsVoicemail",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the NPS score.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Audio file (m4a/aac/mp4)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Voice review added"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or NPS belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "NPS not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quarantines/check": {
      "get": {
        "tags": [
          "Quarantines"
        ],
        "summary": "Check whether a respondent is currently quarantined",
        "description": "Read-only (dry-run) check: tells whether a respondent, identified by email or phone, is currently quarantined for the caller company, so a client can decide whether to prompt for feedback. Records nothing.",
        "operationId": "v1CheckQuarantine",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Respondent email (provide email or phone).",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "description": "Respondent phone (provide email or phone).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Quarantine status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1QuarantineCheckResponse"
                }
              }
            }
          },
          "400": {
            "description": "email or phone is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps/invitations": {
      "post": {
        "tags": [
          "Invitations"
        ],
        "summary": "Send the NPS survey by email or WhatsApp",
        "description": "Dispatches the NPS survey (company-level, or tool-specific via `npsToolId`) to a list of recipients. The response reports per-recipient acceptance: quarantined respondents and recipients blocked by WhatsApp credits or notification policy are listed in `rejected` instead of failing the request. WhatsApp sends consume one WhatsApp credit per recipient.",
        "operationId": "v1SendNpsInvitations",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1NpsInvitationsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Per-recipient dispatch summary. Sends are asynchronous.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1InvitationsResult"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/forms/{id}/invitations": {
      "post": {
        "tags": [
          "Invitations"
        ],
        "summary": "Send a form by email or WhatsApp",
        "description": "Dispatches the form invitation to a list of recipients. The form must belong to the caller company and be open and active. Same per-recipient semantics as the NPS invitations endpoint.",
        "operationId": "v1SendFormInvitations",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1InvitationsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Per-recipient dispatch summary. Sends are asynchronous.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1InvitationsResult"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Form id"
          }
        ]
      }
    },
    "/v1/wokus/{id}/invitations": {
      "post": {
        "tags": [
          "Invitations"
        ],
        "summary": "Send a woku review invitation by email or WhatsApp",
        "description": "Dispatches the woku review invitation to a list of recipients. The woku must belong to the caller company, be open and accept reviews. For the email channel this is equivalent to `POST /v1/wokus/{wokuId}/share`. Same per-recipient semantics as the NPS invitations endpoint.",
        "operationId": "v1SendWokuInvitations",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1InvitationsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Per-recipient dispatch summary. Sends are asynchronous.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1InvitationsResult"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Woku id"
          }
        ]
      }
    },
    "/v1/forms/{id}": {
      "get": {
        "tags": [
          "Forms"
        ],
        "summary": "Get a form definition (to render the form)",
        "description": "Returns the active form (fields, settings, localized content) plus company branding, ready to render in your own UI. Accepts the publishable capture key or the secret key.",
        "operationId": "v1GetForm",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Form id"
          }
        ],
        "responses": {
          "200": {
            "description": "Form definition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1FormDefinitionResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid key, or the form does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Form not found or inactive",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/forms/{id}/responses": {
      "post": {
        "tags": [
          "Forms"
        ],
        "summary": "Submit a form response",
        "description": "Records a response for the form. Answers are validated against the form definition before being stored; quarantined respondents are rejected with 429. Accepts the publishable capture key or the secret key.",
        "operationId": "v1CreateFormResponse",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Form id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateFormResponseRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Form response created"
          },
          "400": {
            "description": "Validation error, or the form is closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid key, or the form does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Form not found or inactive",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "429": {
            "description": "Respondent is quarantined"
          }
        }
      },
      "get": {
        "tags": [
          "Forms"
        ],
        "summary": "List the responses of a form",
        "description": "Paginated, most recent first. Responses expose answers keyed by field id; client contact data is never included. Requires the secret key.",
        "operationId": "v1ListFormResponses",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Form id"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated form responses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1FormResponse"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/flows/{id}": {
      "get": {
        "tags": [
          "Flows"
        ],
        "summary": "Get the data of a flow",
        "description": "Returns the flow journey: wokus in presentation order, company branding and the linked NPS (tool-specific or company-level). Use the existing capture endpoints to record feedback for each step. Accepts the publishable capture key or the secret key.",
        "operationId": "v1GetFlow",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Flow id"
          }
        ],
        "responses": {
          "200": {
            "description": "Flow data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1FlowDataResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid key, or the flow does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Flow not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps-tools": {
      "get": {
        "tags": [
          "NPS"
        ],
        "summary": "List the NPS tools of the caller company",
        "description": "Canonical plural form; `GET /v1/nps-tool/{id}` remains as the single-tool fetch. Requires the secret key.",
        "operationId": "v1ListNpsTools",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated NPS tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1NpsToolSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/nps/{id}": {
      "get": {
        "tags": [
          "NPS"
        ],
        "summary": "Get a single NPS response",
        "operationId": "v1GetNps",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "NPS response id"
          }
        ],
        "responses": {
          "200": {
            "description": "NPS response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1NpsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/forms": {
      "get": {
        "tags": [
          "Forms"
        ],
        "summary": "List the forms of the caller company",
        "operationId": "v1ListForms",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated forms",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1FormSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/{id}": {
      "get": {
        "tags": [
          "Wokus"
        ],
        "summary": "Get a woku with aggregated review stats",
        "operationId": "v1GetWoku",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Woku id"
          }
        ],
        "responses": {
          "200": {
            "description": "Woku with stats",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1WokuWithStats"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wokus/{id}/reviews": {
      "get": {
        "tags": [
          "Wokus"
        ],
        "summary": "List the reviews of a woku (text and voice)",
        "description": "Paginated, most recent first. Voice reviews expose the transcription. Requires the secret key.",
        "operationId": "v1ListWokuReviews",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Woku id"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated reviews",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1WokuReview"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/flows": {
      "get": {
        "tags": [
          "Flows"
        ],
        "summary": "List the flows of the caller company",
        "operationId": "v1ListFlows",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated flows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1FlowSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/captures": {
      "post": {
        "tags": [
          "Captures"
        ],
        "summary": "Capture a woku review or NPS response (mobile SDK)",
        "description": "Ingestion endpoint for the Woku mobile SDK. Routes by `kind`: `woku` creates a woku review, `nps` an NPS response. The company is resolved from the API key and the response channel is sealed server-side to `mobile-sdk`. Send `application/json` for text/rating captures, or `multipart/form-data` (a `file` part plus a `payload` field with the CaptureSubmission JSON) for audio captures, which are stored as voicemail reviews. Idempotent by the submission's client id (`X-Woku-Idempotency-Key` header or `payload.id`): retrying a processed submission returns the same result instead of duplicating it.",
        "operationId": "v1CreateCapture",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CaptureRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "payload"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Audio file (m4a/aac/mp4). Transcribed server-side."
                  },
                  "payload": {
                    "type": "string",
                    "description": "The CaptureSubmission as a JSON string (same shape as the JSON body)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Capture accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CaptureResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error, or audio capture not yet supported",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-Woku-Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Client-generated submission id for idempotent retries (falls back to payload.id)."
          }
        ]
      }
    },
    "/v1/csat-tool/{id}": {
      "get": {
        "tags": [
          "CSAT"
        ],
        "summary": "Get a CSAT tool definition",
        "description": "Returns the definition of a CSAT tool, including its internal name, the survey question shown to respondents and an optional subject, plus the available locales and the default locale. CSAT tools are always custom (tool-specific). Fails with 403 if the tool does not belong to the caller company.",
        "operationId": "v1GetCsatTool",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CSAT tool.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439011"
          }
        ],
        "responses": {
          "200": {
            "description": "CSAT tool definition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CsatToolResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or tool belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CSAT tool not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/csat-tools": {
      "get": {
        "tags": [
          "CSAT"
        ],
        "summary": "List the CSAT tools of the caller company",
        "description": "Paginated list of the company CSAT tools. Requires the secret key.",
        "operationId": "v1ListCsatTools",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CSAT tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1CsatToolSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/csat": {
      "post": {
        "tags": [
          "CSAT"
        ],
        "summary": "Capture a CSAT score",
        "description": "Submits a CSAT satisfaction score (1 to 5) for the caller company. The csatToolId is always required because CSAT tools are always custom (tool-specific). Optionally include the respondent email, or mark the submission anonymous. CSAT has no promoter, passive or detractor category; satisfaction metrics such as top-2-box are computed separately.",
        "operationId": "v1CreateCsat",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateCsatRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "CSAT response created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CreateCsatResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or tool belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "CSAT"
        ],
        "summary": "List the CSAT responses of the caller company",
        "description": "Paginated, most recent first. Filter by CSAT tool, date range and whether the response carries feedback. Requires the secret key.",
        "operationId": "v1ListCsat",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "csatToolId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive lower bound on createdAt."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive upper bound on createdAt."
          },
          {
            "name": "withFeedback",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "true returns only responses with a text or voice comment."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CSAT responses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1CsatResponse"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/csat/{id}": {
      "get": {
        "tags": [
          "CSAT"
        ],
        "summary": "Get a single CSAT response",
        "operationId": "v1GetCsat",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "CSAT response id"
          }
        ],
        "responses": {
          "200": {
            "description": "CSAT response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CsatResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/csat/{id}/textnotes": {
      "post": {
        "tags": [
          "CSAT"
        ],
        "summary": "Add a text comment to a CSAT response",
        "description": "Attaches an optional text comment to an existing CSAT response. The comment is classified asynchronously. Fails with 403 if the CSAT response does not belong to the caller company.",
        "operationId": "v1AddCsatTextnote",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CSAT response.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateCsatTextnoteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Text comment accepted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or CSAT belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CSAT not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/csat/{id}/voicemails": {
      "post": {
        "tags": [
          "CSAT"
        ],
        "summary": "Add a voice comment to a CSAT response",
        "description": "Attaches an optional voice comment (audio) to an existing CSAT response. The audio is uploaded and transcribed server-side. Fails with 403 if the CSAT response does not belong to the caller company.",
        "operationId": "v1AddCsatVoicemail",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CSAT response.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Audio file (m4a/aac/mp4)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Voice comment accepted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or CSAT belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CSAT not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces-tool/{id}": {
      "get": {
        "tags": [
          "CES"
        ],
        "summary": "Get a CES tool definition",
        "description": "Returns the definition of a CES tool: its internal name, the survey question, an optional follow-up action, and the available and default locales used to render the survey. CES tools are always custom. Fails with 403 if the tool does not belong to the caller company.",
        "operationId": "v1GetCesTool",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CES tool.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439011"
          }
        ],
        "responses": {
          "200": {
            "description": "CES tool definition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CesToolResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or tool belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CES tool not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces-tools": {
      "get": {
        "tags": [
          "CES"
        ],
        "summary": "List the CES tools of the caller company",
        "description": "Paginated list of the company CES tools. Requires the secret key.",
        "operationId": "v1ListCesTools",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CES tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1CesToolSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces": {
      "post": {
        "tags": [
          "CES"
        ],
        "summary": "Capture a CES score",
        "description": "Submits a CES effort score (1-5) for the caller company against a specific CES tool. CES tools are always custom, so `cesToolId` is always required. Optionally include the respondent email, or mark the submission anonymous.",
        "operationId": "v1CreateCes",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateCesRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "CES response created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CreateCesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or tool belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "CES"
        ],
        "summary": "List the CES responses of the caller company",
        "description": "Paginated, most recent first. Filter by tool, date range and whether the response carries feedback. Requires the secret key.",
        "operationId": "v1ListCes",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "cesToolId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive lower bound on createdAt."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive upper bound on createdAt."
          },
          {
            "name": "withFeedback",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "true returns only responses with a text or voice comment."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CES responses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/V1CesResponse"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces/{id}": {
      "get": {
        "tags": [
          "CES"
        ],
        "summary": "Get a single CES response",
        "operationId": "v1GetCes",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CES response.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "responses": {
          "200": {
            "description": "CES response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CesResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the resource does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CES response not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces/{id}/textnotes": {
      "post": {
        "tags": [
          "CES"
        ],
        "summary": "Add a text review to a CES response",
        "description": "Attaches an optional text review to an existing CES response. The comment is classified and merged asynchronously. Fails with 403 if the CES response does not belong to the caller company.",
        "operationId": "v1AddCesTextnote",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CES response.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateCesTextnoteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Text review added"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or CES response belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CES response not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces/{id}/voicemails": {
      "post": {
        "tags": [
          "CES"
        ],
        "summary": "Add a voice review to a CES response",
        "description": "Attaches an optional voice review (audio) to an existing CES response. The audio is uploaded and transcribed server-side. Fails with 403 if the CES response does not belong to the caller company.",
        "operationId": "v1AddCesVoicemail",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "PublishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the CES response.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Audio file (m4a/aac/mp4). Transcribed server-side."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Voice review added"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or CES response belongs to another company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "CES response not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/external-trackers/{entityType}/{id}": {
      "get": {
        "tags": [
          "External Trackers"
        ],
        "summary": "List tracker values assigned to a VoC entity",
        "description": "Returns every tracker value currently bound to the given VoC entity (NPS/CSAT/CES tool, Form or Flow), scoped to the caller's company.\n\nMirrors `GET /v1/external-trackers/wokus/{wokuId}` for the polymorphic (non-Woku) entity types.",
        "operationId": "v1ListEntityExternalTrackers",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entityType",
            "in": "path",
            "required": true,
            "description": "VoC entity type that carries the tracker value. `woku` is not allowed here; use the dedicated `/v1/external-trackers/wokus/{wokuId}` routes.",
            "schema": {
              "type": "string",
              "enum": [
                "nps",
                "csat",
                "ces",
                "form",
                "flow"
              ]
            },
            "example": "nps"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the VoC entity.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of values (may be empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/V1EntityExternalTrackerValue"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unsupported entity type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "External Trackers"
        ],
        "summary": "Assign a tracker value to a VoC entity by tracker name (upsert)",
        "description": "Idempotent. If `(entityType, id, trackerName)` already has a value, it is **overwritten**. The server resolves the tracker by name within the caller's company.\n\nFails with 404 if the tracker name is unknown to the company, 400 if the entity type is unsupported or the tracker is inactive, 403 if the entity belongs to another company.",
        "operationId": "v1AssignEntityExternalTracker",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entityType",
            "in": "path",
            "required": true,
            "description": "VoC entity type that carries the tracker value. `woku` is not allowed here; use the dedicated `/v1/external-trackers/wokus/{wokuId}` routes.",
            "schema": {
              "type": "string",
              "enum": [
                "nps",
                "csat",
                "ces",
                "form",
                "flow"
              ]
            },
            "example": "nps"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the VoC entity.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignExternalTrackerRequest"
              },
              "examples": {
                "trr": {
                  "summary": "Tag an NPS response with a CRM transaction id",
                  "value": {
                    "name": "trr",
                    "value": "TX-2026-00043"
                  }
                },
                "sku": {
                  "summary": "Tag a CES response with a product SKU",
                  "value": {
                    "name": "sku",
                    "value": "WK-PRO-2024-BLUE"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Value assigned (created) or updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1EntityExternalTrackerValue"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported entity type, tracker inactive or validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Entity does not belong to the caller's company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Tracker name not found or entity not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/external-trackers/{entityType}/{id}/{trackerName}": {
      "delete": {
        "tags": [
          "External Trackers"
        ],
        "summary": "Remove a tracker value from a VoC entity",
        "description": "Looks up the tracker by name (active or inactive) and removes its value from the VoC entity. Returns the removed document. **Not idempotent**: returns 404 if no value existed.",
        "operationId": "v1RemoveEntityExternalTracker",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entityType",
            "in": "path",
            "required": true,
            "description": "VoC entity type that carries the tracker value. `woku` is not allowed here; use the dedicated `/v1/external-trackers/wokus/{wokuId}` routes.",
            "schema": {
              "type": "string",
              "enum": [
                "nps",
                "csat",
                "ces",
                "form",
                "flow"
              ]
            },
            "example": "nps"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the VoC entity.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{24}$"
            },
            "example": "507f1f77bcf86cd799439abc"
          },
          {
            "name": "trackerName",
            "in": "path",
            "required": true,
            "description": "Name of the company-level tracker definition.",
            "schema": {
              "type": "string"
            },
            "example": "trr"
          }
        ],
        "responses": {
          "200": {
            "description": "Value removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1EntityExternalTrackerValue"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Tracker name not found, or entity has no value for that tracker",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/csat/invitations": {
      "post": {
        "tags": [
          "Invitations"
        ],
        "summary": "Send the CSAT survey by email or WhatsApp",
        "description": "Dispatches the CSAT survey for the given `csatToolId` to a list of recipients. The response reports per-recipient acceptance: quarantined respondents and recipients blocked by WhatsApp credits or notification policy are listed in `rejected` instead of failing the request. WhatsApp sends consume one WhatsApp credit per recipient.",
        "operationId": "v1SendCsatInvitations",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CsatInvitationsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Per-recipient dispatch summary. Sends are asynchronous.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1InvitationsResult"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the CSAT tool does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ces/invitations": {
      "post": {
        "tags": [
          "Invitations"
        ],
        "summary": "Send the CES survey by email or WhatsApp",
        "description": "Dispatches the CES survey for the given `cesToolId` to a list of recipients. The response reports per-recipient acceptance: quarantined respondents and recipients blocked by WhatsApp credits or notification policy are listed in `rejected` instead of failing the request. WhatsApp sends consume one WhatsApp credit per recipient.",
        "operationId": "v1SendCesInvitations",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CesInvitationsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Per-recipient dispatch summary. Sends are asynchronous.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1InvitationsResult"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key, or the CES tool does not belong to the caller company",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ticket-destinations": {
      "get": {
        "tags": [
          "Ticket Destinations"
        ],
        "summary": "List the ticket destinations of the caller company",
        "operationId": "listTicketDestinations",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Destinations (no credentials)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TicketDestination"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Ticket Destinations"
        ],
        "summary": "Create a ticket destination",
        "description": "`kind`: zendesk \\| salesforce \\| slack \\| custom \\| email. `config`/`credentials` are validated per kind (see `CreateTicketDestinationRequest`). `routingConditions` support operator `equals` (a value) or `any` (the whole tracker).",
        "operationId": "createTicketDestination",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTicketDestinationRequest"
              },
              "examples": {
                "zendesk": {
                  "summary": "Zendesk, routed to a specific agent group",
                  "value": {
                    "name": "Zendesk Soporte Chile",
                    "kind": "zendesk",
                    "config": {
                      "connectionId": "665f1a2b3c4d5e6f7a8b9c0d",
                      "groupId": 360000123456
                    },
                    "credentials": {},
                    "routingConditions": [
                      {
                        "trackerId": "507f1f77bcf86cd799439011",
                        "operator": "equals",
                        "value": "reclamo"
                      }
                    ]
                  }
                },
                "salesforce": {
                  "summary": "Salesforce, default queue",
                  "value": {
                    "name": "Salesforce Casos",
                    "kind": "salesforce",
                    "config": {
                      "queueId": "00G5f000004CzXPEA0",
                      "caseOrigin": "Web"
                    },
                    "credentials": {}
                  }
                },
                "slack": {
                  "summary": "Slack channel notification",
                  "value": {
                    "name": "Slack #soporte",
                    "kind": "slack",
                    "config": {
                      "connectionId": "665f1a2b3c4d5e6f7a8b9c0e",
                      "channelId": "C0123ABCD",
                      "channelLabel": "#soporte"
                    },
                    "credentials": {}
                  }
                },
                "custom": {
                  "summary": "Custom HTTP service with a bearer token",
                  "value": {
                    "name": "Servicio interno",
                    "kind": "custom",
                    "config": {
                      "url": "https://tickets.miempresa.com/api/incoming",
                      "method": "POST"
                    },
                    "credentials": {
                      "authType": "bearer",
                      "token": "sk_live_xxx"
                    }
                  }
                },
                "email": {
                  "summary": "Plain email, routed to any value of a tracker",
                  "value": {
                    "name": "Soporte por correo",
                    "kind": "email",
                    "config": {
                      "emails": [
                        "soporte@miempresa.com"
                      ]
                    },
                    "credentials": {},
                    "routingConditions": [
                      {
                        "trackerId": "507f1f77bcf86cd799439011",
                        "operator": "any"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Destination created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketDestination"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ticket-destinations/{id}": {
      "get": {
        "tags": [
          "Ticket Destinations"
        ],
        "summary": "Get one ticket destination",
        "operationId": "getTicketDestination",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TicketDestinationIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Destination",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketDestination"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Destination not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Ticket Destinations"
        ],
        "summary": "Update a ticket destination",
        "description": "All fields optional. Sending `credentials` rotates the stored secret. Sending `kind` switches the provider; `config`/`credentials` are then validated against the NEW kind.",
        "operationId": "updateTicketDestination",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TicketDestinationIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTicketDestinationRequest"
              },
              "examples": {
                "rotateCredentials": {
                  "summary": "Rotate a custom destination's bearer token",
                  "value": {
                    "credentials": {
                      "authType": "bearer",
                      "token": "sk_live_new"
                    }
                  }
                },
                "disable": {
                  "summary": "Temporarily disable a destination",
                  "value": {
                    "enabled": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Destination updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketDestination"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Destination not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Ticket Destinations"
        ],
        "summary": "Delete a ticket destination",
        "description": "Its own routing conditions live on the destination and disappear with it.",
        "operationId": "deleteTicketDestination",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TicketDestinationIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Destination deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Destination not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plan-groups": {
      "get": {
        "tags": [
          "Action Plan Groups"
        ],
        "summary": "List the action plan groups of the caller company",
        "operationId": "listActionPlanGroups",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Free-text search over name, description and condition values.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Action plan groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ActionPlanGroup"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Action Plan Groups"
        ],
        "summary": "Create an action plan group",
        "operationId": "createActionPlanGroup",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateActionPlanGroupRequest"
              },
              "examples": {
                "default": {
                  "summary": "Group for in-store complaints, routed by any value of a tracker",
                  "value": {
                    "name": "Atención en tienda",
                    "description": "Reclamos y sugerencias sobre la experiencia en tienda física.",
                    "conditions": [
                      {
                        "trackerId": "507f1f77bcf86cd799439011",
                        "operator": "any"
                      }
                    ],
                    "members": [
                      {
                        "userId": "507f1f77bcf86cd799439013",
                        "role": "admin"
                      },
                      {
                        "userId": "507f1f77bcf86cd799439014",
                        "role": "assignee"
                      }
                    ],
                    "threshold": 300
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Group created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlanGroup"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plan-groups/{id}": {
      "get": {
        "tags": [
          "Action Plan Groups"
        ],
        "summary": "Get one action plan group",
        "operationId": "getActionPlanGroup",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanGroupIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Group",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlanGroup"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Action Plan Groups"
        ],
        "summary": "Update an action plan group",
        "operationId": "updateActionPlanGroup",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanGroupIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateActionPlanGroupRequest"
              },
              "examples": {
                "raiseThreshold": {
                  "summary": "Raise the drafting threshold",
                  "value": {
                    "threshold": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Group updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlanGroup"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Action Plan Groups"
        ],
        "summary": "Delete an action plan group",
        "description": "Its conditions/members live on the document and disappear with it; already-generated plans keep their own frozen snapshot (they reference the group only by id).",
        "operationId": "deleteActionPlanGroup",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanGroupIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Group deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plan-groups/{id}/enabled": {
      "patch": {
        "tags": [
          "Action Plan Groups"
        ],
        "summary": "Enable or disable an action plan group",
        "description": "A disabled group stops counting feedback and generating drafts, but keeps its history and stats visible.",
        "operationId": "setActionPlanGroupEnabled",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanGroupIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetActionPlanGroupEnabledRequest"
              },
              "examples": {
                "disable": {
                  "value": {
                    "enabled": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Group updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlanGroup"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Group not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans": {
      "get": {
        "tags": [
          "Action Plans"
        ],
        "summary": "List the action plans of the caller company",
        "operationId": "listActionPlans",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "groupId",
            "in": "query",
            "required": false,
            "description": "Filter by group id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "approved",
                "sent",
                "canceled",
                "delivery_error",
                "in_progress",
                "completed"
              ]
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "Plans whose sources include this VoC source.",
            "schema": {
              "type": "string",
              "enum": [
                "woku",
                "nps",
                "csat",
                "ces"
              ]
            }
          },
          {
            "name": "priority",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start of the createdAt range.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end of the createdAt range (exclusive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Search over title and summary.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of plans",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlansPage"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}": {
      "get": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Get one action plan",
        "description": "Returns the full plan, including the frozen evidence snapshot, generation trace and delivery state.",
        "operationId": "getActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/send": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Send an approved plan to a destination",
        "description": "`provider`: jira \\| monday \\| clickup \\| notion (external, needs `target`) or `internal` (managed inside woku, no target). External providers require the plan to be `approved` or `delivery_error`; delivery then runs asynchronously (poll `GET /v1/action-plans/{id}`). `internal` requires the plan to be `approved` and applies synchronously, moving it to `in_progress`.",
        "operationId": "sendActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendActionPlanRequest"
              },
              "examples": {
                "internal": {
                  "summary": "Work the plan inside woku (managed kanban)",
                  "value": {
                    "provider": "internal"
                  }
                },
                "jira": {
                  "summary": "Send to a Jira project",
                  "value": {
                    "provider": "jira",
                    "target": {
                      "projectId": "10032",
                      "issueTypeId": "10001"
                    },
                    "resourceLabel": "Operaciones CX · Backlog"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Send accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendActionPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing target for the provider",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Invalid status transition (wrong status for the chosen provider), or a send is already in progress for this plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/tasks": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Add a task (draft or managed plan)",
        "operationId": "createActionPlanTask",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateActionPlanTaskRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "text": "Llamar al cliente para coordinar el retiro."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Task added; returns the full updated plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "400": {
            "description": "Invalid task text, or the plan already has 100 tasks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Plan is not a draft or in progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/tasks/reorder": {
      "patch": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Reorder tasks (draft or managed plan)",
        "operationId": "reorderActionPlanTasks",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderActionPlanTasksRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "orderedTaskIds": [
                      "507f1f77bcf86cd799439052",
                      "507f1f77bcf86cd799439051"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tasks reordered; returns the full updated plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "400": {
            "description": "orderedTaskIds is not exactly the current task ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Plan is not a draft or in progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/tasks/{taskId}": {
      "patch": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Edit a task: text on a draft/managed plan, status & assignee on a managed plan",
        "operationId": "updateActionPlanTask",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          },
          {
            "$ref": "#/components/parameters/ActionPlanTaskIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateActionPlanTaskRequest"
              },
              "examples": {
                "editText": {
                  "summary": "Edit the task text",
                  "value": {
                    "text": "Llamar al cliente hoy mismo."
                  }
                },
                "moveOnKanban": {
                  "summary": "Move a managed task to done and assign it",
                  "value": {
                    "status": "done",
                    "assigneeId": "507f1f77bcf86cd799439014"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task updated; returns the full updated plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "400": {
            "description": "No fields to update, or invalid task text",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan or task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Plan is not a draft or in progress, or status/assigneeId was sent while the plan is not managed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Remove a task (draft or managed plan)",
        "operationId": "deleteActionPlanTask",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          },
          {
            "$ref": "#/components/parameters/ActionPlanTaskIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Task removed; returns the full updated plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan or task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Plan is not a draft or in progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/approve": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Approve a draft plan",
        "description": "Transition: `draft` → `approved`.",
        "operationId": "approveActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Cannot approve a plan in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/reopen": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Reopen an approved/delivery_error plan to draft",
        "description": "Transition: `approved` \\| `delivery_error` → `draft` (\"Editar\").",
        "operationId": "reopenActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Cannot reopen a plan in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/cancel": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Cancel a plan",
        "description": "Transition: `draft` \\| `approved` \\| `delivery_error` \\| `in_progress` → `canceled`.",
        "operationId": "cancelActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Cannot cancel a plan in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/complete": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Close a managed plan",
        "description": "Transition: `in_progress` → `completed`.",
        "operationId": "completeActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Cannot complete a plan in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/action-plans/{id}/resume": {
      "post": {
        "tags": [
          "Action Plans"
        ],
        "summary": "Reopen a completed managed plan",
        "description": "Transition: `completed` → `in_progress`.",
        "operationId": "resumeActionPlan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActionPlanIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionPlan"
                }
              }
            }
          },
          "403": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "409": {
            "description": "Cannot resume a plan in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Company API key. Obtain this from your Woku dashboard under Settings > API Keys. The same key used for the v0 endpoints."
      },
      "PublishableKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-woku-key",
        "description": "Publishable capture key (pk_…), safe to embed in the public web widget. Accepted only on capture endpoints; scoped to capture (cannot create wokus, share, or read reports)."
      }
    },
    "parameters": {
      "WokuIdPath": {
        "name": "wokuId",
        "in": "path",
        "required": true,
        "description": "MongoDB ObjectId of the Woku.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "example": "507f1f77bcf86cd799439011"
      },
      "TicketDestinationIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "MongoDB ObjectId of the ticket destination.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "example": "507f1f77bcf86cd799439021"
      },
      "ActionPlanGroupIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "MongoDB ObjectId of the action plan group.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "example": "507f1f77bcf86cd799439031"
      },
      "ActionPlanIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "MongoDB ObjectId of the action plan.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "example": "507f1f77bcf86cd799439041"
      },
      "ActionPlanTaskIdPath": {
        "name": "taskId",
        "in": "path",
        "required": true,
        "description": "MongoDB ObjectId of the task (subdocument of the plan).",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-fA-F]{24}$"
        },
        "example": "507f1f77bcf86cd799439051"
      }
    },
    "schemas": {
      "ExternalTracker": {
        "type": "object",
        "description": "Company-level tracker definition.",
        "required": [
          "_id",
          "companyId",
          "name",
          "system",
          "active"
        ],
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439011"
          },
          "companyId": {
            "type": "string",
            "example": "507f1f77bcf86cd799439012"
          },
          "name": {
            "type": "string",
            "description": "Identifier of the tracker within the company catalog (unique per company).",
            "example": "trr"
          },
          "system": {
            "type": "string",
            "description": "External system this tracker maps to.",
            "example": "crm interno"
          },
          "description": {
            "type": "string",
            "description": "Human-readable description of the tracker.",
            "example": "identificador de la transacción de un arriendo o venta"
          },
          "active": {
            "type": "boolean",
            "description": "Whether the tracker is active. Inactive trackers can no longer be assigned to Wokus but existing values remain readable.",
            "example": true
          },
          "createdBy": {
            "type": "string",
            "example": "507f1f77bcf86cd799439013"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WokuExternalTrackerValue": {
        "type": "object",
        "description": "Value of a tracker bound to a specific Woku.",
        "required": [
          "_id",
          "companyId",
          "wokuId",
          "trackerId",
          "value"
        ],
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439015"
          },
          "companyId": {
            "type": "string",
            "example": "507f1f77bcf86cd799439012"
          },
          "wokuId": {
            "type": "string",
            "description": "Woku this value belongs to.",
            "example": "507f1f77bcf86cd799439011"
          },
          "trackerId": {
            "type": "string",
            "description": "Reference to the company-level tracker definition.",
            "example": "507f1f77bcf86cd799439014"
          },
          "value": {
            "type": "string",
            "description": "External identifier (e.g. CRM transaction id).",
            "example": "TX-2026-00043",
            "maxLength": 500
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AssignExternalTrackerRequest": {
        "type": "object",
        "required": [
          "name",
          "value"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Name of the company-level tracker definition the value belongs to.",
            "example": "trr",
            "maxLength": 60
          },
          "value": {
            "type": "string",
            "description": "External identifier value (always stored as string).",
            "example": "TX-2026-00043",
            "maxLength": 500
          }
        }
      },
      "PaginatedExternalTrackers": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalTracker"
            }
          },
          "total": {
            "type": "integer",
            "example": 42
          },
          "page": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 20
          }
        }
      },
      "PaginatedWokuExternalTrackerValues": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WokuExternalTrackerValue"
            }
          },
          "total": {
            "type": "integer",
            "example": 7
          },
          "page": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 20
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "integer",
            "example": 400
          },
          "message": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "error": {
            "type": "string",
            "example": "Bad Request"
          }
        }
      },
      "ForbiddenError": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "integer",
            "example": 403
          },
          "message": {
            "type": "string",
            "example": "Authentication required"
          },
          "error": {
            "type": "string",
            "example": "Forbidden"
          }
        }
      },
      "NotFoundError": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "integer",
            "example": 404
          },
          "message": {
            "type": "string",
            "example": "External tracker not found"
          },
          "error": {
            "type": "string",
            "example": "Not Found"
          }
        }
      },
      "V1CreateWokuRequest": {
        "type": "object",
        "required": [
          "description",
          "fileUrl"
        ],
        "properties": {
          "description": {
            "type": "string",
            "description": "Description of the woku (product, service, or experience being reviewed).",
            "example": "Customer Service Experience - Store #123"
          },
          "fileUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL of an existing file (image or video) to associate with the woku.",
            "example": "https://cdn.example.com/images/product-image.jpg"
          },
          "folderSecondaryKey": {
            "type": "string",
            "example": "store-123"
          },
          "parentFolderSecondaryKey": {
            "type": "string",
            "example": "region-north"
          },
          "clientEmail": {
            "type": "string",
            "format": "email",
            "example": "customer@example.com"
          },
          "clientPhone": {
            "type": "integer",
            "example": 56912345678
          }
        }
      },
      "V1CreateWokuFormDataRequest": {
        "type": "object",
        "required": [
          "description",
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "Image or video file to associate with the woku."
          },
          "description": {
            "type": "string",
            "example": "Customer Service Experience - Store #123"
          },
          "folderSecondaryKey": {
            "type": "string",
            "example": "store-123"
          },
          "parentFolderSecondaryKey": {
            "type": "string",
            "example": "region-north"
          },
          "clientEmail": {
            "type": "string",
            "format": "email"
          },
          "clientPhone": {
            "type": "string",
            "example": "56912345678"
          }
        }
      },
      "V1CreateTextnoteRequest": {
        "type": "object",
        "required": [
          "qualification",
          "description"
        ],
        "properties": {
          "responseChannel": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional inbound response channel: whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string. Defaults to 'api' when omitted; any value provided here REPLACES it and is stored verbatim (user-defined channel)."
          },
          "qualification": {
            "type": "integer",
            "enum": [
              1,
              2,
              3,
              4,
              5
            ],
            "example": 5
          },
          "description": {
            "type": "string",
            "maxLength": 3000,
            "example": "Excellent service! The staff was very helpful."
          },
          "clientEmail": {
            "type": "string",
            "format": "email"
          },
          "clientPhone": {
            "type": "integer",
            "description": "Reviewer phone number."
          },
          "anonymous": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "V1CreateVoicemailRequest": {
        "type": "object",
        "required": [
          "file",
          "qualification"
        ],
        "properties": {
          "responseChannel": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional inbound response channel: whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string. Defaults to 'api' when omitted; any value provided here REPLACES it and is stored verbatim (user-defined channel)."
          },
          "file": {
            "type": "string",
            "format": "binary",
            "description": "Audio file for the voicemail."
          },
          "qualification": {
            "type": "string",
            "enum": [
              "1",
              "2",
              "3",
              "4",
              "5"
            ],
            "example": "5"
          },
          "clientEmail": {
            "type": "string",
            "format": "email"
          },
          "clientPhone": {
            "type": "string"
          },
          "anonymous": {
            "type": "string",
            "enum": [
              "true",
              "false"
            ]
          }
        }
      },
      "V1ShareWokuRequest": {
        "type": "object",
        "description": "Either `clientEmail` (single) or `clientEmails` (array) must be supplied.",
        "properties": {
          "clientEmail": {
            "type": "string",
            "format": "email",
            "example": "customer@example.com"
          },
          "clientEmails": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "example": [
              "customer1@example.com",
              "customer2@example.com"
            ]
          }
        }
      },
      "V1CreateNpsRequest": {
        "type": "object",
        "required": [
          "score"
        ],
        "properties": {
          "responseChannel": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional inbound response channel: whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string. Defaults to 'api' when omitted; any value provided here REPLACES it and is stored verbatim (user-defined channel)."
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "NPS score. 0 to 6 detractor, 7 to 8 passive, 9 to 10 promoter.",
            "example": 9
          },
          "npsToolId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "description": "Optional NPS tool id. With it the score is attached to that tool; without it the score is company-level.",
            "example": "507f1f77bcf86cd799439011"
          },
          "clientEmail": {
            "type": "string",
            "format": "email",
            "description": "Respondent email. Omit (or set anonymous) for an anonymous score.",
            "example": "customer@example.com"
          },
          "anonymous": {
            "type": "boolean",
            "description": "When true, no respondent email is stored.",
            "default": false
          }
        }
      },
      "V1CreateNpsResponse": {
        "type": "object",
        "properties": {
          "npsId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "example": "507f1f77bcf86cd799439abc"
          },
          "nps": {
            "type": "object",
            "description": "The created NPS document.",
            "properties": {
              "_id": {
                "type": "string",
                "pattern": "^[0-9a-fA-F]{24}$"
              },
              "companyId": {
                "type": "string",
                "pattern": "^[0-9a-fA-F]{24}$"
              },
              "score": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10,
                "example": 9
              },
              "npsToolId": {
                "type": "string",
                "pattern": "^[0-9a-fA-F]{24}$",
                "description": "Present only for tool-specific scores."
              },
              "clientId": {
                "type": "string",
                "pattern": "^[0-9a-fA-F]{24}$",
                "description": "Present when the respondent was identified."
              },
              "anonymous": {
                "type": "boolean"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "V1NpsToolResponse": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "example": "507f1f77bcf86cd799439011"
          },
          "name": {
            "type": "string",
            "description": "Internal name of the NPS tool.",
            "example": "Post-checkout NPS"
          },
          "npsMessage": {
            "type": "string",
            "description": "Company or product referenced in the survey question.",
            "example": "Acme Store"
          },
          "audienceType": {
            "type": "string",
            "description": "Audience referenced in the survey question.",
            "example": "shoppers"
          }
        }
      },
      "V1CreateNpsTextnoteRequest": {
        "type": "object",
        "required": [
          "description"
        ],
        "properties": {
          "description": {
            "type": "string",
            "maxLength": 3000,
            "description": "Text feedback content.",
            "example": "El producto cumple mis expectativas."
          }
        }
      },
      "V1QuarantineCheckResponse": {
        "type": "object",
        "required": [
          "quarantined"
        ],
        "properties": {
          "quarantined": {
            "type": "boolean",
            "description": "True if the respondent cannot submit again yet."
          },
          "remainingMinutes": {
            "type": "integer",
            "description": "Minutes until the respondent can submit again (only when quarantined).",
            "example": 42
          },
          "quarantineId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "description": "Id of the matched quarantine rule (only when quarantined)."
          }
        }
      },
      "V1InvitationsRequest": {
        "type": "object",
        "required": [
          "channel",
          "recipients"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "whatsapp"
            ],
            "description": "Delivery channel for the invitations."
          },
          "recipients": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "string"
            },
            "description": "Email addresses (email channel) or phone numbers with country code, digits only, e.g. 56912345678 (whatsapp channel)."
          },
          "language": {
            "type": "string",
            "enum": [
              "es",
              "en"
            ],
            "description": "Language of the invitation template. Defaults to es."
          }
        }
      },
      "V1NpsInvitationsRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/V1InvitationsRequest"
          },
          {
            "type": "object",
            "properties": {
              "npsToolId": {
                "type": "string",
                "description": "NPS tool to survey for. Omit to send the company-level NPS survey."
              }
            }
          }
        ]
      },
      "V1InvitationsResult": {
        "type": "object",
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "whatsapp"
            ]
          },
          "accepted": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Recipients whose invitation was dispatched (or queued). Normalized: emails lowercased, phones digits only."
          },
          "rejected": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "recipient": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "invalid_recipient",
                    "quarantined",
                    "insufficient_credits_or_blocked",
                    "send_failed"
                  ]
                }
              }
            },
            "description": "Recipients whose invitation was not dispatched, with the reason."
          }
        }
      },
      "V1CreateFormResponseRequest": {
        "type": "object",
        "required": [
          "anonymous",
          "answers"
        ],
        "properties": {
          "responseChannel": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional inbound response channel: whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string. Defaults to 'api' when omitted; any value provided here REPLACES it and is stored verbatim (user-defined channel)."
          },
          "anonymous": {
            "type": "boolean",
            "description": "Whether this response is anonymous."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Respondent email (required when the form identifies clients by email and the response is not anonymous)."
          },
          "phone": {
            "type": "string",
            "description": "Respondent phone (required when the form identifies clients by phone and the response is not anonymous)."
          },
          "answers": {
            "type": "object",
            "additionalProperties": true,
            "description": "Answers keyed by field id, e.g. { \"field-uuid-1\": \"John\", \"field-uuid-2\": 5 }."
          }
        }
      },
      "V1FormDefinitionResponse": {
        "type": "object",
        "properties": {
          "form": {
            "type": "object",
            "properties": {
              "_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "text",
                        "number",
                        "date",
                        "select",
                        "multiselect",
                        "email",
                        "phone"
                      ]
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "placeholder": {
                      "type": "string"
                    },
                    "options": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "order": {
                      "type": "number"
                    }
                  }
                }
              },
              "settings": {
                "type": "object"
              },
              "closed": {
                "type": "boolean"
              },
              "availableLocales": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "defaultLocale": {
                "type": "string"
              },
              "localizedContent": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          },
          "company": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "avatar": {
                "type": "string"
              }
            }
          }
        }
      },
      "V1FlowDataResponse": {
        "type": "object",
        "properties": {
          "flowId": {
            "type": "string"
          },
          "flowName": {
            "type": "string"
          },
          "companyId": {
            "type": "string"
          },
          "companyName": {
            "type": "string"
          },
          "companyAvatar": {
            "type": "string"
          },
          "wokusData": {
            "type": "array",
            "description": "Wokus of the journey, in presentation order.",
            "items": {
              "type": "object",
              "properties": {
                "wokuId": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "fileUrl": {
                  "type": "string"
                },
                "fileType": {
                  "type": "string"
                }
              }
            }
          },
          "anonymousDisabled": {
            "type": "boolean"
          },
          "npsToolId": {
            "type": "string",
            "nullable": true,
            "description": "Linked NPS tool, when the flow ends with a tool-specific NPS."
          },
          "companyIdNps": {
            "type": "string",
            "nullable": true,
            "description": "Set when the flow ends with the company-level NPS."
          }
        }
      },
      "V1NpsToolSummary": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "npsMessage": {
            "type": "string"
          },
          "audienceType": {
            "type": "string"
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "defaultLocale": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1NpsResponse": {
        "type": "object",
        "description": "An NPS response. Client contact data is never included; clientId is a reference only.",
        "properties": {
          "responseChannel": {
            "type": "string",
            "nullable": true,
            "description": "Inbound response channel (whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string). Null on historical responses."
          },
          "_id": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "category": {
            "type": "string",
            "enum": [
              "promoter",
              "passive",
              "detractor"
            ]
          },
          "anonymous": {
            "type": "boolean"
          },
          "npsToolId": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "textnote": {
            "type": "object",
            "nullable": true,
            "properties": {
              "description": {
                "type": "string"
              },
              "feedbackType": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "recognition",
                  "improvement",
                  null
                ]
              },
              "anonymous": {
                "type": "boolean"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "voicemail": {
            "type": "object",
            "nullable": true,
            "properties": {
              "transcription": {
                "type": "string"
              },
              "feedbackType": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "recognition",
                  "improvement",
                  null
                ]
              },
              "anonymous": {
                "type": "boolean"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "V1FormSummary": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "closed": {
            "type": "boolean"
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "defaultLocale": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1FormResponse": {
        "type": "object",
        "properties": {
          "responseChannel": {
            "type": "string",
            "nullable": true,
            "description": "Inbound response channel (whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string). Null on historical responses."
          },
          "_id": {
            "type": "string"
          },
          "anonymous": {
            "type": "boolean"
          },
          "clientId": {
            "type": "string",
            "nullable": true
          },
          "answers": {
            "type": "object",
            "additionalProperties": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1WokuSummary": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "file": {
            "type": "object",
            "nullable": true,
            "properties": {
              "url": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            }
          },
          "folderId": {
            "type": "string",
            "nullable": true
          },
          "closed": {
            "type": "boolean"
          },
          "reviewsDisabled": {
            "type": "boolean"
          },
          "anonymousDisabled": {
            "type": "boolean"
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "defaultLocale": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1WokuWithStats": {
        "allOf": [
          {
            "$ref": "#/components/schemas/V1WokuSummary"
          },
          {
            "type": "object",
            "properties": {
              "stats": {
                "type": "object",
                "properties": {
                  "totalReviews": {
                    "type": "integer",
                    "description": "Star-only ratings plus text and voice reviews."
                  },
                  "averageRating": {
                    "type": "number",
                    "nullable": true,
                    "description": "1-5 average over every rating, one decimal."
                  },
                  "recognitionCount": {
                    "type": "integer"
                  },
                  "improvementCount": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        ]
      },
      "V1WokuReview": {
        "type": "object",
        "description": "A woku review (text or voice). Client contact data is never included.",
        "properties": {
          "responseChannel": {
            "type": "string",
            "nullable": true,
            "description": "Inbound response channel (whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string). Null on historical responses."
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "voice"
            ]
          },
          "qualification": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 5
          },
          "description": {
            "type": "string",
            "description": "Text reviews only."
          },
          "transcription": {
            "type": "string",
            "description": "Voice reviews only (server-side transcription)."
          },
          "feedbackType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "recognition",
              "improvement",
              null
            ]
          },
          "anonymous": {
            "type": "boolean"
          },
          "clientId": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1FlowSummary": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "wokusIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "npsToolId": {
            "type": "string",
            "nullable": true
          },
          "companyIdNps": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1CaptureRequest": {
        "type": "object",
        "description": "Normalized capture from the Woku mobile SDK. The response channel is sealed server-side to 'mobile-sdk' (it is not read from the body).",
        "required": [
          "kind"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Client-generated idempotency id"
          },
          "kind": {
            "type": "string",
            "enum": [
              "woku",
              "nps"
            ]
          },
          "targetId": {
            "type": "string",
            "description": "wokuId for a woku capture; optional npsToolId for an NPS capture (company-level when omitted)"
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "description": "Woku star rating (kind=woku)"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "NPS score (kind=nps)"
          },
          "comment": {
            "type": "string",
            "maxLength": 3000
          },
          "respondent": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "externalId": {
                "type": "string"
              }
            }
          }
        }
      },
      "V1CaptureResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Echo of the client idempotency id"
          },
          "kind": {
            "type": "string",
            "enum": [
              "woku",
              "nps"
            ]
          },
          "remoteId": {
            "type": "string",
            "description": "Server id of the created review or NPS"
          },
          "status": {
            "type": "string",
            "enum": [
              "accepted"
            ]
          }
        }
      },
      "V1CreateCsatRequest": {
        "type": "object",
        "required": [
          "score",
          "csatToolId"
        ],
        "properties": {
          "responseChannel": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional inbound response channel: whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string. Defaults to 'api' when omitted; any value provided here REPLACES it and is stored verbatim (user-defined channel)."
          },
          "score": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "description": "CSAT satisfaction score. 1 very unsatisfied to 5 very satisfied.",
            "example": 4
          },
          "csatToolId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "description": "CSAT tool id. Always required because CSAT tools are always custom (tool-specific).",
            "example": "507f1f77bcf86cd799439011"
          },
          "clientEmail": {
            "type": "string",
            "format": "email",
            "description": "Respondent email. Omit (or set anonymous) for an anonymous score.",
            "example": "customer@example.com"
          },
          "anonymous": {
            "type": "boolean",
            "description": "When true, no respondent email is stored.",
            "default": false
          },
          "dispatchToken": {
            "type": "string",
            "maxLength": 64,
            "description": "Opaque invitation dispatch token echoed from the link (?dtoken=). Consumed to mark the outbound invitation responded; never persisted."
          }
        }
      },
      "V1CreateCsatResponse": {
        "type": "object",
        "properties": {
          "csatId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "example": "507f1f77bcf86cd799439abc"
          },
          "csat": {
            "description": "The created CSAT response, in the same curated shape as the read path.",
            "allOf": [
              {
                "$ref": "#/components/schemas/V1CsatResponse"
              }
            ]
          }
        }
      },
      "V1CsatResponse": {
        "type": "object",
        "description": "A CSAT response. Client contact data is never included; clientId is a reference only. The optional comment (text or voice) is inlined on the response. CSAT has no promoter/passive/detractor category.",
        "properties": {
          "_id": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "anonymous": {
            "type": "boolean"
          },
          "csatToolId": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "nullable": true
          },
          "responseChannel": {
            "type": "string",
            "nullable": true,
            "description": "Inbound response channel (whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string). Null on historical responses."
          },
          "commentType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "textnote",
              "voicemail",
              null
            ],
            "description": "Which kind of comment is attached, if any."
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Text comment content (present when commentType is 'textnote')."
          },
          "transcription": {
            "type": "string",
            "nullable": true,
            "description": "Voice comment transcription (present when commentType is 'voicemail')."
          },
          "feedbackType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "recognition",
              "improvement",
              null
            ],
            "description": "AI classification of the comment. Null until classified or when there is no comment."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1CsatToolResponse": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "example": "507f1f77bcf86cd799439011"
          },
          "name": {
            "type": "string",
            "description": "Internal name of the CSAT tool.",
            "example": "Post-purchase CSAT"
          },
          "question": {
            "type": "string",
            "description": "Survey question shown to the respondent.",
            "example": "How satisfied are you with your purchase?"
          },
          "subject": {
            "type": "string",
            "nullable": true,
            "description": "Optional subject or context for the survey.",
            "example": "Order #1234"
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Locales the survey is available in."
          },
          "defaultLocale": {
            "type": "string",
            "description": "Default locale for the survey."
          }
        }
      },
      "V1CsatToolSummary": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "question": {
            "type": "string"
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "defaultLocale": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1CreateCsatTextnoteRequest": {
        "type": "object",
        "required": [
          "description"
        ],
        "properties": {
          "description": {
            "type": "string",
            "maxLength": 3000,
            "description": "Text feedback content.",
            "example": "El proceso fue muy facil."
          }
        }
      },
      "V1CreateCesRequest": {
        "type": "object",
        "required": [
          "score",
          "cesToolId"
        ],
        "properties": {
          "responseChannel": {
            "type": "string",
            "maxLength": 48,
            "description": "Optional inbound response channel: whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string. Defaults to 'api' when omitted; any value provided here REPLACES it and is stored verbatim (user-defined channel)."
          },
          "score": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "description": "CES effort score on a 1-5 scale.",
            "example": 4
          },
          "cesToolId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "description": "CES tool id. Always required: CES tools are always custom.",
            "example": "507f1f77bcf86cd799439011"
          },
          "clientEmail": {
            "type": "string",
            "format": "email",
            "description": "Respondent email. Omit (or set anonymous) for an anonymous capture.",
            "example": "customer@example.com"
          },
          "anonymous": {
            "type": "boolean",
            "description": "When true, no respondent email is stored.",
            "default": false
          },
          "dispatchToken": {
            "type": "string",
            "maxLength": 64,
            "description": "Opaque invitation dispatch token echoed from the link (?dtoken=). Consumed to mark the outbound invitation responded; never persisted."
          }
        }
      },
      "V1CreateCesResponse": {
        "type": "object",
        "properties": {
          "cesId": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "example": "507f1f77bcf86cd799439abc"
          },
          "ces": {
            "description": "The created CES response (curated shape).",
            "allOf": [
              {
                "$ref": "#/components/schemas/V1CesResponse"
              }
            ]
          }
        }
      },
      "V1CesResponse": {
        "type": "object",
        "description": "A CES response. Client contact data is never included; clientId is a reference only.",
        "properties": {
          "_id": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "description": "CES effort score on a 1-5 scale."
          },
          "anonymous": {
            "type": "boolean"
          },
          "cesToolId": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "nullable": true,
            "description": "Reference to the respondent. Always null for anonymous responses."
          },
          "responseChannel": {
            "type": "string",
            "nullable": true,
            "description": "Inbound response channel (whatsapp | email | review-app | widget-web | mobile-sdk | api, or a user-defined string). Null on historical responses."
          },
          "commentType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "textnote",
              "voicemail",
              null
            ],
            "description": "Kind of the attached optional comment, if any."
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Text feedback content (present only when commentType is textnote)."
          },
          "transcription": {
            "type": "string",
            "nullable": true,
            "description": "Audio transcription (present only when commentType is voicemail)."
          },
          "feedbackType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "recognition",
              "improvement",
              null
            ],
            "description": "AI-classified category of the comment; filled asynchronously."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1CesToolResponse": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{24}$",
            "example": "507f1f77bcf86cd799439011"
          },
          "name": {
            "type": "string",
            "description": "Internal name of the CES tool.",
            "example": "Post-support CES"
          },
          "question": {
            "type": "string",
            "description": "Survey question shown to the respondent.",
            "example": "Resolver tu problema fue facil?"
          },
          "action": {
            "type": "string",
            "nullable": true,
            "description": "Optional follow-up action related to the survey."
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "defaultLocale": {
            "type": "string"
          }
        }
      },
      "V1CesToolSummary": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "question": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "nullable": true
          },
          "availableLocales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "defaultLocale": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1CreateCesTextnoteRequest": {
        "type": "object",
        "required": [
          "description"
        ],
        "properties": {
          "description": {
            "type": "string",
            "maxLength": 3000,
            "description": "Text feedback content.",
            "example": "Resolver mi problema fue muy facil."
          }
        }
      },
      "V1EntityExternalTrackerValue": {
        "type": "object",
        "description": "Value of a tracker bound to a specific VoC entity (NPS/CSAT/CES tool, Form or Flow). The polymorphic `(entityType, entityId)` pair generalizes what `WokuExternalTrackerValue` does for Wokus.",
        "required": [
          "_id",
          "companyId",
          "entityType",
          "entityId",
          "trackerId",
          "value"
        ],
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439015"
          },
          "companyId": {
            "type": "string",
            "example": "507f1f77bcf86cd799439012"
          },
          "entityType": {
            "type": "string",
            "description": "VoC entity type that carries this value.",
            "enum": [
              "nps",
              "csat",
              "ces",
              "form",
              "flow"
            ],
            "example": "nps"
          },
          "entityId": {
            "type": "string",
            "description": "VoC entity this value belongs to.",
            "example": "507f1f77bcf86cd799439abc"
          },
          "trackerId": {
            "type": "string",
            "description": "Reference to the company-level tracker definition.",
            "example": "507f1f77bcf86cd799439014"
          },
          "value": {
            "type": "string",
            "description": "External identifier (e.g. CRM transaction id).",
            "example": "TX-2026-00043",
            "maxLength": 500
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V1CsatInvitationsRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/V1InvitationsRequest"
          },
          {
            "type": "object",
            "required": [
              "csatToolId"
            ],
            "properties": {
              "csatToolId": {
                "type": "string",
                "description": "CSAT tool to survey for (always required: CSAT is always custom)."
              }
            }
          }
        ]
      },
      "V1CesInvitationsRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/V1InvitationsRequest"
          },
          {
            "type": "object",
            "required": [
              "cesToolId"
            ],
            "properties": {
              "cesToolId": {
                "type": "string",
                "description": "CES tool to survey for (always required: CES is always custom)."
              }
            }
          }
        ]
      },
      "TicketRoutingCondition": {
        "type": "object",
        "description": "One routing row: the ticket must carry `trackerId` with a value that matches per `operator`. Rows form a boolean expression (AND binds tighter than OR) via `relationToPrevious`; a destination is used when ANY OR-group of its rows fully matches.",
        "required": [
          "trackerId"
        ],
        "properties": {
          "relationToPrevious": {
            "type": "string",
            "enum": [
              "AND",
              "OR"
            ],
            "description": "Joins this row to the previous one. Omitted on the first row."
          },
          "trackerId": {
            "type": "string",
            "description": "ExternalTracker id (the company's tracker catalog).",
            "example": "507f1f77bcf86cd799439011"
          },
          "operator": {
            "type": "string",
            "enum": [
              "equals",
              "any"
            ],
            "default": "equals",
            "description": "`equals` matches the tracker value against `value`. `any` matches any value of the tracker (the whole tracker; `value` is omitted)."
          },
          "value": {
            "type": "string",
            "maxLength": 200,
            "description": "The value the tracker must equal. Required unless operator is \"any\"."
          }
        }
      },
      "ActionPlanGroupCondition": {
        "type": "object",
        "description": "One condition row: feedback must carry `trackerId` with a value that matches per `operator`. Rows form a boolean expression (AND binds tighter than OR) via `relationToPrevious`; the group counts a feedback item when ANY OR-group of its rows fully matches.",
        "required": [
          "trackerId"
        ],
        "properties": {
          "relationToPrevious": {
            "type": "string",
            "enum": [
              "AND",
              "OR"
            ],
            "description": "Joins this row to the previous one. Omitted on the first row."
          },
          "trackerId": {
            "type": "string",
            "description": "ExternalTracker id (the company's tracker catalog).",
            "example": "507f1f77bcf86cd799439011"
          },
          "operator": {
            "type": "string",
            "enum": [
              "equals",
              "any"
            ],
            "default": "equals",
            "description": "`equals` matches the tracker value against `value`. `any` matches any value of the tracker (the whole tracker; `value` is omitted)."
          },
          "value": {
            "type": "string",
            "maxLength": 200,
            "description": "The value the tracker must equal. Required unless operator is \"any\"."
          }
        }
      },
      "TicketDestinationTemplate": {
        "type": "object",
        "description": "Only meaningful for kind `custom`; named kinds (zendesk/salesforce/slack/email) use a fixed preset and reject this field.",
        "required": [
          "preset"
        ],
        "properties": {
          "preset": {
            "type": "string",
            "example": "custom"
          },
          "body": {
            "type": "string",
            "maxLength": 10000,
            "description": "Custom JSON body template with `{{path}}` placeholders resolved against the canonical ticket envelope. Omit to send the canonical envelope unchanged."
          }
        }
      },
      "CreateTicketDestinationRequest": {
        "type": "object",
        "required": [
          "name",
          "kind",
          "config",
          "credentials"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "example": "Zendesk Soporte Chile"
          },
          "kind": {
            "type": "string",
            "enum": [
              "zendesk",
              "salesforce",
              "slack",
              "custom",
              "email"
            ]
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "Non-secret, kind-specific config, validated per kind:\n- `zendesk`: `{ connectionId, groupId? }`. `connectionId` is the id of the company's Zendesk Integration; `groupId` is the optional numeric agent group the ticket lands in.\n- `salesforce`: `{ queueId?, caseOrigin? }`. `queueId` is a Case Queue id (`\"00G...\"`, 15 or 18 chars); `caseOrigin` defaults to `\"Web\"`.\n- `slack`: `{ connectionId, channelId, channelLabel? }`. `connectionId` is the id of the company's Slack Integration; `channelId` is the Slack channel id (`\"C...\"`); `channelLabel` is a display-only name (e.g. `\"#soporte\"`).\n- `custom`: `{ url, method, headers? }`. `method` is `POST` or `PUT`; `headers` are non-secret extra headers (never `Authorization`).\n- `email`: `{ emails }`. Up to 20 recipient addresses; the server sends the ticket as a formatted email, no external call."
          },
          "credentials": {
            "type": "object",
            "additionalProperties": true,
            "description": "Write-only, kind-specific credentials. Never returned by the API; see `credentialsHint` on the response.\n- `zendesk`, `salesforce`, `slack`: authenticate through the company's Integration (connected separately in the dashboard). Send `{}`.\n- `custom`: `{ authType, ... }` where `authType` is `none`, `basic` (adds `username`, `password`), `bearer` (adds `token`) or `api-key-header` (adds `headerName`, `headerValue`).\n- `email`: send `{}`. The server owns the mail transport; there is no credential to submit."
          },
          "aiContext": {
            "type": "string",
            "maxLength": 2000,
            "description": "Per-destination natural-language context injected into the AI triage prompt."
          },
          "routingConditions": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/TicketRoutingCondition"
            }
          },
          "template": {
            "$ref": "#/components/schemas/TicketDestinationTemplate"
          }
        }
      },
      "UpdateTicketDestinationRequest": {
        "type": "object",
        "description": "All fields optional; only the ones present are changed. A `credentials` payload rotates the stored secret (re-validated and re-encrypted). `kind` can change: config/credentials are then validated against the NEW kind.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "kind": {
            "type": "string",
            "enum": [
              "zendesk",
              "salesforce",
              "slack",
              "custom",
              "email"
            ]
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "Non-secret, kind-specific config, validated per kind:\n- `zendesk`: `{ connectionId, groupId? }`. `connectionId` is the id of the company's Zendesk Integration; `groupId` is the optional numeric agent group the ticket lands in.\n- `salesforce`: `{ queueId?, caseOrigin? }`. `queueId` is a Case Queue id (`\"00G...\"`, 15 or 18 chars); `caseOrigin` defaults to `\"Web\"`.\n- `slack`: `{ connectionId, channelId, channelLabel? }`. `connectionId` is the id of the company's Slack Integration; `channelId` is the Slack channel id (`\"C...\"`); `channelLabel` is a display-only name (e.g. `\"#soporte\"`).\n- `custom`: `{ url, method, headers? }`. `method` is `POST` or `PUT`; `headers` are non-secret extra headers (never `Authorization`).\n- `email`: `{ emails }`. Up to 20 recipient addresses; the server sends the ticket as a formatted email, no external call."
          },
          "credentials": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present = credential rotation. Write-only, kind-specific credentials. Never returned by the API; see `credentialsHint` on the response.\n- `zendesk`, `salesforce`, `slack`: authenticate through the company's Integration (connected separately in the dashboard). Send `{}`.\n- `custom`: `{ authType, ... }` where `authType` is `none`, `basic` (adds `username`, `password`), `bearer` (adds `token`) or `api-key-header` (adds `headerName`, `headerValue`).\n- `email`: send `{}`. The server owns the mail transport; there is no credential to submit."
          },
          "aiContext": {
            "type": "string",
            "maxLength": 2000
          },
          "routingConditions": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/TicketRoutingCondition"
            }
          },
          "template": {
            "$ref": "#/components/schemas/TicketDestinationTemplate"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "TicketDestination": {
        "type": "object",
        "description": "Credentials are never included: `credentialsHint` shows only the masked last 4 characters of the stored secret (empty for kinds with no secret of their own).",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439021"
          },
          "companyId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "zendesk",
              "salesforce",
              "slack",
              "custom",
              "email"
            ]
          },
          "config": {
            "type": "object",
            "additionalProperties": true
          },
          "aiContext": {
            "type": "string"
          },
          "routingConditions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TicketRoutingCondition"
            }
          },
          "credentialsHint": {
            "type": "string",
            "description": "Masked preview of the stored secret (e.g. \"••••a1b2\"). Empty when the kind carries no secret of its own.",
            "example": "••••a1b2"
          },
          "template": {
            "$ref": "#/components/schemas/TicketDestinationTemplate"
          },
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "error"
            ],
            "description": "`error` = the last delivery or connection test failed (see `lastError`)."
          },
          "lastDeliveryAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastError": {
            "type": "string",
            "description": "Sanitized failure reason of the last delivery attempt. Never contains credentials."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DeleteResult": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ConflictError": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "integer",
            "example": 409
          },
          "message": {
            "type": "string",
            "example": "Cannot approve a plan in status \"approved\""
          },
          "error": {
            "type": "string",
            "example": "Conflict"
          }
        }
      },
      "ActionPlanGroupMember": {
        "type": "object",
        "required": [
          "userId",
          "role"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "description": "Company member user id.",
            "example": "507f1f77bcf86cd799439013"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "assignee"
            ],
            "description": "`admin` approves/sends plans; `assignee` works on tasks. A group needs at least one of each."
          }
        }
      },
      "CreateActionPlanGroupRequest": {
        "type": "object",
        "required": [
          "name",
          "conditions",
          "members"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "example": "Atención en tienda"
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "conditions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupCondition"
            },
            "description": "Tracker conditions; at least one row is required."
          },
          "members": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupMember"
            },
            "description": "Group team; at least one admin and one assignee."
          },
          "threshold": {
            "type": "integer",
            "minimum": 1,
            "default": 300,
            "description": "New improvement comments that trigger a plan draft."
          }
        }
      },
      "UpdateActionPlanGroupRequest": {
        "type": "object",
        "description": "All fields optional; only the ones present are changed.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Empty string clears it."
          },
          "conditions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupCondition"
            }
          },
          "members": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupMember"
            }
          },
          "threshold": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "SetActionPlanGroupEnabledRequest": {
        "type": "object",
        "required": [
          "enabled"
        ],
        "properties": {
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "ActionPlanGroup": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439031"
          },
          "companyId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "conditions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupCondition"
            }
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupMember"
            }
          },
          "threshold": {
            "type": "integer",
            "example": 300
          },
          "enabled": {
            "type": "boolean",
            "description": "A disabled group stops counting feedback and generating drafts, but keeps its history and stats visible.",
            "default": true
          },
          "counterSince": {
            "type": "string",
            "format": "date-time",
            "description": "Base date of the negatives counter: only feedback created after it counts toward `threshold`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ActionPlanTask": {
        "type": "object",
        "description": "One task of the plan. In a managed plan (status `in_progress`) it also carries a kanban `status` and an optional `assigneeId`.",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439051"
          },
          "text": {
            "type": "string",
            "maxLength": 160
          },
          "order": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "todo",
              "in_progress",
              "done"
            ],
            "default": "todo"
          },
          "assigneeId": {
            "type": "string",
            "description": "Group member responsible for the task (managed plans)."
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Stamped when the task moves to `done`, cleared when it moves back."
          }
        }
      },
      "ActionPlanPattern": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Stable cluster key from the review-intelligence engine."
          },
          "label": {
            "type": "string"
          },
          "mentions": {
            "type": "integer",
            "description": "Real mention count over the whole commented population."
          }
        }
      },
      "ActionPlanQuote": {
        "type": "object",
        "description": "A verbatim customer quote frozen into the evidence snapshot.",
        "properties": {
          "text": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "woku",
              "nps",
              "csat",
              "ces"
            ]
          },
          "contextLabel": {
            "type": "string",
            "description": "Human label of where the quote comes from (tool/operation)."
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          },
          "score": {
            "type": "number",
            "description": "Raw score on the source's own scale."
          }
        }
      },
      "ActionPlanEvidence": {
        "type": "object",
        "description": "Frozen evidence snapshot: the exact analytical scope at generation time plus deterministic counts. Never recomputed after generation.",
        "properties": {
          "windowFrom": {
            "type": "string",
            "format": "date-time"
          },
          "windowTo": {
            "type": "string",
            "format": "date-time",
            "description": "Exclusive upper bound of the window."
          },
          "conditionsSnapshot": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanGroupCondition"
            },
            "description": "Copy of the group's tracker conditions at generation time."
          },
          "negatives": {
            "type": "integer",
            "description": "Commented improvement items in scope."
          },
          "positives": {
            "type": "integer",
            "description": "Commented recognition items in scope."
          },
          "total": {
            "type": "integer",
            "description": "Commented items in scope (score-only items never count as evidence)."
          },
          "quotes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanQuote"
            }
          }
        }
      },
      "ActionPlanGeneration": {
        "type": "object",
        "description": "Traceability of the LLM run that drafted the plan.",
        "properties": {
          "model": {
            "type": "string"
          },
          "promptVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ActionPlanDelivery": {
        "type": "object",
        "description": "Delivery-to-integration state, filled by the send phase. `provider` is `internal` for a managed plan.",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "jira",
              "monday",
              "clickup",
              "notion",
              "internal"
            ]
          },
          "resourceLabel": {
            "type": "string",
            "description": "Human label of the destination resource (project/board/list/page)."
          },
          "externalUrl": {
            "type": "string"
          },
          "externalId": {
            "type": "string"
          },
          "tasksCreated": {
            "type": "integer"
          },
          "lastError": {
            "type": "string",
            "description": "Connector failure reason of the last attempt."
          },
          "sentAt": {
            "type": "string",
            "format": "date-time"
          },
          "sentBy": {
            "type": "string"
          }
        }
      },
      "ActionPlan": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439041"
          },
          "companyId": {
            "type": "string"
          },
          "groupId": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "summary": {
            "type": "string",
            "description": "What the customers said (narrative synthesis, no invented numbers)."
          },
          "objective": {
            "type": "string"
          },
          "expectedImpact": {
            "type": "string"
          },
          "theme": {
            "type": "string",
            "description": "Detected theme the plan addresses (e.g. \"Demoras en despacho\")."
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanTask"
            }
          },
          "patterns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanPattern"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "woku",
                "nps",
                "csat",
                "ces"
              ]
            },
            "description": "VoC sources with evidence in the frozen scope."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "approved",
              "sent",
              "canceled",
              "delivery_error",
              "in_progress",
              "completed"
            ],
            "description": "`in_progress`/`completed` are the managed (in-house) lifecycle; the rest apply to plans sent to an external tool or awaiting approval."
          },
          "priority": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ],
            "description": "Deterministic (engine-computed) priority; never set by the LLM."
          },
          "evidence": {
            "$ref": "#/components/schemas/ActionPlanEvidence"
          },
          "generation": {
            "$ref": "#/components/schemas/ActionPlanGeneration"
          },
          "delivery": {
            "$ref": "#/components/schemas/ActionPlanDelivery"
          },
          "approvedBy": {
            "type": "string"
          },
          "approvedAt": {
            "type": "string",
            "format": "date-time"
          },
          "canceledBy": {
            "type": "string"
          },
          "canceledAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Stamped when a managed plan is closed (status `completed`)."
          },
          "aiGenerated": {
            "type": "boolean",
            "default": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ActionPlanSummary": {
        "type": "object",
        "description": "Row projection returned by the list endpoint (lighter than the full plan; use `GET /v1/action-plans/{id}` for the frozen evidence and delivery detail).",
        "properties": {
          "_id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "approved",
              "sent",
              "canceled",
              "delivery_error",
              "in_progress",
              "completed"
            ]
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "woku",
                "nps",
                "csat",
                "ces"
              ]
            }
          },
          "priority": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "groupId": {
            "type": "string"
          },
          "evidence": {
            "type": "object",
            "properties": {
              "negatives": {
                "type": "integer"
              },
              "positives": {
                "type": "integer"
              }
            }
          },
          "delivery": {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string",
                "enum": [
                  "jira",
                  "monday",
                  "clickup",
                  "notion",
                  "internal"
                ]
              }
            }
          }
        }
      },
      "ActionPlansPage": {
        "type": "object",
        "description": "Not the items/total/page/limit envelope used elsewhere: this list echoes only `data` and `total`; re-send the same `page`/`limit` query params to fetch the next page.",
        "required": [
          "data",
          "total"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionPlanSummary"
            }
          },
          "total": {
            "type": "integer",
            "example": 42
          }
        }
      },
      "SendActionPlanRequest": {
        "type": "object",
        "required": [
          "provider"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "jira",
              "monday",
              "clickup",
              "notion",
              "internal"
            ],
            "description": "`internal` is the managed destination (no target, no external call): the plan moves to `in_progress` and its tasks are worked with the task endpoints below."
          },
          "target": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Required for every provider except `internal`:\n- `jira`: `{ siteId?, projectId, issueTypeId }`\n- `monday`: `{ boardId, groupId }`\n- `clickup`: `{ listId }`\n- `notion`: `{ databaseId }`"
          },
          "resourceLabel": {
            "type": "string",
            "maxLength": 300,
            "description": "Human destination summary (e.g. \"Operaciones CX · Backlog\"), persisted as `delivery.resourceLabel`. Omitted for `internal`."
          }
        }
      },
      "SendActionPlanResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "sending",
              "managed"
            ],
            "description": "`sending` for external providers (delivery runs async; poll `GET /v1/action-plans/{id}` for `status`/`delivery`). `managed` for `internal`, applied synchronously."
          }
        }
      },
      "CreateActionPlanTaskRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "maxLength": 160
          }
        }
      },
      "UpdateActionPlanTaskRequest": {
        "type": "object",
        "description": "Partial edit. `text` applies to a draft or managed plan; `status` and `assigneeId` only apply to a managed plan (status `in_progress`). At least one field is required.",
        "properties": {
          "text": {
            "type": "string",
            "maxLength": 160
          },
          "status": {
            "type": "string",
            "enum": [
              "todo",
              "in_progress",
              "done"
            ]
          },
          "assigneeId": {
            "type": "string",
            "description": "Group member responsible; null or empty string clears the assignee."
          }
        }
      },
      "ReorderActionPlanTasksRequest": {
        "type": "object",
        "required": [
          "orderedTaskIds"
        ],
        "properties": {
          "orderedTaskIds": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "string"
            },
            "description": "ALL task ids of the plan in the desired order (a permutation of the current ids)."
          }
        }
      }
    }
  }
}
