openapi: 3.1.0
info:
  title: Nooks Sequencing API
  version: 0.1.0
  description: |
    The Nooks Sequencing API provides programmatic access to manage sequences, tasks, templates, and prospect engagement workflows created using the Nooks SEP.

    ## Authentication

    Send a bearer token in the `Authorization` header:
    ```
    Authorization: Bearer <token>
    ```

    Two token types are accepted on the same header — the API detects which
    format you sent and validates accordingly. If you already have a token,
    paste it into the Authentication panel and skip the flow setup.

    ### API keys

    Long-lived, workspace-scoped. Best for backend integrations and
    server-to-server automation. Generate one from **Developer Settings →
    API Keys** in your Nooks workspace. API keys are prefixed `nooks-api-`
    and have full read/write access within the owning workspace.

    ### OAuth 2.0 access tokens

    Short-lived (1 hour), user-scoped, scope-limited JWTs issued by
    `https://oauth.nooks.in` via the standard authorization-code + PKCE flow.
    Best for third-party apps acting on behalf of a specific user — the token
    carries that user's identity and a subset of scopes the user consented
    to. Refresh tokens rotate every 90 days and are invalidated on first
    re-use (refresh-token reuse detection).

    **Endpoints:**

    - Authorize: `https://oauth.nooks.in/oauth/authorize`
    - Token: `https://oauth.nooks.in/oauth/token`
    - JWKS: `https://oauth.nooks.in/.well-known/jwks.json`
    - Server metadata (RFC 8414): `https://oauth.nooks.in/.well-known/oauth-authorization-server`

    **Available scopes:**

    | Scope | Grants |
    | --- | --- |
    | `prospects:read` | View your prospects |
    | `prospects:write` | Create and update prospects |
    | `sequences:read` | View your sequences |
    | `sequences:write` | Create and update sequences |
    | `sequence-steps:read` | View sequence steps |
    | `sequence-steps:write` | Create, edit, delete, and reorder sequence steps |
    | `sequence-states:read` | View sequence enrollments |
    | `sequence-states:write` | Enroll prospects and manage enrollments |
    | `tasks:read` | View your tasks |
    | `tasks:write` | Create, update, complete, skip, and delete tasks |
    | `calls:read` | View your calls |
    | `calls:write` | Create and update calls |
    | `call-dispositions:read` | View call dispositions |
    | `emails:read` | View your emails |
    | `emails:write` | Create and update emails |
    | `mailboxes:read` | View connected mailboxes |
    | `users:read` | View users in your workspace |
    | `accounts:read` | View accounts (companies) in your workspace |
    | `notes:write` | Create notes on CRM-backed prospects and accounts |
    | `opportunities:read` | View opportunities (deals) in your workspace |
    | `search:read` | Search across your prospects, accounts, and other records |

    ## Rate Limiting
    API requests are rate limited per workspace and per endpoint in a fixed
    one-minute window. Separate endpoint buckets do not share quota, except
    routes without an explicit limit use the shared default bucket.

    Every response includes these headers:
    - `X-RateLimit-Limit` -- maximum requests allowed in the current per-minute window
    - `X-RateLimit-Remaining` -- requests remaining in the current window
    - `X-RateLimit-Reset` -- seconds until the current window resets

    When the limit is exceeded the API returns `429 Too Many Requests` with a
    `Retry-After` header indicating how many seconds to wait before retrying.

    **Current limits:**

    | Endpoint class | Methods | Limit |
    | --- | --- | --- |
    | List reads: `/sequences`, `/emails`, `/users`, `/sequenceStates`, `/prospects`, `/mailboxes`, `/calls`, `/sequenceSteps`, `/callDispositions`, `/tasks`, `/accounts` | `GET` | 300 requests/minute per endpoint |
    | Read by ID: `/sequences/{id}`, `/emails/{id}`, `/users/{id}`, `/sequenceStates/{id}`, `/prospects/{id}`, `/mailboxes/{id}`, `/calls/{id}`, `/sequenceSteps/{id}`, `/callDispositions/{id}`, `/tasks/{id}`, `/accounts/{id}`, `/emailTemplate/{id}` | `GET` | 600 requests/minute per endpoint |
    | Sequence writes: `/sequences`, `/sequences/{id}`, `/sequences/{id}/actions/reorderSteps` | `POST`, `PATCH` | 120 requests/minute per endpoint |
    | Sequence step writes: `/sequenceSteps`, `/sequenceSteps/{id}` | `POST`, `PATCH`, `DELETE` | 120 requests/minute per endpoint |
    | Sequence state writes: `/sequenceStates`, `/sequenceStates/{id}`, `/sequenceStates/{id}/actions/finish` | `POST`, `DELETE` | 120 requests/minute per endpoint |
    | Task writes: `/tasks`, `/tasks/{id}`, `/tasks/{id}/complete`, `/tasks/{id}/skip` | `POST`, `PATCH`, `DELETE` | 120 requests/minute per endpoint |
    | CRM note writes: `/prospects/{id}/notes`, `/accounts/{id}/notes` | `POST` | 30 requests/minute per endpoint |
    | `/integrations/prospects/sync` | `POST` | 10 requests/minute |
    | Any other endpoint | Any | 30 requests/minute, shared default bucket |

    ## Pagination
    List endpoints support cursor-based pagination using the `page[size]` and `page[after]`/`page[before]` query parameters.
    - Maximum page size: 100
    - Default page size: 50

    ## Include (Inline Expansion)
    Most GET endpoints support an `include` query parameter that expands related `ReferenceObject` fields inline,
    eliminating the need for follow-up API calls.

    **Format:** `?include=field1,field2` (comma-separated field names)

    **Without include:**
    ```json
    GET /v1/prospects/123

    {
      "id": "123",
      "sequenceStates": [
        { "id": "ss-1", "_href": "/v1/sequenceStates/ss-1" }
      ]
    }
    ```

    **With `include=sequenceStates`:**
    ```json
    GET /v1/prospects/123?include=sequenceStates

    {
      "id": "123",
      "sequenceStates": [
        {
          "id": "ss-1",
          "_href": "/v1/sequenceStates/ss-1",
          "state": "active",
          "sequence": { "id": "seq-1", "_href": "/v1/sequences/seq-1" },
          "prospect": { "id": "123", "_href": "/v1/prospects/123" },
          "creator": { "id": "u-1", "_href": "/v1/users/u-1" },
          "sequenceStep": null,
          "createdAt": "2024-01-01T00:00:00Z",
          "updatedAt": "2024-01-01T00:00:00Z"
        }
      ]
    }
    ```

    The expanded object is a superset of `ReferenceObject` — it keeps `id` and `_href` and adds all DTO fields.
    The response shape is unchanged; the field just contains richer data.

    **Hard constraints (enforced with 400 errors):**
    - **Max 3 includes per request.** Requesting more than 3 comma-separated values returns `400: "include accepts at most 3 values"`.
    - **GET endpoints only.** POST, PATCH, and DELETE endpoints do not accept `include`.
    - **No nested includes.** Only top-level field names are valid (e.g., `sequenceStates`). Dot-notation like `sequenceStates.prospect` returns 400.
    - **`account` on Prospect is not includable.** Requesting `include=account` returns 400.

    Each endpoint's `include` parameter lists the valid field names for that resource.

  contact:
    name: Nooks API Support
    email: support@nooks.in
    url: https://www.nooks.in
  license:
    name: Proprietary
  x-logo:
    url: ./nooks-logo.svg
    altText: Nooks Logo
    href: https://www.nooks.ai

servers:
  - url: https://partner-api.nooks.in/v1
    description: Production API

tags:
  - name: Sequences
    description: Manage sales sequences
  - name: Emails
    description: Access email records
  - name: Users
    description: Manage workspace users
  - name: SequenceStates
    description: Track prospect enrollments in sequences
  - name: Prospects
    description: Manage prospects
  - name: Accounts
    description: Access account (company) records
  - name: Notes
    description: Create CRM notes on prospects and accounts
  - name: Mailboxes
    description: Manage mailboxes (email aliases)
  - name: SequenceSteps
    description: Access sequence step definitions
  - name: Calls
    description: Access call records
  - name: CallDispositions
    description: Access call disposition definitions
  - name: Tasks
    description: Manage tasks (one-off calls and email activities)
  - name: EmailTemplates
    description: Access email template content
  - name: Introspection
    description: Inspect the authenticated principal

x-tagGroups:
  - name: API Reference
    tags:
      - Sequences
      - SequenceSteps
      - Emails
      - Users
      - SequenceStates
      - Prospects
      - Accounts
      - Notes
      - Mailboxes
      - Calls
      - CallDispositions
      - Tasks
      - EmailTemplates
      - Introspection

paths:
  /sequences:
    get:
      operationId: listSequences
      summary: List sequences
      description: |
        Returns a paginated list of sequences.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequences?filter[name]=Q1+Outbound+Campaign' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        With include:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequences?include=owner,sequenceSteps' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Sequences
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - name: filter[name]
          in: query
          description: Filter sequences by exact name match
          required: false
          schema:
            type: string
          example: "Q1 Outbound Campaign"
        - name: filter[type]
          in: query
          description: "Filter by sequence type. Valid values: date, interval"
          required: false
          schema:
            type: string
            enum:
              - date
              - interval
          example: "interval"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline (`owner`, `sequenceSteps`) or computed fields to include (`analytics`). Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [owner, sequenceSteps, analytics]
            maxItems: 3
          style: form
          explode: false
          example: [owner, sequenceSteps]
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Sequence"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "550e8400-e29b-41d4-a716-446655440000"
                    name: "Q1 Outbound Campaign"
                    type: "interval"
                    enabled: true
                    owner:
                      id: "660e8400-e29b-41d4-a716-446655440001"
                      _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                    sequenceSteps:
                      - id: "aa0e8400-e29b-41d4-a716-446655440030"
                        _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                      - id: "aa0e8400-e29b-41d4-a716-446655440031"
                        _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440031"
                    privacy: "team_editable"
                    createdAt: "2026-01-12T09:30:00Z"
                    updatedAt: "2026-01-15T09:30:00Z"
                  - id: "660e8400-e29b-41d4-a716-446655440002"
                    name: "Q2 Follow-up Sequence"
                    type: "date"
                    enabled: false
                    owner:
                      id: "660e8400-e29b-41d4-a716-446655440001"
                      _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                    sequenceSteps: []
                    privacy: "private"
                    createdAt: "2026-01-08T14:00:00Z"
                    updatedAt: "2026-01-10T14:00:00Z"
                links:
                  first: "https://partner-api.nooks.in/v1/sequences?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/sequences?page[size]=50&page[after]=eyJpZCI6IjY2MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMiIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createSequence
      summary: Create a sequence
      description: |
        Creates a new, empty sequence owned by the given user. Steps are
        added and edited in the Nooks app — this endpoint creates the
        sequence container (name, type, owner, privacy).

        OAuth callers with the `user` role can only create sequences owned
        by themselves; `admin` and `manager` roles can create sequences for
        any user in the workspace. When that check fails (or `owner.id` is
        not a workspace member), OAuth callers receive `403
        INSUFFICIENT_PERMISSION`; API-key callers receive `422` for an
        unknown owner. To enroll prospects into the new sequence, use
        `POST /v1/sequenceStates`.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/sequences' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "name": "Q3 Outbound Campaign",
              "owner": { "id": "KKLvN5wQoghWFwUvC75CZ12QM7I3" }
            }
          }'
        ```
      tags:
        - Sequences
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for creating a sequence
              additionalProperties: false
              properties:
                data:
                  type: object
                  additionalProperties: false
                  properties:
                    name:
                      type: string
                      minLength: 1
                      description: Name of the sequence
                      example: "Q3 Outbound Campaign"
                    type:
                      type: string
                      enum:
                        - interval
                        - date
                      default: "interval"
                      description: |
                        Scheduling model for the sequence. `interval` steps are
                        spaced relative to the previous step; `date` steps run
                        on fixed dates.
                      example: "interval"
                    owner:
                      type: object
                      description: The user who will own the sequence
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                          minLength: 1
                          maxLength: 128
                          pattern: "^[A-Za-z0-9_-]{1,128}$"
                          description: ID of the owner user (Firebase UID)
                          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                      required:
                        - id
                    enabled:
                      type: boolean
                      default: true
                      description: Whether the sequence is active (`false` = archived)
                      example: true
                    privacy:
                      type: string
                      enum:
                        - private
                        - team_visible
                        - team_editable
                      default: "team_editable"
                      description: Privacy level of the sequence
                      example: "team_editable"
                  required:
                    - name
                    - owner
              required:
                - data
      responses:
        "201":
          description: Sequence created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Sequence"
              example:
                id: "550e8400-e29b-41d4-a716-446655440000"
                name: "Q3 Outbound Campaign"
                type: "interval"
                enabled: true
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                sequenceSteps: []
                privacy: "team_editable"
                createdAt: "2026-01-12T09:30:00Z"
                updatedAt: "2026-01-15T09:30:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `sequences:write` scope
            (`INSUFFICIENT_SCOPE`), or the caller's role does not permit
            acting on this sequence or owner (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "422":
          description: |
            Unprocessable entity — `owner.id` is not a user in this workspace
            (use an id from `GET /v1/users`). Only returned to API-key
            callers; OAuth callers instead receive `403
            INSUFFICIENT_PERMISSION` for an unknown owner (see above).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "UNPROCESSABLE_ENTITY"
                  message: "Owner user KKLvN5wQoghWFwUvC75CZ12QM7I3 not found in this workspace. Use a user id from GET /v1/users."
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /sequences/{id}:
    get:
      operationId: getSequence
      summary: Get sequence by ID
      description: |
        Returns a single sequence by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequences/550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        With include:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequences/550e8400-e29b-41d4-a716-446655440000?include=owner' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Sequences
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline (`owner`, `sequenceSteps`) or computed fields to include (`analytics`). Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [owner, sequenceSteps, analytics]
            maxItems: 3
          style: form
          explode: false
          example: [analytics]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Sequence"
              example:
                id: "550e8400-e29b-41d4-a716-446655440000"
                name: "Q1 Outbound Campaign"
                type: "interval"
                enabled: true
                owner:
                  id: "660e8400-e29b-41d4-a716-446655440001"
                  _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                sequenceSteps:
                  - id: "aa0e8400-e29b-41d4-a716-446655440030"
                    _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                  - id: "aa0e8400-e29b-41d4-a716-446655440031"
                    _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440031"
                privacy: "team_editable"
                analytics:
                  email:
                    delivered: 412
                    opened: 198
                    clicked: 41
                    replied: 23
                    bounced: 7
                    unsubscribed: 2
                  calls:
                    dialed: 380
                    connected: 71
                    meetings: 5
                  linkedIn: null
                createdAt: "2026-01-12T09:30:00Z"
                updatedAt: "2026-01-15T09:30:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    patch:
      operationId: updateSequence
      summary: Update a sequence
      description: |
        Updates a sequence's name and/or `enabled` flag. Setting `enabled`
        to `false` archives the sequence; `true` reactivates it. At least
        one field must be provided.

        OAuth callers with the `user` role can only update sequences they
        own (`403 INSUFFICIENT_PERMISSION` otherwise); `admin` and `manager`
        roles can update any sequence in the workspace.

        Example:
        ```bash
        curl -X PATCH 'https://partner-api.nooks.in/v1/sequences/550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{ "data": { "name": "Q3 Outbound Campaign (paused)", "enabled": false } }'
        ```
      tags:
        - Sequences
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for updating a sequence
              additionalProperties: false
              properties:
                data:
                  type: object
                  minProperties: 1
                  additionalProperties: false
                  description: At least one of `name` or `enabled` is required.
                  properties:
                    name:
                      type: string
                      minLength: 1
                      description: New name for the sequence
                      example: "Q3 Outbound Campaign (paused)"
                    enabled:
                      type: boolean
                      description: Whether the sequence is active (`false` = archived)
                      example: false
              required:
                - data
      responses:
        "200":
          description: Sequence updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Sequence"
              example:
                id: "550e8400-e29b-41d4-a716-446655440000"
                name: "Q3 Outbound Campaign (paused)"
                type: "interval"
                enabled: false
                owner:
                  id: "660e8400-e29b-41d4-a716-446655440001"
                  _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                sequenceSteps:
                  - id: "aa0e8400-e29b-41d4-a716-446655440030"
                    _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                privacy: "team_editable"
                createdAt: "2026-01-12T09:30:00Z"
                updatedAt: "2026-01-15T09:30:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `sequences:write` scope
            (`INSUFFICIENT_SCOPE`), or the caller's role does not permit
            acting on this sequence or owner (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /sequences/{id}/actions/reorderSteps:
    post:
      operationId: reorderSequenceSteps
      summary: Reorder a sequence's steps
      description: |
        Reorders a sequence's steps to the given `stepIds` order. `stepIds`
        must be the COMPLETE ordered list of the sequence's current step
        ids — SEP validates both count and membership, so a partial list,
        an id from another sequence, or a duplicate id is rejected with
        `422` rather than applied as a partial reorder.

        Returns the reordered steps as `{ "data": SequenceStep[] }`, in the
        same order as the requested `stepIds` (re-read through the same
        path as `GET /sequenceSteps`, so `template`/`hasAbVariants` reflect
        current state; unlike the internal SEP response, this omits nothing).

        To build the `stepIds` list, first fetch the sequence's current
        steps in order: `GET /sequenceSteps?filter[sequence][id]={id}`
        (steps are returned ordered by `order`).

        OAuth callers with the `user` role can only reorder steps of
        sequences they own (`403 INSUFFICIENT_PERMISSION` otherwise);
        `admin` and `manager` roles can reorder any sequence in the
        workspace.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/sequences/550e8400-e29b-41d4-a716-446655440000/actions/reorderSteps' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "stepIds": [
                "aa0e8400-e29b-41d4-a716-446655440031",
                "aa0e8400-e29b-41d4-a716-446655440030"
              ]
            }
          }'
        ```
      tags:
        - SequenceSteps
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for reordering a sequence's steps
              additionalProperties: false
              properties:
                data:
                  type: object
                  additionalProperties: false
                  properties:
                    stepIds:
                      type: array
                      minItems: 1
                      items:
                        type: string
                        format: uuid
                      description: |
                        The COMPLETE ordered list of the sequence's step ids,
                        in the desired new order.
                      example:
                        - "aa0e8400-e29b-41d4-a716-446655440031"
                        - "aa0e8400-e29b-41d4-a716-446655440030"
                  required:
                    - stepIds
              required:
                - data
      responses:
        "200":
          description: Steps reordered
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SequenceStep"
              example:
                data:
                  - id: "aa0e8400-e29b-41d4-a716-446655440031"
                    order: 0
                    interval: null
                    action: "call"
                    name: null
                    note: null
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template: null
                    hasAbVariants: false
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2026-01-15T09:30:00.000Z"
                  - id: "aa0e8400-e29b-41d4-a716-446655440030"
                    order: 1
                    interval: 4320
                    action: "auto_email"
                    name: null
                    note: "Initial outreach email"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template:
                      id: "ee0e8400-e29b-41d4-a716-446655440080"
                      _href: "/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440080"
                    hasAbVariants: true
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2026-01-15T09:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `sequence-steps:write`
            scope (`INSUFFICIENT_SCOPE`), or the caller's role does not
            permit acting on this sequence (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          description: |
            Unprocessable entity — `stepIds` is missing one or more of the
            sequence's current steps, contains an id that does not belong
            to this sequence, or contains a duplicate.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                incompleteList:
                  summary: stepIds omits an existing step
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Expected 3 steps, received 2"
                foreignStepId:
                  summary: stepIds contains an id from another sequence
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Step ID aa0e8400-e29b-41d4-a716-446655440099 does not belong to this sequence"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /sequenceSteps:
    get:
      operationId: listSequenceSteps
      summary: List sequence steps
      description: |
        Returns a paginated list of sequence steps. Supports filtering by step IDs or by sequence.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceSteps?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by sequence:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceSteps?filter[sequence][id]=550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by IDs:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceSteps?filter[id]=UUID1,UUID2' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceSteps
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - $ref: "#/components/parameters/FilterId"
        - name: filter[sequence][id]
          in: query
          description: Filter steps belonging to a specific sequence
          required: false
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline (`sequence`, `template`) or computed fields to include (`analytics`). Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [sequence, template, analytics]
            maxItems: 3
          style: form
          explode: false
          example: [analytics]
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SequenceStep"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "aa0e8400-e29b-41d4-a716-446655440030"
                    order: 0
                    interval: null
                    action: "auto_email"
                    note: "Initial outreach email"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template:
                      id: "ee0e8400-e29b-41d4-a716-446655440080"
                      _href: "/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440080"
                    hasAbVariants: true
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2025-10-01T10:00:00.000Z"
                  - id: "aa0e8400-e29b-41d4-a716-446655440031"
                    order: 1
                    interval: 4320
                    action: "call"
                    note: null
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template: null
                    hasAbVariants: false
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2025-10-01T10:00:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/sequenceSteps?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/sequenceSteps?page[size]=50&page[after]=eyJpZCI6ImFhMGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAzMSIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createSequenceStep
      summary: Create a sequence step
      description: |
        Creates an interval-based sequence step on the given sequence.
        `interval` is in MINUTES. This endpoint only creates interval-based
        steps — `date`/scheduled-send fields and AI email/LinkedIn fields
        (`emailAgentId`, `linkedInMessageAgentConfig`, ...) are not
        supported here.

        When `order` is omitted, SEP appends the step to the end of the
        sequence. SEP also creates the step's per-step email template (for
        email-action steps), compacts sequence step order, and writes the
        audit log.

        `action: "automatic_linkedin_message"` is rejected (`400`) — SEP
        only allows it inside connection-request follow-up (child)
        sequences, which this API does not expose. Use
        `"manual_linkedin_message"` instead.

        Email content is written via `emailSubject`/`emailBody` here and
        read back via `step.template.id` → `GET /emailTemplate/{id}`.
        Reply-action steps (`manual_email_reply`, `auto_email_reply`)
        always have their template subject forced to `""` regardless of
        `emailSubject`.

        OAuth callers with the `user` role can only add steps to sequences
        they own (`403 INSUFFICIENT_PERMISSION` otherwise); `admin` and
        `manager` roles can add steps to any sequence in the workspace.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/sequenceSteps' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "sequence": { "id": "550e8400-e29b-41d4-a716-446655440000" },
              "action": "call",
              "interval": 1440,
              "note": "Follow-up call"
            }
          }'
        ```
      tags:
        - SequenceSteps
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for creating a sequence step
              additionalProperties: false
              properties:
                data:
                  type: object
                  additionalProperties: false
                  properties:
                    sequence:
                      type: object
                      description: The sequence to add this step to
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: ID of the sequence
                          example: "550e8400-e29b-41d4-a716-446655440000"
                      required:
                        - id
                    action:
                      type: string
                      enum:
                        - call
                        - manual_email
                        - auto_email
                        - manual_email_reply
                        - auto_email_reply
                        - basic_task
                        - linkedin_connection_request
                        - manual_linkedin_message
                        - manual_sms
                        - auto_sms
                        - manual_whatsapp
                      description: |
                        Type of action for this step. `automatic_linkedin_message`
                        and `manual_linkedin_connection_request` are not
                        accepted here (see description above).
                      example: "call"
                    interval:
                      type: integer
                      minimum: 0
                      description: Minutes to wait after the previous step before executing this one
                      example: 1440
                    order:
                      type: integer
                      minimum: 0
                      description: Zero-based position within the sequence. Defaults to appending at the end.
                      example: 2
                    name:
                      type: string
                      nullable: true
                      maxLength: 100
                      description: Optional display name for this step
                      example: "Follow-up call"
                    note:
                      type: string
                      nullable: true
                      description: Optional note or instructions for this step
                      example: "Mention the pricing page"
                    emailSubject:
                      type: string
                      description: Email subject. Only valid on email actions (`manual_email`, `auto_email`, `manual_email_reply`, `auto_email_reply`) — any other action returns `400`.
                      example: "Quick question about {{company}}"
                    emailBody:
                      type: string
                      description: Email body HTML. Only valid on email actions — any other action returns `400`.
                      example: "<p>Hi {{firstName}},</p>"
                  required:
                    - sequence
                    - action
                    - interval
              required:
                - data
            examples:
              callStep:
                summary: Create a call step
                value:
                  data:
                    sequence: { id: "550e8400-e29b-41d4-a716-446655440000" }
                    action: "call"
                    interval: 1440
                    note: "Follow-up call"
              autoEmailStep:
                summary: Create an auto_email step with content
                value:
                  data:
                    sequence: { id: "550e8400-e29b-41d4-a716-446655440000" }
                    action: "auto_email"
                    interval: 0
                    emailSubject: "Quick question about {{company}}"
                    emailBody: "<p>Hi {{firstName}},</p>"
      responses:
        "201":
          description: Sequence step created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SequenceStep"
              examples:
                callStep:
                  summary: Call step created
                  value:
                    id: "aa0e8400-e29b-41d4-a716-446655440032"
                    order: 2
                    name: "Follow-up call"
                    interval: 1440
                    action: "call"
                    note: "Follow-up call"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template: null
                    hasAbVariants: false
                    createdAt: "2026-01-15T09:30:00.000Z"
                    updatedAt: "2026-01-15T09:30:00.000Z"
                autoEmailStep:
                  summary: auto_email step created — note the auto-created template
                  value:
                    id: "aa0e8400-e29b-41d4-a716-446655440033"
                    order: 3
                    name: null
                    interval: 0
                    action: "auto_email"
                    note: null
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template:
                      id: "ee0e8400-e29b-41d4-a716-446655440081"
                      _href: "/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440081"
                    hasAbVariants: false
                    createdAt: "2026-01-15T09:30:00.000Z"
                    updatedAt: "2026-01-15T09:30:00.000Z"
        "400":
          description: |
            Bad request — Zod validation failed: a required field is
            missing, an unknown field was sent, or `action` is
            `"automatic_linkedin_message"` or
            `"manual_linkedin_connection_request"` (neither can be used to
            create a new step — see the `action` description above).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INVALID_REQUEST_PARAMETERS"
                  message: "Invalid request parameters"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `sequence-steps:write`
            scope (`INSUFFICIENT_SCOPE`), or the caller's role does not
            permit acting on this sequence (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "404":
          description: The referenced sequence does not exist in this workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "NOT_FOUND"
                  message: "The requested resource was not found"
        "422":
          description: |
            Unprocessable entity — SEP rejected the step definition: only
            one connection-request step (`linkedin_connection_request`) is
            allowed per sequence, or an `auto_email` step that starts a new
            thread requires a non-empty `emailSubject`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                duplicateConnectionRequest:
                  summary: Sequence already has a connection-request step
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Only one connection request step is allowed per sequence"
                missingAutoEmailSubject:
                  summary: auto_email step with no subject
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Automatic email steps that start a new thread require a subject"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /sequenceSteps/{id}:
    get:
      operationId: getSequenceStep
      summary: Get sequence step by ID
      description: |
        Returns a single sequence step by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceSteps
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence step
          schema:
            type: string
            format: uuid
          example: "aa0e8400-e29b-41d4-a716-446655440030"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline (`sequence`, `template`) or computed fields to include (`analytics`). Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [sequence, template, analytics]
            maxItems: 3
          style: form
          explode: false
          example: [analytics]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SequenceStep"
              example:
                id: "aa0e8400-e29b-41d4-a716-446655440030"
                order: 0
                interval: null
                action: "auto_email"
                note: "Initial outreach email"
                sequence:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                  _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                template:
                  id: "ee0e8400-e29b-41d4-a716-446655440080"
                  _href: "/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440080"
                hasAbVariants: true
                analytics:
                  email:
                    delivered: 412
                    opened: 198
                    clicked: 41
                    replied: 23
                    bounced: 7
                    unsubscribed: 2
                  calls: null
                  linkedIn: null
                createdAt: "2025-10-01T10:00:00.000Z"
                updatedAt: "2025-10-01T10:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    patch:
      operationId: updateSequenceStep
      summary: Update a sequence step
      description: |
        Updates a sequence step's `action`, `interval`, `name`, `note`,
        and/or email content. `interval` is in MINUTES. At least one field
        must be provided. Same interval-only, no-AI-fields scope as
        `POST /sequenceSteps` — see its description.

        `action: "automatic_linkedin_message"` is rejected (`400`) for the
        same reason as create. Changing `action` across channels (e.g.
        `call` → `manual_email`) is rejected by SEP with `422` — delete the
        step and create a new one of the desired type instead.

        Email content is written via `emailSubject`/`emailBody` and read
        back via `step.template.id` → `GET /emailTemplate/{id}`. Reply-action
        steps always have their template subject forced to `""`.

        This endpoint does not accept `order` — to reposition a step within
        its sequence, use `POST /sequences/{id}/actions/reorderSteps`
        instead.

        OAuth callers with the `user` role can only update steps of
        sequences they own (`403 INSUFFICIENT_PERMISSION` otherwise);
        `admin` and `manager` roles can update any sequence's steps in the
        workspace.

        Example:
        ```bash
        curl -X PATCH 'https://partner-api.nooks.in/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{ "data": { "interval": 2880, "note": "Wait two days instead" } }'
        ```
      tags:
        - SequenceSteps
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence step
          schema:
            type: string
            format: uuid
          example: "aa0e8400-e29b-41d4-a716-446655440030"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for updating a sequence step
              additionalProperties: false
              properties:
                data:
                  type: object
                  minProperties: 1
                  additionalProperties: false
                  description: At least one field is required.
                  properties:
                    action:
                      type: string
                      enum:
                        - call
                        - manual_email
                        - auto_email
                        - manual_email_reply
                        - auto_email_reply
                        - basic_task
                        - linkedin_connection_request
                        - manual_linkedin_connection_request
                        - manual_linkedin_message
                        - manual_sms
                        - auto_sms
                        - manual_whatsapp
                      description: |
                        New action type. Must stay within the same channel
                        as the step's current action (see description).
                        Unlike create, `manual_linkedin_connection_request`
                        IS accepted here — converting an existing step to it
                        is supported even though creating one directly is not.
                      example: "auto_email"
                    interval:
                      type: integer
                      minimum: 0
                      description: New number of minutes to wait before executing this step
                      example: 2880
                    name:
                      type: string
                      nullable: true
                      maxLength: 100
                      description: New display name, or null to clear it
                      example: "Follow-up (2 days)"
                    note:
                      type: string
                      nullable: true
                      description: New note or instructions, or null to clear it
                      example: "Wait two days instead"
                    emailSubject:
                      type: string
                      description: New email subject. Only valid when the step's action (current, or the `action` in this request) is an email action — otherwise `400` when `action` rides in the same request, `422` when checked against the step's current action.
                      example: "Following up on {{company}}"
                    emailBody:
                      type: string
                      description: New email body HTML. Same email-actions-only rule as `emailSubject`.
                      example: "<p>Hi {{firstName}}, following up...</p>"
              required:
                - data
            examples:
              rescheduleAndNote:
                summary: Change interval and note
                value:
                  data: { interval: 2880, note: "Wait two days instead" }
              updateEmailContent:
                summary: Update the step's email content
                value:
                  data:
                    emailSubject: "Following up on {{company}}"
                    emailBody: "<p>Hi {{firstName}}, following up...</p>"
      responses:
        "200":
          description: Sequence step updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SequenceStep"
              examples:
                rescheduleAndNote:
                  summary: interval/note updated
                  value:
                    id: "aa0e8400-e29b-41d4-a716-446655440030"
                    order: 0
                    name: null
                    interval: 2880
                    action: "auto_email"
                    note: "Wait two days instead"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template:
                      id: "ee0e8400-e29b-41d4-a716-446655440080"
                      _href: "/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440080"
                    hasAbVariants: true
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2026-01-15T09:30:00.000Z"
                updateEmailContent:
                  summary: |
                    Email content updated — same template.id, its
                    subject/body were overwritten in place
                  value:
                    id: "aa0e8400-e29b-41d4-a716-446655440030"
                    order: 0
                    name: null
                    interval: 4320
                    action: "auto_email"
                    note: "Initial outreach email"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    template:
                      id: "ee0e8400-e29b-41d4-a716-446655440080"
                      _href: "/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440080"
                    hasAbVariants: true
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2026-01-15T09:35:00.000Z"
        "400":
          description: |
            Bad request — Zod validation failed (no fields provided, an
            unknown field was sent, or
            `action: "automatic_linkedin_message"` was requested).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INVALID_REQUEST_PARAMETERS"
                  message: "Invalid request parameters"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `sequence-steps:write`
            scope (`INSUFFICIENT_SCOPE`), or the caller's role does not
            permit acting on this step's sequence (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          description: |
            Unprocessable entity — SEP rejected the update: a cross-channel
            `action` change, an `action` change while an A/B experiment
            exists on this step, or an `auto_email` step ending up with an
            empty subject.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                crossChannelChange:
                  summary: action change crosses channels
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Cross-channel step action changes are not supported. Delete this step and add a new one of the desired type."
                missingAutoEmailSubject:
                  summary: Resulting auto_email step has no subject
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Automatic email steps that start a new thread require a subject"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteSequenceStep
      summary: Delete a sequence step
      description: |
        Deletes a sequence step. SEP handles enrollment side effects
        (in-flight tasks on this step, advancing affected enrollments).

        OAuth callers with the `user` role can only delete steps of
        sequences they own (`403 INSUFFICIENT_PERMISSION` otherwise);
        `admin` and `manager` roles can delete any sequence's steps in the
        workspace.

        Example:
        ```bash
        curl -X DELETE 'https://partner-api.nooks.in/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceSteps
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence step to delete
          schema:
            type: string
            format: uuid
          example: "aa0e8400-e29b-41d4-a716-446655440030"
      responses:
        "204":
          description: Sequence step deleted
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `sequence-steps:write`
            scope (`INSUFFICIENT_SCOPE`), or the caller's role does not
            permit acting on this step's sequence (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /emailTemplate/{id}:
    get:
      operationId: getEmailTemplate
      summary: Get email template by ID
      description: |
        Returns the full content of a single email template by its unique identifier.
        Template IDs are typically discovered via the `template` reference on a sequence step
        (use `GET /v1/sequenceSteps/{id}?include=template` to inline the template DTO directly).

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emailTemplate/ee0e8400-e29b-41d4-a716-446655440080' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - EmailTemplates
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the email template
          schema:
            type: string
            format: uuid
          example: "ee0e8400-e29b-41d4-a716-446655440080"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailTemplate"
              example:
                id: "ee0e8400-e29b-41d4-a716-446655440080"
                name: "Initial outreach"
                subject: "Quick question, {{firstName}}"
                htmlBody: "<p>Hi {{firstName}},</p><p>...</p>"
                source: "manual"
                archived: false
                order: 0
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                privacy: "team_editable"
                createdAt: "2025-10-01T10:00:00.000Z"
                updatedAt: "2025-10-01T10:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /users:
    get:
      operationId: listUsers
      summary: List users
      description: |
        Returns a paginated list of users in the workspace. Only users with active seat assignments are returned.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/users?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by email:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/users?filter[email]=jane@example.com' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by CRM id — looks up the Nooks user mapped to the given external
        id in the connected CRM (e.g. a Salesforce `User.Id` / `OwnerId`).
        Returns an empty list if no Nooks user is mapped to that id:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/users?filter[crmId]=0051a000000XyzABC' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Users
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - name: filter[name]
          in: query
          description: Filter users by exact name match
          required: false
          schema:
            type: string
          example: "Jane Doe"
        - name: filter[email]
          in: query
          description: Filter users by exact email match.
          required: false
          schema:
            type: string
            format: email
          example: "jane@example.com"
        - name: filter[crmId]
          in: query
          description: >
            Filter to the Nooks user mapped to the given id in the customer's
            CRM (e.g. a Salesforce User.Id or OwnerId). Resolved via the
            workspace's CRM user mappings. Returns an empty list if no Nooks
            user is mapped to the given id.
          required: false
          schema:
            type: string
          example: "0051a000000XyzABC"
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/User"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    name: "Jane Doe"
                    email: "jane@example.com"
                    crmId: "0051a000000XyzABC"
                    createdAt: "2025-09-29T22:10:51.271Z"
                    updatedAt: "2025-10-07T20:42:17.281Z"
                  - id: "1erKK6gEL7ZUDFPEjCiHfV02fFD3"
                    name: "John Smith"
                    email: "john@example.com"
                    crmId: null
                    createdAt: "2025-10-06T14:39:35.322Z"
                    updatedAt: "2026-02-27T22:04:32.536Z"
                links:
                  first: "https://partner-api.nooks.in/v1/users?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/users?page[size]=50&page[after]=eyJpZCI6IjFlcktLNmdFTDdaVURGUEVqQ2lIZlYwMmZGRDMiLCJ2IjoxfQ"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /users/{id}:
    get:
      operationId: getUser
      summary: Get user by ID
      description: |
        Returns a single user by their unique identifier. Returns 404 if the user does not exist or does not have a seat assignment in the workspace.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the user
          schema:
            type: string
          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
              example:
                id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                name: "Jane Doe"
                email: "jane@example.com"
                crmId: "0051a000000XyzABC"
                createdAt: "2025-09-29T22:10:51.271Z"
                updatedAt: "2025-10-07T20:42:17.281Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sequenceStates:
    get:
      operationId: listSequenceStates
      summary: List sequence states
      description: |
        Returns a paginated list of sequence states (enrollments). Supports filtering by sequence, prospect, account, and state.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceStates?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by sequence:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceStates?filter[sequence][id]=550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by account (joins through prospect):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceStates?filter[account][id]=990e8400-e29b-41d4-a716-446655440099' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by state:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceStates?filter[state]=active,paused' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceStates
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - name: filter[sequence][id]
          in: query
          description: Filter by sequence ID
          required: false
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: filter[prospect][id]
          in: query
          description: Filter by prospect ID
          required: false
          schema:
            type: string
            format: uuid
          example: "770e8400-e29b-41d4-a716-446655440003"
        - name: filter[account][id]
          in: query
          description: Filter by account ID (matches the prospect's account).
          required: false
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440099"
        - name: filter[state]
          in: query
          description: "Filter by state (comma-separated). Valid values: active, paused, finished"
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [active, paused, finished]
          style: form
          explode: false
          example: [active, paused]
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `sequence`, `prospect`, `creator`, `sequenceStep`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [sequence, prospect, creator, sequenceStep]
            maxItems: 3
          style: form
          explode: false
          example: [sequence, prospect]
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SequenceState"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "880e8400-e29b-41d4-a716-446655440010"
                    state: "active"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    prospect:
                      id: "770e8400-e29b-41d4-a716-446655440003"
                      _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                    account:
                      id: "990e8400-e29b-41d4-a716-446655440099"
                      _href: "/v1/accounts/990e8400-e29b-41d4-a716-446655440099"
                    sequenceStep:
                      id: "aa0e8400-e29b-41d4-a716-446655440030"
                      _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                    creator:
                      id: "660e8400-e29b-41d4-a716-446655440001"
                      _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                    createdAt: "2025-11-01T10:00:00.000Z"
                    updatedAt: "2025-11-05T14:30:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/sequenceStates?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/sequenceStates?page[size]=50&page[after]=eyJpZCI6Ijg4MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAxMCIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createSequenceState
      summary: Enroll a prospect in a sequence
      description: |
        Enrolls a prospect into a sequence, creating a new sequence state.

        When enrolling on behalf of a user, respect the sequence's `privacy`
        (returned by `GET /v1/sequences/{id}`): enroll into `private` sequences
        only on behalf of their owner.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/sequenceStates' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "prospect": { "id": "770e8400-e29b-41d4-a716-446655440003" },
              "sequence": { "id": "550e8400-e29b-41d4-a716-446655440000" },
              "owner": { "id": "KKLvN5wQoghWFwUvC75CZ12QM7I3" },
              "mailbox": { "id": "bb0e8400-e29b-41d4-a716-446655440040" },
              "emailOverrides": {
                "aa0e8400-e29b-41d4-a716-446655440050": {
                  "subject": "Custom subject for step",
                  "body": "<p>Custom email body</p>"
                }
              }
            }
          }'
        ```
      tags:
        - SequenceStates
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for enrolling a prospect in a sequence
              properties:
                data:
                  type: object
                  additionalProperties: false
                  properties:
                    prospect:
                      type: object
                      description: The prospect to enroll
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: ID of the prospect
                          example: "770e8400-e29b-41d4-a716-446655440003"
                      required:
                        - id
                    sequence:
                      type: object
                      description: The sequence to enroll the prospect in
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: ID of the sequence
                          example: "550e8400-e29b-41d4-a716-446655440000"
                      required:
                        - id
                    owner:
                      type: object
                      description: The user who owns this enrollment
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                          minLength: 1
                          maxLength: 128
                          pattern: "^[A-Za-z0-9_-]{1,128}$"
                          description: ID of the owner user (Firebase UID)
                          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                      required:
                        - id
                    state:
                      type: string
                      enum:
                        - active
                        - paused
                      default: "active"
                      description: Initial state of the enrollment
                      example: "active"
                    mailbox:
                      type: object
                      description: The mailbox to use for email outreach (optional, defaults to user's primary email)
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                          format: uuid
                          description: ID of the mailbox
                          example: "bb0e8400-e29b-41d4-a716-446655440040"
                      required:
                        - id
                    scheduledAt:
                      type: string
                      format: date-time
                      description: When to start the sequence (ISO 8601 with timezone offset). If omitted, starts immediately.
                      example: "2025-12-01T09:00:00-05:00"
                    emailOverrides:
                      type: object
                      description: |
                        Per-step email content overrides. Keys are sequence step IDs (UUIDs, obtainable via GET /sequenceSteps).
                        Values specify custom subject and/or body for that step. Omitted fields use the step's template defaults.
                        Returns 422 if any key is not a valid step ID within the target sequence.
                      propertyNames:
                        # Keys are validated against this UUID pattern by the
                        # API (Zod `z.record(z.string().uuid(), ...)`); a
                        # non-UUID key returns 400 INVALID_REQUEST_PARAMETERS.
                        pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
                      additionalProperties:
                        type: object
                        properties:
                          subject:
                            type: string
                            description: Custom email subject line
                            example: "Custom subject for this prospect"
                          body:
                            type: string
                            description: Custom email body (HTML)
                            example: "<p>Custom email body</p>"
                        additionalProperties: false
                      example:
                        "aa0e8400-e29b-41d4-a716-446655440050":
                          subject: "Custom subject for step"
                          body: "<p>Custom email body</p>"
                    enrich:
                      type: object
                      additionalProperties: false
                      description: |
                        Opt this enrollment into data enrichment, per channel. If the workspace forces
                        enrichment for a channel, that channel is enriched regardless of what you pass
                        here; a `false` or omitted channel simply defers to the workspace setting.
                      properties:
                        phoneNumbers:
                          type: boolean
                          description: Enrich the prospect's phone numbers on enrollment.
                          example: true
                        emails:
                          type: boolean
                          description: Enrich the prospect's email addresses on enrollment.
                          example: false
                  required:
                    - prospect
                    - sequence
                    - owner
              required:
                - data
            example:
              data:
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                sequence:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                state: "active"
                mailbox:
                  id: "bb0e8400-e29b-41d4-a716-446655440040"
                emailOverrides:
                  "aa0e8400-e29b-41d4-a716-446655440050":
                    subject: "Custom subject for step"
                    body: "<p>Custom email body</p>"
                enrich:
                  phoneNumbers: true
                  emails: false
      responses:
        "201":
          description: Prospect successfully enrolled in sequence
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SequenceState"
              example:
                id: "880e8400-e29b-41d4-a716-446655440010"
                state: "active"
                createdAt: "2025-11-01T10:00:00.000Z"
                updatedAt: "2025-11-01T10:00:00.000Z"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                  _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                sequenceStep:
                  id: "aa0e8400-e29b-41d4-a716-446655440030"
                  _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Conflict - prospect is already enrolled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "CONFLICT"
                  message: "Conflict"
                  details:
                    message: "Prospect is already enrolled in this sequence"
        "422":
          description: |
            Unprocessable entity. Possible causes:
            - Enrollment prerequisites not met (e.g. prospect missing a valid email)
            - `emailOverrides` contains step IDs that do not belong to the target sequence
            - `owner.id` is not a user in this workspace (use an id from `GET /v1/users`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                missingEmail:
                  summary: Prospect missing valid email
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Prospect does not have a valid primary email"
                ownerNotFound:
                  summary: Owner is not a user in the workspace
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Owner user KKLvN5wQoghWFwUvC75CZ12QM7I3 not found in this workspace. Use a user id from GET /v1/users."
                invalidStepIds:
                  summary: emailOverrides references invalid step IDs
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "emailOverrides contains step IDs not in sequence 550e8400-...: aa0e8400-..., bb0e8400-..."
        "500":
          $ref: "#/components/responses/InternalError"

  /sequenceStates/{id}:
    get:
      operationId: getSequenceState
      summary: Get sequence state by ID
      description: |
        Returns a single sequence state by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440010' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceStates
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence state
          schema:
            type: string
            format: uuid
          example: "880e8400-e29b-41d4-a716-446655440010"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `sequence`, `prospect`, `creator`, `sequenceStep`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [sequence, prospect, creator, sequenceStep]
            maxItems: 3
          style: form
          explode: false
          example: [sequence, prospect]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SequenceState"
              example:
                id: "880e8400-e29b-41d4-a716-446655440010"
                state: "active"
                sequence:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                  _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                account:
                  id: "990e8400-e29b-41d4-a716-446655440099"
                  _href: "/v1/accounts/990e8400-e29b-41d4-a716-446655440099"
                sequenceStep:
                  id: "aa0e8400-e29b-41d4-a716-446655440030"
                  _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                creator:
                  id: "660e8400-e29b-41d4-a716-446655440001"
                  _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                createdAt: "2025-11-01T10:00:00.000Z"
                updatedAt: "2025-11-05T14:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      operationId: deleteSequenceState
      summary: Remove prospect from sequence
      description: |
        Removes a prospect from a sequence by deleting the sequence state. This unenrolls the prospect and cancels any pending tasks.

        Example:
        ```bash
        curl -X DELETE 'https://partner-api.nooks.in/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440010' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceStates
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence state to remove
          schema:
            type: string
            format: uuid
          example: "880e8400-e29b-41d4-a716-446655440010"
      responses:
        "204":
          description: Prospect successfully removed from sequence
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sequenceStates/{id}/actions/finish:
    post:
      operationId: finishSequenceState
      summary: Finish a sequence state
      description: |
        Marks a sequence state as finished (complete). This stops the prospect's progression
        through the sequence and performs cleanup of pending tasks.

        This endpoint is idempotent: returns 204 even if the sequence state is already finished.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440003/actions/finish' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - SequenceStates
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the sequence state
          schema:
            type: string
            format: uuid
          example: "880e8400-e29b-41d4-a716-446655440003"
      responses:
        "204":
          description: Sequence state finished (or already finished)
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /prospects:
    get:
      operationId: listProspects
      summary: List prospects
      description: |
        Returns a paginated list of prospects. Supports filtering by prospect IDs, sequence enrollment, or account.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/prospects?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by specific IDs:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/prospects?filter[id]=UUID1,UUID2' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by sequence enrollment:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/prospects?filter[sequence][id]=550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by account:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/prospects?filter[account][id]=990e8400-e29b-41d4-a716-446655440099' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Prospects
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - $ref: "#/components/parameters/FilterId"
        - name: filter[name]
          in: query
          description: Filter prospects by exact name match
          required: false
          schema:
            type: string
          example: "Jane Doe"
        - name: filter[title]
          in: query
          description: Filter prospects by exact job title match
          required: false
          schema:
            type: string
          example: "VP of Sales"
        - name: filter[primaryEmail]
          in: query
          description: >-
            Comma-separated list of email addresses to filter by (up to 10).
            Returns prospects whose primary email matches any of the provided values.
          required: false
          schema:
            type: string
          example: "jane@acme.com,john@acme.com"
        - name: filter[crmId]
          in: query
          description: Filter prospects by CRM external ID
          required: false
          schema:
            type: string
          example: "003ABC123DEF456"
        - name: filter[sequence][id]
          in: query
          description: Filter prospects enrolled in a specific sequence
          required: false
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: filter[account][id]
          in: query
          description: Filter prospects associated with a specific account
          required: false
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440099"
        - name: filter[updatedAt][gte]
          in: query
          description: Filter prospects updated at or after this timestamp (ISO 8601)
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-03-01T00:00:00Z"
        - name: filter[updatedAt][lte]
          in: query
          description: Filter prospects updated at or before this timestamp (ISO 8601)
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-03-31T23:59:59Z"
        - name: include
          in: query
          description: |
            Comma-separated relations to expand inline. Valid values: `sequenceStates`. Max 3.
            Note: `account` is not yet includable — fetch via the `_href` on the `account` reference instead.
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [sequenceStates]
            maxItems: 3
          style: form
          explode: false
          example: [sequenceStates]
        - $ref: "#/components/parameters/IncludeCRMCustomFields"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Prospect"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "990e8400-e29b-41d4-a716-446655440020"
                    name: "Jane Doe"
                    firstName: "Jane"
                    lastName: "Doe"
                    title: "VP of Engineering"
                    primaryEmail: "jane@acme.com"
                    linkedInUrl: "https://linkedin.com/in/janedoe"
                    crmId: "003ABC123DEF456"
                    account:
                      id: "aa0e8400-e29b-41d4-a716-446655440030"
                      _href: "/v1/accounts/aa0e8400-e29b-41d4-a716-446655440030"
                    sequenceStates:
                      - id: "880e8400-e29b-41d4-a716-446655440010"
                        _href: "/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440010"
                    crmCustomFields: {}
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2025-11-15T14:30:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/prospects?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/prospects?page[size]=50&page[after]=eyJpZCI6Ijk5MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAyMCIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /integrations/prospects/sync:
    post:
      operationId: syncProspects
      summary: Sync prospects from CRM
      description: |
        Syncs up to 100 CRM records (Salesforce or HubSpot) into Nooks prospects by `externalId`, creating them if they don't already exist. Per-record outcomes are returned in `results` and `errors`.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/integrations/prospects/sync' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "type": "salesforce",
              "records": ["003ABC123DEF456", "003XYZ789GHI012"]
            }
          }'
        ```
      tags:
        - Prospects
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncProspectsRequest"
            example:
              data:
                type: "salesforce"
                records:
                  - "003ABC123DEF456"
                  - "003XYZ789GHI012"
      responses:
        "200":
          description: Request processed. Inspect `results` and `errors` for per-record outcomes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncProspectsResponse"
              example:
                results:
                  - externalId: "003ABC123DEF456"
                    prospectId: "990e8400-e29b-41d4-a716-446655440020"
                    created: true
                errors:
                  - externalId: "003XYZ789GHI012"
                    code: "NOT_FOUND_IN_CRM"
                    message: "No record found in salesforce for externalId 003XYZ789GHI012"
        "400":
          description: Malformed body (`Error`), or the CRM isn't usable (`NO_CRM_CONNECTION`). See the `NoCrmConnectionError` schema.
          content:
            application/json:
              schema:
                # anyOf, not oneOf: NoCrmConnectionError is a specialization of
                # the standard Error envelope (same `error.{code,message}` plus
                # `integrationType`), so a NoCrmConnectionError body also
                # validates as a plain Error. oneOf requires matching exactly
                # one and would (correctly) reject that overlap.
                anyOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/NoCrmConnectionError"
              example:
                error:
                  code: "NO_CRM_CONNECTION"
                  message: "Your salesforce connection is no longer valid. Reconnect salesforce in Nooks."
                  integrationType: "salesforce"
                traceId: "6b1c9f2e8d3a4c5b7e9f0a1d2c3b4a5e"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /prospects/{id}:
    get:
      operationId: getProspect
      summary: Get prospect by ID
      description: |
        Returns a single prospect by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/prospects/990e8400-e29b-41d4-a716-446655440020' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        With CRM custom fields (adds a `crmCustomFields` object keyed by CRM field id):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/prospects/990e8400-e29b-41d4-a716-446655440020?includeCRMCustomFields=true' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Prospects
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the prospect
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440020"
        - name: include
          in: query
          description: |
            Comma-separated relations to expand inline. Valid values: `sequenceStates`. Max 3.
            Note: `account` is not yet includable — fetch via the `_href` on the `account` reference instead.
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [sequenceStates]
            maxItems: 3
          style: form
          explode: false
          example: [sequenceStates]
        - $ref: "#/components/parameters/IncludeCRMCustomFields"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Prospect"
              example:
                id: "990e8400-e29b-41d4-a716-446655440020"
                name: "Jane Doe"
                firstName: "Jane"
                lastName: "Doe"
                title: "VP of Engineering"
                primaryEmail: "jane@acme.com"
                linkedInUrl: "https://linkedin.com/in/janedoe"
                account:
                  id: "aa0e8400-e29b-41d4-a716-446655440030"
                  _href: "/v1/accounts/aa0e8400-e29b-41d4-a716-446655440030"
                sequenceStates:
                  - id: "880e8400-e29b-41d4-a716-446655440010"
                    _href: "/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440010"
                crmCustomFields: {}
                createdAt: "2025-10-01T10:00:00.000Z"
                updatedAt: "2025-11-15T14:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /prospects/{id}/notes:
    post:
      operationId: createProspectNote
      summary: Create a prospect note
      description: |
        Creates a CRM note on a CRM-backed prospect. The prospect must be
        sourced from Salesforce or HubSpot, and `data.integrationType` must
        match that CRM source.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/prospects/990e8400-e29b-41d4-a716-446655440020/notes' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "text": "Follow up after legal review.",
              "integrationType": "salesforce"
            }
          }'
        ```
      tags:
        - Notes
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the prospect
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440020"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateNoteRequest"
            example:
              data:
                text: "Follow up after legal review."
                integrationType: "salesforce"
      responses:
        "201":
          description: Note successfully created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Note"
              example:
                id: "bb0e8400-e29b-41d4-a716-446655440080"
                externalId: "00TABC123DEF456"
                text: "Follow up after legal review."
                associatedType: "prospect"
                associated:
                  id: "990e8400-e29b-41d4-a716-446655440020"
                  _href: "/v1/prospects/990e8400-e29b-41d4-a716-446655440020"
                integrationType: "salesforce"
                userExternalId: "005ABC123DEF456"
                userName: "Sam Seller"
                externalCreatedAt: "2026-07-09T16:00:00.000Z"
                externalUpdatedAt: "2026-07-09T16:00:00.000Z"
                createdAt: "2026-07-09T16:00:01.000Z"
                updatedAt: "2026-07-09T16:00:01.000Z"
        "400":
          description: Malformed body (`Error`), or the CRM isn't usable for the acting user (`NO_CRM_CONNECTION`). See the `NoCrmConnectionError` schema.
          content:
            application/json:
              schema:
                # anyOf, not oneOf: NoCrmConnectionError is a specialization of
                # the standard Error envelope (same `error.{code,message}` plus
                # `integrationType`), so a NoCrmConnectionError body also
                # validates as a plain Error. oneOf requires matching exactly
                # one and would (correctly) reject that overlap.
                anyOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/NoCrmConnectionError"
              example:
                error:
                  code: "NO_CRM_CONNECTION"
                  message: "The user this request acts as has no salesforce connection in Nooks. Connect salesforce for that user and try again — API-key requests act as a workspace admin."
                  integrationType: "salesforce"
                traceId: "6b1c9f2e8d3a4c5b7e9f0a1d2c3b4a5e"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          description: |
            Unprocessable entity. Possible causes:
            - `integrationType` does not match the prospect's CRM source
            - The CRM rejected the note creation request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "UNPROCESSABLE_ENTITY"
                  message: "Unprocessable entity"
                  details:
                    message: "integrationType must match the prospect source (hubspot)"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /accounts:
    get:
      operationId: listAccounts
      summary: List accounts
      description: |
        Returns a paginated list of accounts (companies) in the workspace.
        Supports filtering by indexed account columns.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/accounts?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by domain:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/accounts?filter[domain]=acme.com' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Note: results are restricted to accounts sourced from a customer
        CRM (`salesforce`, `hubspot`).
      tags:
        - Accounts
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - name: filter[id]
          in: query
          description: Comma-separated list of account UUIDs to fetch
          required: false
          schema:
            type: string
          example: "aa0e8400-e29b-41d4-a716-446655440030,bb0e8400-e29b-41d4-a716-446655440031"
        - name: filter[name]
          in: query
          description: Filter accounts by exact name match
          required: false
          schema:
            type: string
          example: "Acme Corp"
        - name: filter[domain]
          in: query
          description: Filter accounts by exact domain match
          required: false
          schema:
            type: string
          example: "acme.com"
        - name: filter[crmId]
          in: query
          description: Filter accounts by CRM external ID
          required: false
          schema:
            type: string
          example: "0014x00000ABCDEF"
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
        - $ref: "#/components/parameters/IncludeCRMCustomFields"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Account"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "aa0e8400-e29b-41d4-a716-446655440030"
                    name: "Acme Corp"
                    domain: "acme.com"
                    numEmployees: 500
                    linkedInUrl: "https://linkedin.com/company/acme"
                    description: "B2B SaaS for the manufacturing sector."
                    crmCustomFields: {}
                    createdAt: "2025-09-01T10:00:00.000Z"
                    updatedAt: "2025-11-15T14:30:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/accounts?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/accounts?page[size]=50&page[after]=eyJpZCI6ImFhMGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAzMCIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /accounts/{id}:
    get:
      operationId: getAccount
      summary: Get account by ID
      description: |
        Returns a single account by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/accounts/aa0e8400-e29b-41d4-a716-446655440030' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        With CRM custom fields (adds a `crmCustomFields` object keyed by CRM field id):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/accounts/aa0e8400-e29b-41d4-a716-446655440030?includeCRMCustomFields=true' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Accounts
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the account
          schema:
            type: string
            format: uuid
          example: "aa0e8400-e29b-41d4-a716-446655440030"
        - $ref: "#/components/parameters/IncludeCRMCustomFields"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
              example:
                id: "aa0e8400-e29b-41d4-a716-446655440030"
                name: "Acme Corp"
                domain: "acme.com"
                numEmployees: 500
                linkedInUrl: "https://linkedin.com/company/acme"
                description: "B2B SaaS for the manufacturing sector."
                crmCustomFields: {}
                createdAt: "2025-09-01T10:00:00.000Z"
                updatedAt: "2025-11-15T14:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /accounts/{id}/notes:
    post:
      operationId: createAccountNote
      summary: Create an account note
      description: |
        Creates a CRM note on a CRM-backed account. The account must be
        sourced from Salesforce or HubSpot, and `data.integrationType` must
        match that CRM source.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/accounts/aa0e8400-e29b-41d4-a716-446655440030/notes' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "text": "Champion moved teams; confirm new buying committee.",
              "integrationType": "hubspot"
            }
          }'
        ```
      tags:
        - Notes
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the account
          schema:
            type: string
            format: uuid
          example: "aa0e8400-e29b-41d4-a716-446655440030"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateNoteRequest"
            example:
              data:
                text: "Champion moved teams; confirm new buying committee."
                integrationType: "hubspot"
      responses:
        "201":
          description: Note successfully created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Note"
              example:
                id: "cc0e8400-e29b-41d4-a716-446655440081"
                externalId: "28491827364"
                text: "Champion moved teams; confirm new buying committee."
                associatedType: "account"
                associated:
                  id: "aa0e8400-e29b-41d4-a716-446655440030"
                  _href: "/v1/accounts/aa0e8400-e29b-41d4-a716-446655440030"
                integrationType: "hubspot"
                userExternalId: null
                userName: "Sam Seller"
                externalCreatedAt: "2026-07-09T16:00:00.000Z"
                externalUpdatedAt: "2026-07-09T16:00:00.000Z"
                createdAt: "2026-07-09T16:00:01.000Z"
                updatedAt: "2026-07-09T16:00:01.000Z"
        "400":
          description: Malformed body (`Error`), or the CRM isn't usable for the acting user (`NO_CRM_CONNECTION`). See the `NoCrmConnectionError` schema.
          content:
            application/json:
              schema:
                # anyOf, not oneOf: NoCrmConnectionError is a specialization of
                # the standard Error envelope (same `error.{code,message}` plus
                # `integrationType`), so a NoCrmConnectionError body also
                # validates as a plain Error. oneOf requires matching exactly
                # one and would (correctly) reject that overlap.
                anyOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/NoCrmConnectionError"
              example:
                error:
                  code: "NO_CRM_CONNECTION"
                  message: "The user this request acts as has no hubspot connection in Nooks. Connect hubspot for that user and try again — API-key requests act as a workspace admin."
                  integrationType: "hubspot"
                traceId: "6b1c9f2e8d3a4c5b7e9f0a1d2c3b4a5e"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          description: |
            Unprocessable entity. Possible causes:
            - `integrationType` does not match the account's CRM source
            - The CRM rejected the note creation request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "UNPROCESSABLE_ENTITY"
                  message: "Unprocessable entity"
                  details:
                    message: "CRM rejected the note creation request"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /mailboxes:
    get:
      operationId: listMailboxes
      summary: List mailboxes
      description: |
        Returns a paginated list of mailboxes (email aliases).

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/mailboxes?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by user:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/mailboxes?filter[userId]=KKLvN5wQoghWFwUvC75CZ12QM7I3' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Mailboxes
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - name: filter[userId]
          in: query
          description: Filter mailboxes belonging to a specific user (Firebase UID)
          required: false
          schema:
            type: string
            minLength: 1
          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `user`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [user]
            maxItems: 3
          style: form
          explode: false
          example: [user]
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Mailbox"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "bb0e8400-e29b-41d4-a716-446655440040"
                    email: "jane@acme.com"
                    isDefault: true
                    user:
                      id: "660e8400-e29b-41d4-a716-446655440001"
                      _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2025-11-15T14:30:00.000Z"
                  - id: "cc0e8400-e29b-41d4-a716-446655440041"
                    email: "jane.alt@acme.com"
                    isDefault: false
                    user:
                      id: "660e8400-e29b-41d4-a716-446655440001"
                      _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                    createdAt: "2025-10-05T12:00:00.000Z"
                    updatedAt: "2025-11-20T16:00:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/mailboxes?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/mailboxes?page[size]=50&page[after]=eyJpZCI6ImNjMGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDA0MSIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /mailboxes/{id}:
    get:
      operationId: getMailbox
      summary: Get mailbox by ID
      description: |
        Returns a single mailbox by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/mailboxes/bb0e8400-e29b-41d4-a716-446655440040' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Mailboxes
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the mailbox
          schema:
            type: string
            format: uuid
          example: "bb0e8400-e29b-41d4-a716-446655440040"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `user`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [user]
            maxItems: 3
          style: form
          explode: false
          example: [user]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mailbox"
              example:
                id: "bb0e8400-e29b-41d4-a716-446655440040"
                email: "jane@acme.com"
                isDefault: true
                user:
                  id: "660e8400-e29b-41d4-a716-446655440001"
                  _href: "/v1/users/660e8400-e29b-41d4-a716-446655440001"
                createdAt: "2025-10-01T10:00:00.000Z"
                updatedAt: "2025-11-15T14:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /emails:
    get:
      operationId: listEmails
      summary: List emails
      description: |
        Returns a paginated list of emails. Supports filtering by ID, prospect, task, status, and updatedAt range.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by prospect:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails?filter[prospect][id]=770e8400-e29b-41d4-a716-446655440003' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by task (the sequence task that triggered the email):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails?filter[task][id]=990e8400-e29b-41d4-a716-446655440099' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by account (joins through the email's prospect):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails?filter[account][id]=990e8400-e29b-41d4-a716-446655440099' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by delivery status:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails?filter[status]=sent,failed' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by updatedAt range:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails?filter[updatedAt][gte]=2025-01-01T00:00:00Z' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Emails
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - $ref: "#/components/parameters/FilterId"
        - name: filter[prospect][id]
          in: query
          description: Filter emails for a specific prospect
          required: false
          schema:
            type: string
            format: uuid
          example: "770e8400-e29b-41d4-a716-446655440003"
        - name: filter[task][id]
          in: query
          description: Filter emails by the sequence task they were sent for. Matches Email.sepTaskId (null for emails unrelated to a sequence task).
          required: false
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440099"
        - name: filter[account][id]
          in: query
          description: |
            Filter emails associated with a specific account. Joins through
            the email's prospect (`Prospect.accountId`); rows whose prospect
            has no account, or rows with no prospect, are excluded.
          required: false
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440099"
        - name: filter[status]
          in: query
          description: |
            Filter emails by delivery status. Accepts a comma-separated list;
            rows matching any of the provided statuses are returned. Note that
            legacy rows may carry statuses outside this set (e.g. `delivered`,
            `sent_from_gmail`) — filtering is case-sensitive and exact, so
            those rows will not match `sent`.
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [draft, in_progress, sent, failed]
          style: form
          explode: false
          example: [sent, failed]
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `prospect`, `sequence`, `sequenceStep`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [prospect, sequence, sequenceStep]
            maxItems: 3
          style: form
          explode: false
          example: [prospect, sequence]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Email"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createEmail
      summary: Create an email
      description: |
        Creates a one-off email to a prospect on behalf of a workspace user
        (`ownerId`), and optionally sends it immediately.

        Nooks creates a `manual_email` task for the prospect with a draft
        email attached, writes the given content to the draft, and — when
        `send: true` — submits it through the owner's connected mailbox,
        applying the workspace's deliverability safeguards (unsubscribe
        checks, pre-bounce validation, tracking, send spacing). With
        `send: false` (the default) the email is left as a draft the owner
        can review and send from Nooks.

        Recipients default to the prospect's primary email; pass
        `recipients` to override them (required when the prospect has no
        primary email). The sender defaults to the owner's default mailbox
        alias; pass `from` to use another of the owner's aliases.

        Not transactional: the draft is created before the send. If a
        `send: true` request returns 422 for a send-time rejection, the
        draft email and its `manual_email` task already exist in the
        owner's Nooks task list — fix and resend from Nooks rather than
        retrying this endpoint, which would create a second draft.

        Example (create and send):
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/emails' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "prospectId": "770e8400-e29b-41d4-a716-446655440003",
              "ownerId": "KKLvN5wQoghWFwUvC75CZ12QM7I3",
              "subject": "Quick question",
              "bodyHtml": "<p>Hi Jane, ...</p>",
              "send": true
            }
          }'
        ```
      tags:
        - Emails
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for creating an email
              additionalProperties: false
              properties:
                data:
                  type: object
                  additionalProperties: false
                  properties:
                    prospectId:
                      type: string
                      format: uuid
                      description: ID of the prospect this email is for
                      example: "770e8400-e29b-41d4-a716-446655440003"
                    ownerId:
                      type: string
                      minLength: 1
                      maxLength: 128
                      pattern: "^[A-Za-z0-9_-]{1,128}$"
                      description: "ID of the user the email is created — and, with `send: true`, sent — on behalf of (Firebase UID)"
                      example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    subject:
                      type: string
                      minLength: 1
                      maxLength: 998
                      description: Email subject
                      example: "Quick question"
                    bodyHtml:
                      type: string
                      minLength: 1
                      description: Email body as HTML
                      example: "<p>Hi Jane, ...</p>"
                    from:
                      type: string
                      format: email
                      description: Sending mailbox alias. Must belong to the owner; defaults to the owner's default alias.
                      example: "rep@company.com"
                    recipients:
                      type: array
                      items:
                        type: string
                        format: email
                      minItems: 1
                      maxItems: 10
                      description: Recipient override. Defaults to the prospect's primary email. Required when the prospect has no primary email.
                      example: ["jane@acme.com"]
                    cc:
                      type: array
                      items:
                        type: string
                        format: email
                      maxItems: 10
                      description: CC recipients
                      example: ["manager@acme.com"]
                    bcc:
                      type: array
                      items:
                        type: string
                        format: email
                      maxItems: 10
                      description: BCC recipients
                      example: ["crm-bcc@company.com"]
                    send:
                      type: boolean
                      default: false
                      description: When `true`, sends the email immediately after creating it. When `false`, leaves it as a draft.
                  required:
                    - prospectId
                    - ownerId
                    - subject
                    - bodyHtml
              required:
                - data
            example:
              data:
                prospectId: "770e8400-e29b-41d4-a716-446655440003"
                ownerId: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                subject: "Quick question"
                bodyHtml: "<p>Hi Jane, ...</p>"
                send: true
      responses:
        "201":
          description: |
            Email created; when `send: true` it has also been submitted for
            sending (`status` is `in_progress`/`sent`; a rate-limited send is
            queued and retried automatically). Unsent drafts return
            `status: draft`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Email"
              example:
                id: "550e8400-e29b-41d4-a716-446655440000"
                type: "one_off"
                status: "in_progress"
                from: "rep@company.com"
                to: "jane@acme.com"
                recipients: ["jane@acme.com"]
                subject: "Quick question"
                bodyText: "Hi Jane, ..."
                bodyHtml: "<p>Hi Jane, ...</p>"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence: null
                sequenceStep: null
                date: null
                deliveredAt: null
                openedAt: null
                clickedAt: null
                bouncedAt: null
                repliedAt: null
                openCount: null
                clickCount: null
                createdAt: "2026-08-21T10:00:00.000Z"
                updatedAt: "2026-08-21T10:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden — the OAuth token lacks the `emails:write` scope
            (`INSUFFICIENT_SCOPE`), or the caller's role does not permit
            acting on behalf of `ownerId` (`INSUFFICIENT_PERMISSION`).
            API-key callers are not subject to this check.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: "INSUFFICIENT_PERMISSION"
                  message: "You do not have permission to perform this action"
        "422":
          description: |
            Unprocessable entity. Possible causes:
            - `ownerId` is not a user in this workspace (use an id from `GET /v1/users`)
            - The prospect does not exist, or has unsubscribed from emails
            - `send: true` and the send was rejected (e.g. pre-bounce block, no usable mailbox)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                prospectUnsubscribed:
                  summary: Prospect has unsubscribed from emails
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Cannot create email: prospect has unsubscribed from emails"
                ownerNotFound:
                  summary: Owner is not a user in the workspace
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Owner user KKLvN5wQoghWFwUvC75CZ12QM7I3 not found in this workspace. Use a user id from GET /v1/users."
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "500":
          $ref: "#/components/responses/InternalError"

  /emails/{id}:
    get:
      operationId: getEmail
      summary: Get email by ID
      description: |
        Returns a single email by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/emails/550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Emails
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the email
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `prospect`, `sequence`, `sequenceStep`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [prospect, sequence, sequenceStep]
            maxItems: 3
          style: form
          explode: false
          example: [prospect]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Email"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /calls:
    get:
      operationId: listCalls
      summary: List calls
      description: |
        Returns a paginated list of calls made on the Nooks SEP. Supports filtering by ID and updatedAt range.

        There is no fixed API-enforced historical lookback window for this
        endpoint. You can page through all retained Nooks SEP call records
        available in the workspace. Historical availability depends on when
        the workspace started using Nooks SEP and the workspace's retained call
        data. Use `filter[time]` to fetch by when the call occurred, or
        `filter[createdAt]` / `filter[updatedAt]` for ingestion and sync
        workflows.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/calls?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by updatedAt range:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/calls?filter[updatedAt][gte]=2025-01-01T00:00:00Z&filter[updatedAt][lt]=2026-01-01T00:00:00Z' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by IDs:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/calls?filter[id]=550e8400-e29b-41d4-a716-446655440000,660e8400-e29b-41d4-a716-446655440001' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by account:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/calls?filter[account][id]=990e8400-e29b-41d4-a716-446655440099' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by owner (user who placed the call):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/calls?filter[owner][id]=KKLvN5wQoghWFwUvC75CZ12QM7I3' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Calls
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - $ref: "#/components/parameters/FilterId"
        - $ref: "#/components/parameters/FilterUpdatedAtGte"
        - $ref: "#/components/parameters/FilterUpdatedAtLt"
        - name: filter[time][gte]
          in: query
          description: Filter calls that occurred at or after this timestamp (ISO 8601). `time` is the actual call time (distinct from `createdAt`, which is the ingestion time).
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-04-22T00:00:00Z"
        - name: filter[time][lte]
          in: query
          description: Filter calls that occurred at or before this timestamp (ISO 8601).
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-04-22T23:59:59Z"
        - name: filter[createdAt][gte]
          in: query
          description: Filter calls ingested at or after this timestamp (ISO 8601).
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-04-22T00:00:00Z"
        - name: filter[createdAt][lte]
          in: query
          description: Filter calls ingested at or before this timestamp (ISO 8601).
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-04-22T23:59:59Z"
        - name: filter[prospect][id]
          in: query
          description: Filter calls to a specific prospect.
          required: false
          schema:
            type: string
            format: uuid
          example: "770e8400-e29b-41d4-a716-446655440003"
        - name: filter[account][id]
          in: query
          description: |
            Filter calls associated with a specific account. Matches the
            account snapshot stored on the call at ingestion time.
          required: false
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440099"
        - name: filter[callDisposition][id]
          in: query
          description: Filter calls by their recorded disposition.
          required: false
          schema:
            type: string
            format: uuid
          example: "cc0e8400-e29b-41d4-a716-446655440040"
        - name: filter[owner][id]
          in: query
          description: |
            Filter calls placed by a specific user. Matched via the linked
            caller's `userId` (the User who owns the caller record).
          required: false
          schema:
            type: string
          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `prospect`, `sequence`, `sequenceStep`, `callDisposition`, `owner`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [prospect, sequence, sequenceStep, callDisposition, owner]
            maxItems: 3
          style: form
          explode: false
          example: [prospect, callDisposition]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Call"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "ff0e8400-e29b-41d4-a716-446655440060"
                    source: "nooks"
                    duration: 120.5
                    from: "+14155551234"
                    to: "+14155555678"
                    direction: "outgoing"
                    recordingUrl: "https://example.com/recordings/abc123"
                    prospect:
                      id: "770e8400-e29b-41d4-a716-446655440003"
                      _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    sequenceStep:
                      id: "aa0e8400-e29b-41d4-a716-446655440030"
                      _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                    callDisposition:
                      id: "dd0e8400-e29b-41d4-a716-446655440070"
                      _href: "/v1/callDispositions/dd0e8400-e29b-41d4-a716-446655440070"
                    owner:
                      id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                      _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    createdAt: "2025-11-01T10:00:00.000Z"
                    updatedAt: "2025-11-01T10:02:00.000Z"
                  - id: "ff1e8400-e29b-41d4-a716-446655440061"
                    source: "nooks"
                    duration: 45.0
                    from: "+14155551234"
                    to: "+14155559999"
                    direction: "outgoing"
                    recordingUrl: null
                    prospect: null
                    sequence: null
                    sequenceStep: null
                    callDisposition: null
                    owner: null
                    createdAt: "2025-11-02T14:00:00.000Z"
                    updatedAt: "2025-11-02T14:00:45.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/calls?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/calls?page[size]=50&page[after]=eyJpZCI6ImZmMWU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDA2MSIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /calls/{id}:
    get:
      operationId: getCall
      summary: Get call by ID
      description: |
        Returns a single call made on the Nooks SEP by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/calls/ff0e8400-e29b-41d4-a716-446655440060' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Calls
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the call
          schema:
            type: string
            format: uuid
          example: "ff0e8400-e29b-41d4-a716-446655440060"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `prospect`, `sequence`, `sequenceStep`, `callDisposition`, `owner`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [prospect, sequence, sequenceStep, callDisposition, owner]
            maxItems: 3
          style: form
          explode: false
          example: [prospect]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Call"
              example:
                id: "ff0e8400-e29b-41d4-a716-446655440060"
                source: "nooks"
                duration: 120.5
                from: "+14155551234"
                to: "+14155555678"
                direction: "outgoing"
                recordingUrl: "https://example.com/recordings/abc123"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                  _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                sequenceStep:
                  id: "aa0e8400-e29b-41d4-a716-446655440030"
                  _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                callDisposition:
                  id: "dd0e8400-e29b-41d4-a716-446655440070"
                  _href: "/v1/callDispositions/dd0e8400-e29b-41d4-a716-446655440070"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                createdAt: "2025-11-01T10:00:00.000Z"
                updatedAt: "2025-11-01T10:02:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /callDispositions:
    get:
      operationId: listCallDispositions
      summary: List call dispositions
      description: |
        Returns a paginated list of call dispositions for the workspace. Only dispositions with type `nooks_sep` are returned.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/callDispositions?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by name:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/callDispositions?filter[name]=Connected' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by IDs:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/callDispositions?filter[id]=UUID1,UUID2' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - CallDispositions
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - $ref: "#/components/parameters/FilterId"
        - name: filter[name]
          in: query
          description: Filter dispositions by exact name match
          required: false
          schema:
            type: string
          example: "Connected"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CallDisposition"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "dd0e8400-e29b-41d4-a716-446655440070"
                    name: "Connected"
                    callOutcome: "connected"
                    order: 1
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2025-10-01T10:00:00.000Z"
                  - id: "dd0e8400-e29b-41d4-a716-446655440071"
                    name: "Left Voicemail"
                    callOutcome: "voicemail"
                    order: 2
                    createdAt: "2025-10-01T10:00:00.000Z"
                    updatedAt: "2025-10-01T10:00:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/callDispositions?page[size]=50"
                  prev: null
                  next: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /callDispositions/{id}:
    get:
      operationId: getCallDisposition
      summary: Get call disposition by ID
      description: |
        Returns a single call disposition by its unique identifier. Only dispositions with type `nooks_sep` are accessible.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/callDispositions/dd0e8400-e29b-41d4-a716-446655440070' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - CallDispositions
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the call disposition
          schema:
            type: string
            format: uuid
          example: "dd0e8400-e29b-41d4-a716-446655440070"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallDisposition"
              example:
                id: "dd0e8400-e29b-41d4-a716-446655440070"
                name: "Connected"
                callOutcome: "connected"
                order: 1
                createdAt: "2025-10-01T10:00:00.000Z"
                updatedAt: "2025-10-01T10:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /tasks:
    get:
      operationId: listTasks
      summary: List tasks
      description: |
        Returns a paginated list of tasks. Tasks represent individual outreach activities (calls, emails, etc.) assigned to users.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/tasks?page[size]=50' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by prospect:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/tasks?filter[prospect][id]=770e8400-e29b-41d4-a716-446655440003' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by action type:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/tasks?filter[action]=call,manual_email' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by sequence:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/tasks?filter[sequence][id]=550e8400-e29b-41d4-a716-446655440000' \
          -H "Authorization: Bearer nooks-api-..."
        ```

        Filter by account (joins through the task's prospect):
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/tasks?filter[account][id]=990e8400-e29b-41d4-a716-446655440099' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Tasks
      parameters:
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageAfter"
        - $ref: "#/components/parameters/PageBefore"
        - $ref: "#/components/parameters/FilterId"
        - name: filter[sequence][id]
          in: query
          description: Filter tasks belonging to a specific sequence
          required: false
          schema:
            type: string
            format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: filter[sequenceStep][id]
          in: query
          description: Filter tasks tied to a specific sequence step
          required: false
          schema:
            type: string
            format: uuid
          example: "aa0e8400-e29b-41d4-a716-446655440030"
        - name: filter[prospect][id]
          in: query
          description: Filter tasks for a specific prospect
          required: false
          schema:
            type: string
            format: uuid
          example: "770e8400-e29b-41d4-a716-446655440003"
        - name: filter[account][id]
          in: query
          description: |
            Filter tasks associated with a specific account. Joins through
            the task's prospect (`Prospect.accountId`).
          required: false
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440099"
        - name: filter[owner][id]
          in: query
          description: Filter tasks assigned to a specific user
          required: false
          schema:
            type: string
          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
        - name: filter[action]
          in: query
          description: "Filter by action type (comma-separated). Valid values: call, manual_email, auto_email, manual_email_reply, auto_email_reply, basic_task, linkedin_connection_request, manual_linkedin_connection_request, manual_linkedin_message, automatic_linkedin_message, manual_sms, auto_sms"
          required: false
          schema:
            type: array
            items:
              type: string
              enum:
                - call
                - manual_email
                - auto_email
                - manual_email_reply
                - auto_email_reply
                - basic_task
                - linkedin_connection_request
                - manual_linkedin_connection_request
                - manual_linkedin_message
                - automatic_linkedin_message
                - manual_sms
                - auto_sms
          style: form
          explode: false
          example: [call, manual_email]
        - name: filter[status]
          in: query
          description: "Filter by status (comma-separated). Valid values: pending, ready, finished, skipped, failed"
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [pending, ready, finished, skipped, failed]
          style: form
          explode: false
          example: [ready]
        - name: filter[priority]
          in: query
          description: "Filter by priority (comma-separated). Valid values: low, normal, high, urgent"
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [low, normal, high, urgent]
          style: form
          explode: false
          example: [high, urgent]
        - name: filter[dueAt][gte]
          in: query
          description: Filter tasks due at or after this timestamp (ISO 8601)
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-04-22T00:00:00Z"
        - name: filter[dueAt][lte]
          in: query
          description: Filter tasks due at or before this timestamp (ISO 8601)
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-04-22T23:59:59Z"
        - name: filter[completed]
          in: query
          description: "Filter by completion state. Valid values: true, false"
          required: false
          schema:
            type: string
            enum: ["true", "false"]
          example: "true"
        - name: filter[sequenceState][state]
          in: query
          description: "Filter by the state of the task's sequence state (comma-separated). Valid values: active, paused, finished, null. The sentinel `null` matches one-off tasks not associated with a sequence."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [active, paused, finished, "null"]
          style: form
          explode: false
          example: [active, "null"]
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `owner`, `prospect`, `sequence`, `sequenceState`, `sequenceStep`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [owner, prospect, sequence, sequenceState, sequenceStep]
            maxItems: 3
          style: form
          explode: false
          example: [owner, prospect]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Task"
                  links:
                    $ref: "#/components/schemas/PaginationLinks"
              example:
                data:
                  - id: "dd0e8400-e29b-41d4-a716-446655440050"
                    action: "call"
                    completed: false
                    dueAt: "2026-03-19T23:26:21.536Z"
                    note: "Initial outreach call"
                    status: "ready"
                    priority: "normal"
                    owner:
                      id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                      _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    prospect:
                      id: "770e8400-e29b-41d4-a716-446655440003"
                      _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                    sequence:
                      id: "550e8400-e29b-41d4-a716-446655440000"
                      _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                    sequenceState:
                      id: "880e8400-e29b-41d4-a716-446655440010"
                      _href: "/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440010"
                    sequenceStep:
                      id: "aa0e8400-e29b-41d4-a716-446655440030"
                      _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                    createdAt: "2026-03-18T10:00:00.000Z"
                    updatedAt: "2026-03-18T10:00:00.000Z"
                  - id: "dd1e8400-e29b-41d4-a716-446655440051"
                    action: "call"
                    completed: false
                    dueAt: "2026-03-20T15:00:00.000Z"
                    note: "Follow-up call"
                    status: "ready"
                    priority: "high"
                    owner:
                      id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                      _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    prospect:
                      id: "990e8400-e29b-41d4-a716-446655440020"
                      _href: "/v1/prospects/990e8400-e29b-41d4-a716-446655440020"
                    sequence: null
                    sequenceState: null
                    sequenceStep: null
                    createdAt: "2026-03-18T12:00:00.000Z"
                    updatedAt: "2026-03-18T12:00:00.000Z"
                links:
                  first: "https://partner-api.nooks.in/v1/tasks?page[size]=50"
                  prev: null
                  next: "https://partner-api.nooks.in/v1/tasks?page[size]=50&page[after]=eyJpZCI6ImRkMWU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDA1MSIsInYiOjF9"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createTask
      summary: Create a task
      description: |
        Creates a one-off task (not associated with a sequence). The task is validated and synced to CRM automatically.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/tasks' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{
            "data": {
              "action": "call",
              "dueAt": "2026-03-19T23:26:21.536Z",
              "ownerId": "KKLvN5wQoghWFwUvC75CZ12QM7I3",
              "prospectId": "770e8400-e29b-41d4-a716-446655440003",
              "note": "Test Call",
              "priority": "normal"
            }
          }'
        ```
      tags:
        - Tasks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Request body for creating a task
              additionalProperties: false
              properties:
                data:
                  type: object
                  additionalProperties: false
                  properties:
                    action:
                      type: string
                      enum:
                        - call
                        - manual_email
                        - manual_email_reply
                        - basic_task
                        - linkedin_connection_request
                        - manual_linkedin_connection_request
                        - manual_linkedin_message
                        - manual_sms
                      description: Type of task action
                      example: "call"
                    dueAt:
                      type: string
                      format: date-time
                      description: When the task is due (ISO 8601 with timezone offset)
                      example: "2026-03-19T23:26:21.536Z"
                    ownerId:
                      type: string
                      minLength: 1
                      maxLength: 128
                      pattern: "^[A-Za-z0-9_-]{1,128}$"
                      description: ID of the user who owns this task (Firebase UID)
                      example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    prospectId:
                      type: string
                      format: uuid
                      description: ID of the prospect this task is for
                      example: "770e8400-e29b-41d4-a716-446655440003"
                    note:
                      type: string
                      description: Optional note or instructions for the task
                      example: "Test Call"
                    priority:
                      type: string
                      enum:
                        - low
                        - normal
                        - high
                        - urgent
                      default: "normal"
                      description: Priority level of the task
                      example: "normal"
                  required:
                    - action
                    - dueAt
                    - ownerId
                    - prospectId
              required:
                - data
            example:
              data:
                action: "call"
                dueAt: "2026-03-19T23:26:21.536Z"
                ownerId: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                prospectId: "770e8400-e29b-41d4-a716-446655440003"
                note: "Test Call"
                priority: "normal"
      responses:
        "201":
          description: Task successfully created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
              example:
                id: "dd0e8400-e29b-41d4-a716-446655440050"
                action: "call"
                completed: false
                dueAt: "2026-03-19T23:26:21.536Z"
                note: "Test Call"
                status: "ready"
                priority: "normal"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence: null
                sequenceState: null
                sequenceStep: null
                createdAt: "2026-03-18T10:00:00.000Z"
                updatedAt: "2026-03-18T10:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          description: |
            Unprocessable entity. Possible causes:
            - Task creation prerequisites not met (e.g. prospect marked Do Not Call)
            - `ownerId` is not a user in this workspace (use an id from `GET /v1/users`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                prospectDnc:
                  summary: Prospect is marked Do Not Call
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Cannot create call task: prospect has been marked as Do Not Call"
                ownerNotFound:
                  summary: Owner is not a user in the workspace
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Owner user KKLvN5wQoghWFwUvC75CZ12QM7I3 not found in this workspace. Use a user id from GET /v1/users."
        "500":
          $ref: "#/components/responses/InternalError"

  /tasks/{id}:
    get:
      operationId: getTask
      summary: Get task by ID
      description: |
        Returns a single task by its unique identifier.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/tasks/dd0e8400-e29b-41d4-a716-446655440050' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Tasks
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the task
          schema:
            type: string
            format: uuid
          example: "dd0e8400-e29b-41d4-a716-446655440050"
        - name: include
          in: query
          description: "Comma-separated relations to expand inline. Valid values: `owner`, `prospect`, `sequence`, `sequenceState`, `sequenceStep`. Max 3."
          required: false
          schema:
            type: array
            items:
              type: string
              enum: [owner, prospect, sequence, sequenceState, sequenceStep]
            maxItems: 3
          style: form
          explode: false
          example: [owner]
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
              example:
                id: "dd0e8400-e29b-41d4-a716-446655440050"
                action: "call"
                completed: false
                dueAt: "2026-03-19T23:26:21.536Z"
                note: "Initial outreach call"
                status: "ready"
                priority: "normal"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence:
                  id: "550e8400-e29b-41d4-a716-446655440000"
                  _href: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
                sequenceState:
                  id: "880e8400-e29b-41d4-a716-446655440010"
                  _href: "/v1/sequenceStates/880e8400-e29b-41d4-a716-446655440010"
                sequenceStep:
                  id: "aa0e8400-e29b-41d4-a716-446655440030"
                  _href: "/v1/sequenceSteps/aa0e8400-e29b-41d4-a716-446655440030"
                createdAt: "2026-03-18T10:00:00.000Z"
                updatedAt: "2026-03-18T10:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    patch:
      operationId: updateTask
      summary: Update a task
      description: |
        Updates one or more fields on an existing task.

        **Updatable fields only** — only the following fields can be changed:
        - `dueAt` — reschedule the task to a new due date/time
        - `ownerId` — reassign the task to a different user
        - `priority` — change the task priority (`low`, `normal`, `high`, `urgent`)
        - `note` — update the task note (set to `null` to clear)

        At least one field must be provided. All other task fields (action, prospect, sequence, etc.) are immutable through this endpoint.

        Example (reschedule):
        ```bash
        curl -X PATCH 'https://partner-api.nooks.in/v1/tasks/dd0e8400-e29b-41d4-a716-446655440050' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{"data": {"dueAt": "2026-04-01T09:00:00.000Z"}}'
        ```

        Example (reassign + reprioritize):
        ```bash
        curl -X PATCH 'https://partner-api.nooks.in/v1/tasks/dd0e8400-e29b-41d4-a716-446655440050' \
          -H "Authorization: Bearer nooks-api-..." \
          -H "Content-Type: application/json" \
          -d '{"data": {"ownerId": "ABC123", "priority": "high"}}'
        ```
      tags:
        - Tasks
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the task
          schema:
            type: string
            format: uuid
          example: "dd0e8400-e29b-41d4-a716-446655440050"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Fields to update on the task. At least one field is required.
              additionalProperties: false
              required:
                - data
              properties:
                data:
                  type: object
                  description: |
                    The fields to update. Only `dueAt`, `ownerId`, `priority`, and `note` are
                    accepted — all other fields are rejected. At least one must be provided.
                  minProperties: 1
                  additionalProperties: false
                  properties:
                    dueAt:
                      type: string
                      format: date-time
                      description: New due date/time for the task (ISO 8601 with timezone offset)
                      example: "2026-04-01T09:00:00.000Z"
                    ownerId:
                      type: string
                      minLength: 1
                      maxLength: 128
                      pattern: "^[A-Za-z0-9_-]{1,128}$"
                      description: ID of the user to reassign the task to (Firebase UID)
                      example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    priority:
                      type: string
                      enum:
                        - low
                        - normal
                        - high
                        - urgent
                      description: New priority level for the task
                      example: "high"
                    note:
                      type: string
                      nullable: true
                      description: Updated note for the task. Set to null to clear.
                      example: "Follow up after the demo"
            example:
              data:
                dueAt: "2026-04-01T09:00:00.000Z"
                priority: "high"
      responses:
        "200":
          description: Task successfully updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
              example:
                id: "dd0e8400-e29b-41d4-a716-446655440050"
                action: "call"
                completed: false
                dueAt: "2026-04-01T09:00:00.000Z"
                note: "Follow up after the demo"
                status: "ready"
                priority: "high"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence: null
                sequenceState: null
                sequenceStep: null
                createdAt: "2026-03-18T10:00:00.000Z"
                updatedAt: "2026-04-01T08:45:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          description: |
            Unprocessable entity. Possible causes:
            - The update violates task business rules
            - `ownerId` is not a user in this workspace (use an id from `GET /v1/users`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                ownerNotFound:
                  summary: New owner is not a user in the workspace
                  value:
                    error:
                      code: "UNPROCESSABLE_ENTITY"
                      message: "Unprocessable entity"
                      details:
                        message: "Owner user KKLvN5wQoghWFwUvC75CZ12QM7I3 not found in this workspace. Use a user id from GET /v1/users."
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteTask
      summary: Delete a task
      description: |
        Permanently deletes a task. If the task belongs to an active sequence enrollment,
        the sequence is automatically advanced to the next step (or finished if it was the
        last step) before deletion.

        Example:
        ```bash
        curl -X DELETE 'https://partner-api.nooks.in/v1/tasks/dd0e8400-e29b-41d4-a716-446655440050' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Tasks
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the task to delete
          schema:
            type: string
            format: uuid
          example: "dd0e8400-e29b-41d4-a716-446655440050"
      responses:
        "204":
          description: Task successfully deleted (no content)
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "500":
          $ref: "#/components/responses/InternalError"

  /tasks/{id}/complete:
    post:
      operationId: completeTask
      summary: Complete a task
      description: |
        Marks a task as completed. This triggers CRM sync and, for sequence tasks,
        automatically advances the prospect to the next step in the sequence.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/tasks/dd0e8400-e29b-41d4-a716-446655440050/complete' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Tasks
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the task to complete
          schema:
            type: string
            format: uuid
          example: "dd0e8400-e29b-41d4-a716-446655440050"
      responses:
        "200":
          description: Task successfully completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
              example:
                id: "dd0e8400-e29b-41d4-a716-446655440050"
                action: "call"
                completed: true
                dueAt: "2026-03-19T23:26:21.536Z"
                note: "Initial outreach call"
                status: "ready"
                priority: "normal"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence: null
                sequenceState: null
                sequenceStep: null
                createdAt: "2026-03-18T10:00:00.000Z"
                updatedAt: "2026-03-19T23:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "500":
          $ref: "#/components/responses/InternalError"

  /tasks/{id}/skip:
    post:
      operationId: skipTask
      summary: Skip a task
      description: |
        Skips a task without completing the associated action. For sequence tasks,
        skipping advances the prospect to the next step in the sequence.
        The task status will be updated to `skipped`.

        Example:
        ```bash
        curl -X POST 'https://partner-api.nooks.in/v1/tasks/dd0e8400-e29b-41d4-a716-446655440050/skip' \
          -H "Authorization: Bearer nooks-api-..."
        ```
      tags:
        - Tasks
      parameters:
        - name: id
          in: path
          required: true
          description: Unique identifier for the task to skip
          schema:
            type: string
            format: uuid
          example: "dd0e8400-e29b-41d4-a716-446655440050"
      responses:
        "200":
          description: Task successfully skipped
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
              example:
                id: "dd0e8400-e29b-41d4-a716-446655440050"
                action: "call"
                completed: false
                dueAt: "2026-03-19T23:26:21.536Z"
                note: "Initial outreach call"
                status: "skipped"
                priority: "normal"
                owner:
                  id: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                  _href: "/v1/users/KKLvN5wQoghWFwUvC75CZ12QM7I3"
                prospect:
                  id: "770e8400-e29b-41d4-a716-446655440003"
                  _href: "/v1/prospects/770e8400-e29b-41d4-a716-446655440003"
                sequence: null
                sequenceState: null
                sequenceStep: null
                createdAt: "2026-03-18T10:00:00.000Z"
                updatedAt: "2026-03-19T23:30:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "500":
          $ref: "#/components/responses/InternalError"

  /me:
    get:
      operationId: getMe
      summary: Get the authenticated principal
      description: |
        Returns the workspace and user for the current credential. A `200`
        confirms the credential is valid; `401` means it's expired,
        revoked, or malformed.

        - For OAuth callers, `userId` is the end user who authorized the
          token (JWT `sub`).
        - For API-key callers, `userId` is the Nooks user who generated
          the key in **Developer Settings → API Keys**.
        - `userId` is always populated. `email` and `name` are `null` if
          that user no longer has a seat in the workspace.
        - `clientId` and `scopes` are `null` for API-key callers (API
          keys have full workspace access).

        Response is intentionally flat as an introspection endpoint and
        doesn't follow the nested ReferenceObject shape the resource
        endpoints use.

        Available to any authenticated caller regardless of granted
        scopes.

        Example:
        ```bash
        curl -X GET 'https://partner-api.nooks.in/v1/me' \
          -H "Authorization: Bearer <token-or-api-key>"
        ```
      tags:
        - Introspection
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                required:
                  [workspaceId, userId, email, name, authType, clientId, scopes]
                properties:
                  workspaceId:
                    type: string
                    description: Workspace the credential is scoped to.
                  userId:
                    type: string
                    description: |
                      Acting user's ID. For OAuth this is the end user
                      who authorized the token (JWT `sub`); for API keys
                      it's the user who created the key. Always populated.
                  email:
                    type: string
                    format: email
                    nullable: true
                    description: Email of the acting user; `null` if no longer a seat holder.
                  name:
                    type: string
                    nullable: true
                    description: Full name of the acting user; `null` if no longer a seat holder.
                  authType:
                    type: string
                    enum: [oauth, apiKey]
                    description: Authentication scheme used for the request.
                  clientId:
                    type: string
                    nullable: true
                    description: |
                      For OAuth: the partner-facing client identifier
                      (the same `client_id` used in OAuth requests). For
                      API keys: `null`.
                  scopes:
                    type: array
                    nullable: true
                    items:
                      type: string
                    description: |
                      For OAuth: the granted scope list from the access
                      token. For API keys: `null` (keys grant blanket
                      access).
              examples:
                oauth:
                  summary: OAuth caller
                  value:
                    workspaceId: "example-workspace"
                    userId: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
                    email: "jane@example.com"
                    name: "Jane Doe"
                    authType: "oauth"
                    clientId: "acme-integration"
                    scopes: ["sequences:read", "prospects:read"]
                apiKey:
                  summary: API-key caller
                  value:
                    workspaceId: "example-workspace"
                    userId: "1erKK6gEL7ZUDFPEjCiHfV02fFD3"
                    email: "john@example.com"
                    name: "John Smith"
                    authType: "apiKey"
                    clientId: null
                    scopes: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

webhooks:
  call.logged:
    post:
      summary: Call logged
      operationId: callLoggedWebhook
      description: |
        Fires once a call is fully finalized in Nooks (status, disposition,
        recording, transcript, and notes resolved). Delivered as an HTTP `POST`
        to every webhook URL configured for the workspace.

        **Scope:** fires for *every* call logged in the workspace — inbound
        and outbound, dialer calls, manually logged calls, and calls placed
        from a Nooks sequence.

        Configure endpoints in **Integrations → Webhooks** in your Nooks
        workspace. On save Nooks performs a verification ping and returns a
        signing key once — store it securely. For full setup and reference
        verification code in Node/Python/Ruby, see the
        [Nooks Webhooks Integration Guide](https://nooks.help.usepylon.com/articles/8109690071-Nooks-Webhooks-Integration-Guide).

        ### Headers

        Each delivery includes:

        | Header | Description |
        | --- | --- |
        | `Content-Type` | `application/json` |
        | `x-webhook-signature` | `t=<unix-ms>,s=<base64-hmac-sha256>` — see verification steps below |

        ### Signature verification

        The signature is computed as:

        ```
        s = base64(HMAC-SHA256(signingKey, timestamp + "." + raw_body))
        ```

        Steps to verify on your endpoint:

        1. Parse the `x-webhook-signature` header to extract `t` and `s`.
        2. Rebuild the signed string `timestamp + "." + raw_body` using the
           **unparsed** request body (do not re-serialize the JSON).
        3. Compute HMAC-SHA256 with your workspace signing key.
        4. Compare against `s` using a timing-safe comparison
           (e.g. `crypto.timingSafeEqual` in Node).
        5. Reject requests where `t` is older than 5 minutes to prevent replay.

        ### Response and retries

        Respond with any `2xx` within 15 seconds. Non-2xx responses or timeouts
        are retried with exponential backoff and jitter, up to 8 attempts over
        ~30 minutes. Process events asynchronously and acknowledge immediately.

        ### Idempotency

        `eventId` is unique per delivery attempt and is intended for logging
        only. Use `callData.callId` as your idempotency key — the same call
        may be delivered more than once due to retries.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: |
                Payload delivered to your webhook URL when a call is finalized.
              required:
                - event
                - eventId
                - occurredAt
                - callData
              properties:
                event:
                  type: string
                  enum:
                    - call.logged
                  description: Event type. Always `call.logged` for this payload.
                  example: "call.logged"
                eventId:
                  type: string
                  description: |
                    Unique per delivery attempt. Use for logging only — not
                    for deduplication. The same call may produce multiple
                    `eventId`s if delivery is retried. Use `callData.callId`
                    as your idempotency key.
                  example: "evt_01HW9X4S5K2M8R0Q1Y3Z6T7V8B"
                occurredAt:
                  type: string
                  format: date-time
                  description: ISO 8601 timestamp when the event was finalized in Nooks.
                  example: "2026-05-22T18:14:07.103Z"
                callData:
                  type: object
                  description: Complete call metadata for the finalized call.
                  required:
                    - callId
                    - workspaceId
                    - userData
                    - prospectData
                    - accountData
                    - callDirection
                    - status
                    - disposition
                    - startedAt
                    - durationSeconds
                  properties:
                    callId:
                      type: string
                      description: |
                        Permanent Nooks call identifier. Use this as your
                        idempotency key when processing webhook events.
                      example: "c-7f3d2a1b-9c4e-4f8a-b1d2-5e6f7a8b9c0d"
                    workspaceId:
                      type: string
                      description: Workspace the call belongs to.
                      example: "ws-3b2c1a09-8d7e-6f5a-4b3c-2a1b0c9d8e7f"
                    userData:
                      type: object
                      description: The Nooks user who placed or received the call.
                      required:
                        - userId
                        - email
                        - name
                      properties:
                        userId:
                          type: string
                          nullable: true
                          example: "u-1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
                        email:
                          type: string
                          nullable: true
                          example: "rep@example.com"
                        name:
                          type: string
                          nullable: true
                          example: "Alex Rep"
                    prospectData:
                      type: object
                      description: The prospect on the other end of the call.
                      required:
                        - prospectId
                        - name
                        - phoneNumber
                      properties:
                        prospectId:
                          type: string
                          nullable: true
                          example: "p-9b8a7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
                        name:
                          type: string
                          nullable: true
                          example: "Jamie Buyer"
                        phoneNumber:
                          type: string
                          nullable: true
                          description: E.164-formatted phone number when available.
                          example: "+14155550199"
                        email:
                          type: string
                          nullable: true
                          example: "jamie@prospectco.com"
                        linkedInUrl:
                          type: string
                          nullable: true
                          example: "https://www.linkedin.com/in/jamie-buyer"
                    accountData:
                      type: object
                      description: Account (company) associated with the prospect.
                      required:
                        - accountId
                        - name
                      properties:
                        accountId:
                          type: string
                          nullable: true
                          example: "a-2c3d4e5f-6a7b-8c9d-0e1f-2a3b4c5d6e7f"
                        name:
                          type: string
                          nullable: true
                          example: "Prospect Co"
                    callDirection:
                      type: string
                      enum:
                        - inbound
                        - outbound
                      example: "outbound"
                    status:
                      type: string
                      enum:
                        - completed
                      description: Final call status.
                      example: "completed"
                    disposition:
                      type: object
                      description: Call outcome / disposition.
                      required:
                        - id
                        - name
                      properties:
                        id:
                          type: string
                          nullable: true
                          example: "d-connected-decision-maker"
                        name:
                          type: string
                          nullable: true
                          example: "Connected — Decision Maker"
                    startedAt:
                      type: string
                      format: date-time
                      description: ISO 8601 timestamp when the call started.
                      example: "2026-05-22T18:11:42.000Z"
                    durationSeconds:
                      type: number
                      description: Call length in seconds.
                      example: 142
                    recordingUrl:
                      type: string
                      nullable: true
                      description: Link to the recording, or `null` if unavailable.
                      example: "https://recordings.nooks.in/c-7f3d2a1b/audio.mp3"
                    notes:
                      type: string
                      nullable: true
                      description: Rep-entered call notes, or `null` if none were captured.
                      example: "Decision-maker interested in Q3 pilot. Follow up Tuesday."
                    transcriptUrl:
                      type: string
                      nullable: true
                      description: Link to the transcript page in Nooks, or `null`.
                      example: "https://app.nooks.in/transcripts/c-7f3d2a1b"
                    sequenceData:
                      type: object
                      description: |
                        Sequence attribution. Always present on the payload;
                        both inner fields are `null` when the call did not
                        originate from a Nooks sequence task (manually-logged
                        calls, dialer calls, and inbound calls).
                      required:
                        - sequenceName
                        - sequenceStep
                      properties:
                        sequenceName:
                          type: string
                          nullable: true
                          description: |
                            Name of the sequence the call was placed from, or
                            `null` if the call wasn't sequence-attributed.
                          example: "Q1 Outbound Campaign"
                        sequenceStep:
                          type: string
                          nullable: true
                          description: |
                            Label of the sequence step the call was placed
                            from, or `null` if the call wasn't
                            sequence-attributed.
                          example: "Day 3 — Call"
      responses:
        "2XX":
          description: |
            Any `2xx` response acknowledges receipt. Respond within 15 seconds.

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Bearer token sent in the `Authorization` header. Accepts either a
        long-lived Nooks API key (`nooks-api-...`, from Developer Settings →
        API Keys) or an OAuth 2.0 access token issued by
        `https://oauth.nooks.in`. Use whichever you already have — the API
        validates both formats on the same header. For full OAuth flow
        details (authorize/token endpoints, scopes, refresh behavior) see
        the Authentication section of this spec's introduction.

  parameters:
    IncludeCRMCustomFields:
      name: includeCRMCustomFields
      in: query
      description: |
        When `true`, each returned record's `crmCustomFields` carries the
        record's CRM custom fields as a single JSON object keyed by the CRM
        field id/key (e.g. `{"hs_lead_status": "OPEN"}`). The field itself is
        always present in responses; without this opt-in (the default) it is
        an empty object `{}` and no custom-field data is fetched.
      required: false
      schema:
        type: string
        enum: ["true", "false"]
      example: "true"

    PageSize:
      name: page[size]
      in: query
      description: Number of items per page (max 100)
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
      example: 50

    PageAfter:
      name: page[after]
      in: query
      description: |
        Cursor for fetching the next page. Opaque token returned by the
        API in `links.next`; callers should treat it as opaque and only
        pass values they received from a previous response. Malformed
        cursors return `400 Bad Request`.
      required: false
      schema:
        type: string
      example: "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCIsInYiOjF9"

    PageBefore:
      name: page[before]
      in: query
      description: |
        Cursor for fetching the previous page. Opaque token returned by
        the API in `links.prev`; callers should treat it as opaque and
        only pass values they received from a previous response.
        Malformed cursors return `400 Bad Request`.
      required: false
      schema:
        type: string
      example: "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCIsInYiOjF9"

    FilterId:
      name: filter[id]
      in: query
      description: Filter by IDs (comma-separated UUIDs). An empty value is treated as "no filter".
      required: false
      schema:
        type: string
        # Empty (no filter) or one-or-more comma-separated UUIDs.
        pattern: "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(,[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})*)?$"
      example: "550e8400-e29b-41d4-a716-446655440000,660e8400-e29b-41d4-a716-446655440001"

    FilterUpdatedAtGte:
      name: filter[updatedAt][gte]
      in: query
      description: Filter by minimum updatedAt (ISO 8601 datetime)
      required: false
      schema:
        type: string
        format: date-time
      example: "2025-01-01T00:00:00Z"

    FilterUpdatedAtLt:
      name: filter[updatedAt][lt]
      in: query
      description: Filter by updatedAt before this timestamp, exclusive (ISO 8601 datetime)
      required: false
      schema:
        type: string
        format: date-time
      example: "2026-01-01T00:00:00Z"

  schemas:
    # --- Sequences ---
    Sequence:
      type: object
      description: A sales sequence defining a series of outreach steps
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the sequence
          example: "550e8400-e29b-41d4-a716-446655440000"
        name:
          type: string
          description: Name of the sequence
          example: "Q1 Outbound Campaign"
        type:
          type: string
          enum:
            - date
            - interval
          description: |
            Type of sequence timing:
            - `date`: Steps are scheduled for specific dates
            - `interval`: Steps are scheduled based on intervals from enrollment
          example: "interval"
        enabled:
          type: boolean
          description: Whether the sequence is currently active
          example: true
        owner:
          description: The user who owns this sequence
          $ref: "#/components/schemas/ReferenceObject"
        sequenceSteps:
          type: array
          description: Steps in this sequence
          items:
            $ref: "#/components/schemas/ReferenceObject"
        privacy:
          type: string
          enum:
            - private
            - team_visible
            - team_editable
          description: |
            Privacy level of the sequence:
            - `private`: Only the owner (and workspace managers/admins) can view or edit it in Nooks.
            - `team_visible`: All team members can view and enroll prospects; only the owner can edit.
            - `team_editable`: All team members can view, enroll prospects, and edit (the default).

            Always present. Integrations acting on behalf of a user should
            respect this — treat `private` sequences as owner-only.
          example: "team_editable"
        analytics:
          description: |
            Lifetime engagement counts summed over this sequence's steps,
            grouped by channel. **Only present when the request includes
            `include=analytics`.** A group is `null` iff the sequence has no
            step of that channel. `linkedIn.repliesReceived` is populated at
            this level (LinkedIn replies cannot be attributed to individual
            steps).
          $ref: "#/components/schemas/Analytics"
        createdAt:
          type: string
          format: date-time
          description: When the sequence was created
          example: "2026-01-12T09:30:00Z"
        updatedAt:
          type: string
          format: date-time
          description: |
            Effective last-updated time of the sequence: the most recent of the
            sequence's own update time and the update time of any of its steps.
            `filter[updatedAt]` applies to this same derived value.
          example: "2026-01-15T09:30:00Z"
      required:
        - id
        - name
        - type
        - enabled
        - owner
        - sequenceSteps
        - privacy
        - createdAt
        - updatedAt

    # --- SequenceSteps ---
    SequenceStep:
      type: object
      description: A step within a sales sequence defining an action to perform
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the sequence step
          example: "aa0e8400-e29b-41d4-a716-446655440030"
        order:
          type: integer
          description: Zero-based position of this step within the sequence
          example: 0
        name:
          type: string
          nullable: true
          description: Optional display name for this step, or null if unnamed
          example: "Intro email"
        interval:
          type: integer
          nullable: true
          description: |
            Number of minutes to wait before executing this step (null for
            the first step). `POST`/`PATCH /sequenceSteps` always require a
            non-null `interval` — `null` is a read-only state you cannot
            currently set through this API.
          example: 4320
        action:
          type: string
          enum:
            - call
            - manual_email
            - auto_email
            - manual_email_reply
            - auto_email_reply
            - basic_task
            - linkedin_connection_request
            - manual_linkedin_connection_request
            - manual_linkedin_message
            - automatic_linkedin_message
            - manual_sms
            - auto_sms
          description: |
            Type of action for this step:
            - `call`: Phone call task
            - `manual_email`: Manually composed email
            - `auto_email`: Automatically sent email
            - `manual_email_reply`: Manual email reply
            - `auto_email_reply`: Automatic email reply
            - `basic_task`: Generic manual task
            - `linkedin_connection_request`: LinkedIn connection request
            - `manual_linkedin_connection_request`: Manual LinkedIn connection request
            - `manual_linkedin_message`: Manual LinkedIn message
            - `automatic_linkedin_message`: Automatically sent LinkedIn message
            - `manual_sms`: Manual SMS message
            - `auto_sms`: Automatically sent SMS message
          example: "auto_email"
        note:
          type: string
          nullable: true
          description: Optional note or instructions for this step
          example: "Initial outreach email"
        sequence:
          description: The sequence this step belongs to
          $ref: "#/components/schemas/ReferenceObject"
        template:
          nullable: true
          description: |
            The single email template attached to this step, or null if the step has no template
            (e.g. call/LinkedIn/SMS steps, or AI-email steps that select from a template chain at send time).
            Use `?include=template` to inline the full EmailTemplate DTO.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        hasAbVariants:
          type: boolean
          description: |
            Whether this step is running an A/B test (i.e. has multiple variants).
          example: false
        analytics:
          description: |
            Lifetime engagement counts for this step, grouped by channel.
            **Only present when the request includes `include=analytics`**
            (steps inlined into other resources via `include=sequenceSteps`
            never carry it). Only the group matching the step's `action`
            channel is populated; the others are `null`.
            `linkedIn.repliesReceived` is always `null` on steps — replies are
            only attributable on the Sequence.
          $ref: "#/components/schemas/Analytics"
        createdAt:
          type: string
          format: date-time
          description: When the step was created
          example: "2025-10-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the step was last updated
          example: "2025-10-01T10:00:00.000Z"
      required:
        - id
        - order
        - name
        - interval
        - action
        - note
        - sequence
        - template
        - hasAbVariants
        - createdAt
        - updatedAt

    # --- Analytics (shared by Sequence and SequenceStep) ---
    EmailAnalytics:
      type: object
      description: Lifetime email engagement counts.
      properties:
        delivered:
          type: integer
          description: Emails delivered
          example: 412
        opened:
          type: integer
          description: Emails opened (requires open tracking to be enabled for the workspace)
          example: 198
        clicked:
          type: integer
          description: Emails with a tracked link click (requires click tracking)
          example: 41
        replied:
          type: integer
          description: Email replies received
          example: 23
        bounced:
          type: integer
          description: Emails that bounced
          example: 7
        unsubscribed:
          type: integer
          description: Unsubscribes attributed to these emails
          example: 2
      required:
        - delivered
        - opened
        - clicked
        - replied
        - bounced
        - unsubscribed
    CallAnalytics:
      type: object
      description: |
        Lifetime call outcome counts. Within a single step, each enrolled
        prospect is counted once, by the furthest outcome reached (not once
        per dial). Counts nest numerically (meetings ≤ connected ≤ dialed).
      properties:
        dialed:
          type: integer
          description: Prospects dialed
          example: 380
        connected:
          type: integer
          description: Prospects where a call connected
          example: 71
        meetings:
          type: integer
          description: Prospects where a meeting was booked
          example: 5
      required:
        - dialed
        - connected
        - meetings
    LinkedInAnalytics:
      type: object
      description: |
        Lifetime LinkedIn engagement counts for sequence-attributed activity.
        Only activity tied to a sequence task is counted — LinkedIn connections
        imported from the user's existing network and manually logged activity
        are excluded. Acceptances are discovered by periodic polling from the
        Nooks Chrome extension, so `connectionRequestsAccepted` can lag reality.
      properties:
        connectionRequestsSent:
          type: integer
          description: Connection requests sent (pending or accepted; withdrawn/failed requests are not counted)
          example: 96
        connectionRequestsAccepted:
          type: integer
          description: Connection requests accepted
          example: 31
        messagesSent:
          type: integer
          description: LinkedIn messages sent
          example: 64
        repliesReceived:
          type: integer
          nullable: true
          description: |
            Inbound LinkedIn replies. Replies can only be attributed at the
            sequence level (via the conversation's first-reply sequence), so
            this is a number on Sequence analytics and always `null` on
            SequenceStep analytics.
          example: 12
      required:
        - connectionRequestsSent
        - connectionRequestsAccepted
        - messagesSent
        - repliesReceived
    Analytics:
      type: object
      description: |
        Channel-grouped engagement counts, returned only when requested via
        `include=analytics`. Counts are lifetime totals (not date-windowed)
        and are raw counts, not rates — pick your own denominators.

        All three keys are always present. A group is `null` if and only if
        the channel is not applicable to the object: on a SequenceStep, only
        the group matching the step's `action` channel is populated; on a
        Sequence, a group is populated when the sequence has at least one step
        of that channel (summed over its steps). `null` always means "channel
        not applicable here" — never "not computed". Zero means "applicable,
        nothing happened yet". Steps whose channel has no tracked analytics
        (`basic_task`, SMS) have all three groups `null`.
      properties:
        email:
          nullable: true
          description: Email engagement counts, or null if this object has no email channel
          allOf:
            - $ref: "#/components/schemas/EmailAnalytics"
        calls:
          nullable: true
          description: Call outcome counts, or null if this object has no call channel
          allOf:
            - $ref: "#/components/schemas/CallAnalytics"
        linkedIn:
          nullable: true
          description: LinkedIn engagement counts, or null if this object has no LinkedIn channel
          allOf:
            - $ref: "#/components/schemas/LinkedInAnalytics"
      required:
        - email
        - calls
        - linkedIn

    # --- Email Templates ---
    EmailTemplate:
      type: object
      description: An email template stored in the workspace. Templates are referenced by sequence steps and contain the body content sent to prospects.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the email template
          example: "ee0e8400-e29b-41d4-a716-446655440080"
        name:
          type: string
          nullable: true
          description: Display name of the template
          example: "Initial outreach"
        subject:
          type: string
          nullable: true
          description: Email subject line. May contain merge fields (e.g. `{{firstName}}`).
          example: "Quick question, {{firstName}}"
        htmlBody:
          type: string
          nullable: true
          description: HTML body of the email. This is the source of truth; plain-text rendering is derived from it at send time.
          example: "<p>Hi {{firstName}},</p><p>...</p>"
        source:
          type: string
          description: How the template was created (e.g. `manual`, `ai`)
          example: "manual"
        archived:
          type: boolean
          description: Whether the template has been archived
          example: false
        order:
          type: integer
          description: Sort order of the template within the workspace
          example: 0
        owner:
          nullable: true
          description: The user who owns the template, or null if unowned
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        privacy:
          type: string
          enum:
            - private
            - team_visible
            - team_editable
          description: |
            Privacy level of the template:
            - `private`: Only the owner (and workspace managers/admins) can view or edit it in Nooks.
            - `team_visible`: All team members can view; only the owner can edit.
            - `team_editable`: All team members can view and edit (the default).

            Always present. Reflects how the template is shared within the
            workspace in Nooks.
          example: "team_editable"
        createdAt:
          type: string
          format: date-time
          description: When the template was created
          example: "2025-10-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the template was last updated
          example: "2025-10-01T10:00:00.000Z"
      required:
        - id
        - name
        - subject
        - htmlBody
        - source
        - archived
        - order
        - owner
        - privacy
        - createdAt
        - updatedAt

    # --- Users ---
    User:
      type: object
      description: A workspace user
      properties:
        id:
          type: string
          description: Unique identifier for the user
          example: "KKLvN5wQoghWFwUvC75CZ12QM7I3"
        name:
          type: string
          description: Full name of the user
          example: "Jane Doe"
        email:
          type: string
          format: email
          description: Email address of the user
          example: "jane@example.com"
        crmId:
          type: string
          nullable: true
          description: >
            The user's id in the customer's CRM of record (e.g. a Salesforce
            User.Id / OwnerId), resolved via the workspace's CRM user mappings.
            Null when the user has no CRM mapping.
          example: "0051a000000XyzABC"
        createdAt:
          type: string
          format: date-time
          description: When the user was created
          example: "2025-09-29T22:10:51.271Z"
        updatedAt:
          type: string
          format: date-time
          description: When the user was last updated
          example: "2025-10-07T20:42:17.281Z"
      required:
        - id
        - name
        - email
        - crmId
        - createdAt
        - updatedAt

    # --- SequenceStates ---
    SequenceState:
      type: object
      description: A prospect's enrollment in a sequence
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the sequence state
          example: "880e8400-e29b-41d4-a716-446655440010"
        state:
          type: string
          enum:
            - active
            - paused
            - finished
          description: |
            Current state of the enrollment:
            - `active`: Prospect is actively progressing through the sequence
            - `paused`: Enrollment is temporarily paused
            - `finished`: Enrollment has completed or been stopped
          example: "active"
        sequence:
          $ref: "#/components/schemas/ReferenceObject"
        prospect:
          $ref: "#/components/schemas/ReferenceObject"
        account:
          nullable: true
          description: |
            Account the prospect belongs to. Derived from the prospect's
            `accountId`; `null` when the prospect has no account. Always
            populated on `GET /sequenceStates` (list and by-id); may be
            omitted from `POST /sequenceStates` 201 responses.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        creator:
          $ref: "#/components/schemas/ReferenceObject"
        owner:
          $ref: "#/components/schemas/ReferenceObject"
        sequenceStep:
          nullable: true
          description: The current sequence step
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        createdAt:
          type: string
          format: date-time
          description: When the enrollment was created
          example: "2025-11-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the enrollment was last updated
          example: "2025-11-05T14:30:00.000Z"
      required:
        - id
        - state
        - sequence
        - prospect
        - creator
        - owner
        - sequenceStep
        - createdAt
        - updatedAt

    # --- Prospects ---
    Prospect:
      type: object
      description: A sales prospect
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the prospect
          example: "990e8400-e29b-41d4-a716-446655440020"
        name:
          type: string
          nullable: true
          description: Full name of the prospect
          example: "Jane Doe"
        firstName:
          type: string
          nullable: true
          description: First name of the prospect
          example: "Jane"
        lastName:
          type: string
          nullable: true
          description: Last name of the prospect
          example: "Doe"
        title:
          type: string
          nullable: true
          description: Job title of the prospect
          example: "VP of Engineering"
        primaryEmail:
          type: string
          nullable: true
          format: email
          description: Primary email address of the prospect
          example: "jane@acme.com"
        linkedInUrl:
          type: string
          nullable: true
          format: uri
          description: LinkedIn profile URL of the prospect
          example: "https://linkedin.com/in/janedoe"
        crmId:
          type: string
          nullable: true
          description: External ID of the prospect in the customer's CRM
          example: "003ABC123DEF456"
        account:
          nullable: true
          description: Associated account
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequenceStates:
          type: array
          description: Sequence enrollments for this prospect
          items:
            $ref: "#/components/schemas/ReferenceObject"
        crmCustomFields:
          type: object
          additionalProperties: true
          description: |
            CRM custom fields as a JSON object keyed by the CRM field id/key.
            Always present; populated only when the request passed
            `includeCRMCustomFields=true`, otherwise an empty object `{}`.
          example:
            hs_lead_status: "OPEN"
            lifecycle_stage: "mql"
        createdAt:
          type: string
          format: date-time
          description: When the prospect was created
          example: "2025-10-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the prospect was last updated
          example: "2025-11-15T14:30:00.000Z"
      required:
        - id
        - name
        - firstName
        - lastName
        - title
        - primaryEmail
        - linkedInUrl
        - crmId
        - account
        - sequenceStates
        - crmCustomFields
        - createdAt
        - updatedAt

    SyncableCrmType:
      type: string
      enum:
        - salesforce
        - hubspot
      description: The CRM integration to sync from.

    SyncProspectsRequest:
      type: object
      description: Request body for POST /integrations/prospects/sync.
      additionalProperties: false
      properties:
        data:
          type: object
          additionalProperties: false
          properties:
            type:
              $ref: "#/components/schemas/SyncableCrmType"
            records:
              type: array
              minItems: 1
              maxItems: 100
              description: External ids of the CRM records to sync.
              items:
                type: string
                minLength: 1
                description: The CRM record's external id (Salesforce Contact/Lead Id, or HubSpot contact id).
                example: "003ABC123DEF456"
          required:
            - type
            - records
      required:
        - data

    SyncProspectResult:
      type: object
      description: Per-record success entry from a sync call.
      properties:
        externalId:
          type: string
          description: The CRM external id supplied in the request.
          example: "003ABC123DEF456"
        prospectId:
          type: string
          format: uuid
          description: The Nooks prospect UUID.
          example: "990e8400-e29b-41d4-a716-446655440020"
        created:
          type: boolean
          description: True if the row was inserted; false if it was updated.
          example: true
      required:
        - externalId
        - prospectId
        - created

    SyncProspectError:
      type: object
      description: Per-record failure entry from a sync call.
      properties:
        externalId:
          type: string
          example: "003ABC123DEF456"
        code:
          type: string
          enum:
            - NOT_FOUND_IN_CRM
            - MAPPING_FAILED
          description: |
            * `NOT_FOUND_IN_CRM` — the CRM had no record for the given `externalId`.
            * `MAPPING_FAILED` — failed to map a Nooks prospect to the CRM record.
          example: "NOT_FOUND_IN_CRM"
        message:
          type: string
          description: Human-readable detail.
          example: "No record found in salesforce for externalId 003ABC123DEF456"
      required:
        - externalId
        - code
        - message

    SyncProspectsResponse:
      type: object
      description: Response body for POST /integrations/prospects/sync.
      properties:
        results:
          type: array
          description: Successfully-synced records. Order is not guaranteed.
          items:
            $ref: "#/components/schemas/SyncProspectResult"
        errors:
          type: array
          description: Per-record failures. Empty when every record succeeded.
          items:
            $ref: "#/components/schemas/SyncProspectError"
      required:
        - results
        - errors

    NoCrmConnectionError:
      type: object
      description: >
        Returned (400) when the requested CRM isn't usable — never connected,
        token/session expired/revoked, or the acting user has no connection of
        their own. `message` carries the remediation, so branch on `code`,
        not `message`.
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - NO_CRM_CONNECTION
              example: "NO_CRM_CONNECTION"
            message:
              type: string
              description: Human-readable remediation text; display-only (do not parse).
              example: "Your salesforce connection is no longer valid. Reconnect salesforce in Nooks."
            integrationType:
              $ref: "#/components/schemas/SyncableCrmType"
          required:
            - code
            - message
            - integrationType
        traceId:
          type: string
          description: Request trace id, for correlating with Nooks support.
          example: "6b1c9f2e8d3a4c5b7e9f0a1d2c3b4a5e"
      required:
        - error

    # --- Notes ---
    CrmNoteIntegrationType:
      type: string
      enum:
        - salesforce
        - hubspot
      description: The CRM integration where the note should be created.

    CreateNoteRequest:
      type: object
      description: Request body for creating a CRM note.
      additionalProperties: false
      properties:
        data:
          type: object
          additionalProperties: false
          properties:
            text:
              type: string
              minLength: 1
              maxLength: 65535
              description: Note body text. Must contain at least one non-whitespace character.
              example: "Follow up after legal review."
            integrationType:
              $ref: "#/components/schemas/CrmNoteIntegrationType"
          required:
            - text
            - integrationType
      required:
        - data

    Note:
      type: object
      description: A CRM note created through Nooks.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the Nooks note record.
          example: "bb0e8400-e29b-41d4-a716-446655440080"
        externalId:
          type: string
          description: Unique identifier for the note in the destination CRM.
          example: "00TABC123DEF456"
        text:
          type: string
          description: Note body text.
          example: "Follow up after legal review."
        associatedType:
          type: string
          enum:
            - prospect
            - account
          description: Type of resource the note is associated with.
          example: "prospect"
        associated:
          $ref: "#/components/schemas/ReferenceObject"
        integrationType:
          $ref: "#/components/schemas/CrmNoteIntegrationType"
        userExternalId:
          type: string
          nullable: true
          description: External CRM user identifier for the user who authored the note, when available.
          example: "005ABC123DEF456"
        userName:
          type: string
          nullable: true
          description: Display name for the user who authored the note, when available.
          example: "Sam Seller"
        externalCreatedAt:
          type: string
          format: date-time
          description: When the note was created in the destination CRM.
          example: "2026-07-09T16:00:00.000Z"
        externalUpdatedAt:
          type: string
          format: date-time
          description: When the note was last updated in the destination CRM.
          example: "2026-07-09T16:00:00.000Z"
        createdAt:
          type: string
          format: date-time
          description: When the Nooks note record was created.
          example: "2026-07-09T16:00:01.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the Nooks note record was last updated.
          example: "2026-07-09T16:00:01.000Z"
      required:
        - id
        - externalId
        - text
        - associatedType
        - associated
        - integrationType
        - userExternalId
        - userName
        - externalCreatedAt
        - externalUpdatedAt
        - createdAt
        - updatedAt

    # --- Accounts ---
    Account:
      type: object
      description: An account (company) record
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the account
          example: "aa0e8400-e29b-41d4-a716-446655440030"
        name:
          type: string
          nullable: true
          description: Name of the account (company)
          example: "Acme Corp"
        domain:
          type: string
          nullable: true
          description: Primary domain associated with the account
          example: "acme.com"
        numEmployees:
          type: integer
          nullable: true
          description: Number of employees at the account
          example: 500
        linkedInUrl:
          type: string
          nullable: true
          format: uri
          description: LinkedIn company page URL
          example: "https://linkedin.com/company/acme"
        description:
          type: string
          nullable: true
          description: Free-form description of the account
          example: "B2B SaaS for the manufacturing sector."
        crmCustomFields:
          type: object
          additionalProperties: true
          description: |
            CRM custom fields as a JSON object keyed by the CRM field id/key.
            Always present; populated only when the request passed
            `includeCRMCustomFields=true`, otherwise an empty object `{}`.
          example:
            Industry: "SaaS"
        createdAt:
          type: string
          format: date-time
          description: When the account was created
          example: "2025-09-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the account was last updated
          example: "2025-11-15T14:30:00.000Z"
      required:
        - id
        - name
        - domain
        - numEmployees
        - linkedInUrl
        - description
        - crmCustomFields
        - createdAt
        - updatedAt

    # --- Mailboxes ---
    Mailbox:
      type: object
      description: An email mailbox (alias)
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the mailbox
          example: "bb0e8400-e29b-41d4-a716-446655440040"
        email:
          type: string
          format: email
          description: Email address of the mailbox
          example: "jane@acme.com"
        isDefault:
          type: boolean
          description: Whether this is the user's default mailbox
          example: true
        user:
          $ref: "#/components/schemas/ReferenceObject"
        createdAt:
          type: string
          format: date-time
          description: When the mailbox was created
          example: "2025-10-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the mailbox was last updated
          example: "2025-11-15T14:30:00.000Z"
      required:
        - id
        - email
        - isDefault
        - user
        - createdAt
        - updatedAt

    # --- Calls ---
    Call:
      type: object
      description: A call record
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the call
          example: "ff0e8400-e29b-41d4-a716-446655440060"
        source:
          type: string
          description: Source/type of the call (e.g., nooks, outreach, salesforce)
          example: "nooks"
        duration:
          type: number
          description: Duration of the call in seconds
          example: 120.5
        from:
          type: string
          nullable: true
          description: Phone number of the rep who made/received the call
          example: "+14155551234"
        to:
          type: string
          nullable: true
          description: Phone number of the prospect
          example: "+14155555678"
        direction:
          type: string
          nullable: true
          description: Direction of the call
          example: "outgoing"
        recordingUrl:
          type: string
          nullable: true
          description: URL to the call recording
          example: "https://example.com/recordings/abc123"
        time:
          type: string
          format: date-time
          description: |
            Wall-clock time the call actually happened. Distinct from
            `createdAt` (ingestion time) — `time` is what the dialer /
            telephony layer recorded. Use this for call-time-based queries
            and filtering.
          example: "2025-11-01T09:57:12.000Z"
        prospect:
          nullable: true
          description: Prospect this call is associated with
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequence:
          nullable: true
          description: |
            Sequence associated with this call when matched to a Nooks SEP task.
            Null when the call is not matched to a Nooks SEP task.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequenceStep:
          nullable: true
          description: |
            Sequence step associated with this call when matched to a Nooks SEP task.
            Null when the call is not matched to a Nooks SEP task.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        callDisposition:
          nullable: true
          description: |
            Call disposition assigned to this call.
            Null when no disposition has been set.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        owner:
          nullable: true
          description: |
            The user who placed the call. Sourced from the linked caller's
            `userId`. Null when the caller has no associated Nooks user.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        createdAt:
          type: string
          format: date-time
          description: When the call was created
          example: "2025-11-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the call was last updated
          example: "2025-11-05T14:30:00.000Z"
      required:
        - id
        - source
        - duration
        - from
        - to
        - direction
        - recordingUrl
        - time
        - prospect
        - sequence
        - sequenceStep
        - callDisposition
        - owner
        - createdAt
        - updatedAt

    CallDisposition:
      type: object
      description: A call disposition definition used to categorize call outcomes
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the call disposition
          example: "dd0e8400-e29b-41d4-a716-446655440070"
        name:
          type: string
          description: Display name of the disposition
          example: "Connected"
        callOutcome:
          type: string
          description: The call outcome category this disposition maps to
          example: "connected"
        order:
          type: integer
          nullable: true
          description: Sort order for display (null if unordered)
          example: 1
        createdAt:
          type: string
          format: date-time
          description: When the disposition was created
          example: "2025-10-01T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the disposition was last updated
          example: "2025-10-01T10:00:00.000Z"
      required:
        - id
        - name
        - callOutcome
        - order
        - createdAt
        - updatedAt

    # --- Tasks ---
    Task:
      type: object
      description: A task representing an outreach activity assigned to a user
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the task
          example: "dd0e8400-e29b-41d4-a716-446655440050"
        action:
          type: string
          enum:
            - call
            - manual_email
            - auto_email
            - manual_email_reply
            - auto_email_reply
            - basic_task
            - linkedin_connection_request
            - manual_linkedin_connection_request
            - manual_linkedin_message
            - automatic_linkedin_message
            - manual_sms
            - auto_sms
          description: |
            Type of action for this task:
            - `call`: Phone call task
            - `manual_email`: Manually composed email
            - `auto_email`: Automatically sent email
            - `manual_email_reply`: Manual email reply
            - `auto_email_reply`: Automatic email reply
            - `basic_task`: Generic manual task
            - `linkedin_connection_request`: LinkedIn connection request
            - `manual_linkedin_connection_request`: Manual LinkedIn connection request
            - `manual_linkedin_message`: Manual LinkedIn message
            - `automatic_linkedin_message`: Automatically sent LinkedIn message
            - `manual_sms`: Manual SMS message
            - `auto_sms`: Automatically sent SMS message
          example: "call"
        completed:
          type: boolean
          description: Whether the task has been completed
          example: false
        dueAt:
          type: string
          format: date-time
          description: When the task is due
          example: "2026-03-19T23:26:21.536Z"
        note:
          type: string
          nullable: true
          description: Optional note or instructions for the task
          example: "Initial outreach call"
        status:
          type: string
          nullable: true
          description: Current status of the task (pending, ready, finished, skipped, failed)
          example: "ready"
        priority:
          type: string
          enum:
            - low
            - normal
            - high
            - urgent
          description: Priority level of the task
          example: "normal"
        owner:
          description: The user this task is assigned to
          $ref: "#/components/schemas/ReferenceObject"
        prospect:
          description: The prospect this task is for
          $ref: "#/components/schemas/ReferenceObject"
        sequence:
          nullable: true
          description: The sequence this task belongs to (null for one-off tasks)
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequenceState:
          nullable: true
          description: The sequence enrollment this task belongs to (null for one-off tasks)
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequenceStep:
          nullable: true
          description: The sequence step this task was created from (null for one-off tasks)
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        createdAt:
          type: string
          format: date-time
          description: When the task was created
          example: "2026-03-18T10:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: When the task was last updated
          example: "2026-03-18T10:00:00.000Z"
      required:
        - id
        - action
        - completed
        - dueAt
        - note
        - status
        - priority
        - owner
        - prospect
        - sequence
        - sequenceState
        - sequenceStep
        - createdAt
        - updatedAt

    Email:
      type: object
      description: An email record
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the email
        type:
          type: string
          nullable: true
          description: Integration type of the email
        status:
          type: string
          nullable: true
          description: Current status of the email
        from:
          type: string
          nullable: true
          description: Sender email address
        to:
          type: string
          nullable: true
          description: Recipient email address
        recipients:
          type: array
          nullable: true
          items:
            type: string
          description: List of recipient email addresses
        subject:
          type: string
          nullable: true
          description: Email subject line
        bodyText:
          type: string
          nullable: true
          description: The plain-text body of the email.
        bodyHtml:
          type: string
          nullable: true
          description: The HTML body of the email.
        prospect:
          nullable: true
          description: Associated prospect
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequence:
          nullable: true
          description: |
            Sequence associated with this email when matched to a Nooks SEP task.
            Null when the email is not matched to a Nooks SEP task.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        sequenceStep:
          nullable: true
          description: |
            Sequence step associated with this email when matched to a Nooks SEP task.
            Null when the email is not matched to a Nooks SEP task.
          allOf:
            - $ref: "#/components/schemas/ReferenceObject"
        date:
          type: string
          format: date-time
          nullable: true
          description: Date of the email
        deliveredAt:
          type: string
          format: date-time
          nullable: true
          description: When the email was delivered
        openedAt:
          type: string
          format: date-time
          nullable: true
          description: When the email was first opened
        clickedAt:
          type: string
          format: date-time
          nullable: true
          description: When a link in the email was first clicked
        bouncedAt:
          type: string
          format: date-time
          nullable: true
          description: When the email bounced
        repliedAt:
          type: string
          format: date-time
          nullable: true
          description: When a reply was received
        openCount:
          type: integer
          nullable: true
          description: Number of times the email was opened
        clickCount:
          type: integer
          nullable: true
          description: Number of times links were clicked
        createdAt:
          type: string
          format: date-time
          description: When the email record was created
        updatedAt:
          type: string
          format: date-time
          description: When the email record was last updated
      required:
        - id
        - prospect
        - sequence
        - sequenceStep
        - createdAt
        - updatedAt
    # --- Shared schemas ---
    ReferenceObject:
      type: object
      description: A reference to a related resource
      properties:
        id:
          type: string
          description: Unique identifier of the referenced resource
          example: "550e8400-e29b-41d4-a716-446655440000"
        _href:
          type: string
          description: API path to the referenced resource
          example: "/v1/sequences/550e8400-e29b-41d4-a716-446655440000"
      required:
        - id
        - _href

    PaginationLinks:
      type: object
      description: |
        Pagination links for navigating through results.
        Links are returned as relative references (path + query) that
        should be resolved against the base URL of the request.
      properties:
        first:
          type: string
          format: uri-reference
          description: URL for the first page
          example: "/v1/sequences?page[size]=50"
        prev:
          type: string
          format: uri-reference
          nullable: true
          description: URL for the previous page (null if on first page)
          example: null
        next:
          type: string
          format: uri-reference
          nullable: true
          description: URL for the next page (null if on last page)
          example: "/v1/sequences?page[size]=50&page[after]=eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCIsInYiOjF9"

    Error:
      type: object
      description: Standard error response
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Error code
              example: "NOT_FOUND"
            message:
              type: string
              description: Human-readable error message
              example: "The requested resource was not found"
        traceId:
          type: string
          description: |
            Request trace identifier for correlating this error with server
            logs and support requests. Present whenever the request carried a
            trace context (the normal case); omitted otherwise.
          example: "abc123def456"

  responses:
    BadRequest:
      description: Bad request - invalid parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "BAD_REQUEST"
              message: "Invalid page[after] cursor"

    Unauthorized:
      description: Unauthorized - invalid or missing API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "UNAUTHORIZED"
              message: "Invalid or missing API key"

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "NOT_FOUND"
              message: "The requested resource was not found"

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "INTERNAL_ERROR"
              message: "An unexpected error occurred"

    RateLimitExceeded:
      description: |
        Rate limit exceeded. May reflect either the per-workspace Nooks
        rate limit (see headers in the rate-limiting section) or an
        upstream CRM (Salesforce / HubSpot) rate limit hit while
        servicing the request. Retry after the `Retry-After` interval.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "RATE_LIMIT_EXCEEDED"
              message: "Rate limit exceeded"

    UnprocessableEntity:
      description: |
        Unprocessable entity - the request was well-formed but cannot be
        processed. Typical causes: a referenced resource (prospect, sequence,
        sequence step) does not exist or is in a state that prevents the
        operation; a sequence-step task is missing required associations;
        scheduling exceeds the 30-day limit.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "UNPROCESSABLE_ENTITY"
              message: "Unprocessable entity"
              details:
                message: "Referenced prospect does not exist"

security:
  - BearerAuth: []
