openapi: 3.0.3
info:
  title: Zero API
  description: |
    The Zero API allows you to programmatically access and manage your workspaces, companies, contacts, and deals.
    
    > **Beta Access:** The Zero API is currently in beta. Contact the Zero team to request access.
    
    ## Authentication
    All API requests require a Bearer token in the Authorization header. Contact the Zero team to obtain your API token.
  version: 1.4.0
  contact:
    name: Zero Support
    url: https://zero.inc/support

servers:
  - url: https://api.zero.inc
    description: Production server

security:
  - bearerAuth: []

tags:
  - name: Workspaces
    description: Manage workspaces
  - name: Companies
    description: Manage companies within workspaces
  - name: Contacts
    description: Manage contacts within workspaces
  - name: Deals
    description: Manage deals within workspaces
  - name: Notes
    description: Manage notes attached to companies, contacts, and deals.
  - name: Tasks
    description: Manage tasks. Tasks can be assigned to users and linked to companies, contacts, and deals.
  - name: Columns
    description: |
      Retrieve custom property definitions for a workspace. Use this to discover the IDs, types, and options of custom fields before filtering or updating records.

paths:
  /api/workspaces:
    get:
      tags:
        - Workspaces
      summary: List workspaces
      description: Returns workspaces the authenticated user is a member of.
      operationId: listWorkspaces
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Workspace'
                  total:
                    type: integer
                    description: Total number of records
              example:
                data:
                  - id: "workspace-uuid"
                    name: "Acme Corp"
                    domain: "acme.com"
                    avatar: "https://example.com/avatar.png"
                total: 1
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/companies:
    get:
      tags:
        - Companies
      summary: List companies
      description: |
        Returns companies the user has access to. Filter by workspace using the `where` parameter: `{"workspaceId": "<WORKSPACE_UUID>"}`.
      operationId: listCompanies
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Company'
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Companies
      summary: Create a company
      description: Create a new company in a workspace.
      operationId: createCompany
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyCreate'
            example:
              workspaceId: "workspace-uuid"
              name: "Acme Inc"
              domain: "acme.com"
              linkedin: "acme-inc"
      responses:
        '200':
          description: Company created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Company'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/companies/{companyId}:
    get:
      tags:
        - Companies
      summary: Get a company
      description: Returns a single company by ID.
      operationId: getCompany
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: |
            Successful response. If the company does not exist, the API returns HTTP 200 with an empty body (`{}`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Company'
        '401':
          $ref: '#/components/responses/Unauthorized'
    patch:
      tags:
        - Companies
      summary: Update a company
      description: |
        Update an existing company.
        
        ### Updating custom properties
        
        > **⚠️ Important:** Do **not** pass the entire `custom` object when updating custom properties — this will overwrite all existing custom property values on the record.
        
        Instead, use **dot-notation** to update individual custom properties:
        
        ```json
        // ✅ Correct — only updates the specific custom property
        {"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}
        
        // ❌ Wrong — overwrites the entire custom object, deleting all other values
        {"custom": {"54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}}
        ```
        
        Use `GET /api/columns` to discover the available custom property IDs and types for the workspace.
      operationId: updateCompany
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyUpdate'
            example:
              name: "Acme Corporation"
      responses:
        '200':
          description: Company updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Company'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        # Note: If the company does not exist, the API returns HTTP 200 with {"sideEffects": []} rather than a 404.
    delete:
      tags:
        - Companies
      summary: Delete a company
      description: Delete a company. Use archive=true for soft delete.
      operationId: deleteCompany
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: archive
          in: query
          description: If true, soft deletes (archives) the company instead of permanently deleting it.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Company deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    description: |
                      Hard delete (`archive` omitted or `false`): returns `1` on success.
                      Soft delete (`archive=true`): returns a single-element array containing the archived company object.
                    oneOf:
                      - type: integer
                        example: 1
                      - type: array
                        items:
                          $ref: '#/components/schemas/Company'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/contacts:
    get:
      tags:
        - Contacts
      summary: List contacts
      description: |
        Returns contacts the user has access to. Filter by workspace using the `where` parameter: `{"workspaceId": "<WORKSPACE_UUID>"}`.
      operationId: listContacts
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Contacts
      summary: Create a contact
      description: |
        Create a new contact in a workspace.
        
        Note: If companyId is not provided but email or linkedin is, the system will automatically find or create the associated company.
      operationId: createContact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactCreate'
            example:
              workspaceId: "workspace-uuid"
              name: "John Doe"
              email: "john@acme.com"
              title: "CEO"
              companyId: "company-uuid"
              linkedin: "johndoe"
      responses:
        '200':
          description: Contact created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Contact'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/contacts/{contactId}:
    get:
      tags:
        - Contacts
      summary: Get a contact
      description: Returns a single contact by ID.
      operationId: getContact
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: |
            Successful response. If the contact does not exist, the API returns HTTP 200 with an empty body (`{}`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
    patch:
      tags:
        - Contacts
      summary: Update a contact
      description: |
        Update an existing contact.
        
        ### Updating custom properties
        
        > **⚠️ Important:** Do **not** pass the entire `custom` object when updating custom properties — this will overwrite all existing custom property values on the record.
        
        Instead, use **dot-notation** to update individual custom properties:
        
        ```json
        // ✅ Correct — only updates the specific custom property
        {"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}
        
        // ❌ Wrong — overwrites the entire custom object, deleting all other values
        {"custom": {"54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}}
        ```
        
        Use `GET /api/columns` to discover the available custom property IDs and types for the workspace.
      operationId: updateContact
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactUpdate'
      responses:
        '200':
          description: Contact updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Contact'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        # Note: If the contact does not exist, the API returns HTTP 200 with {"sideEffects": []} rather than a 404.
    delete:
      tags:
        - Contacts
      summary: Delete a contact
      description: Delete a contact. Use archive=true for soft delete.
      operationId: deleteContact
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: archive
          in: query
          description: If true, soft deletes (archives) the contact instead of permanently deleting it.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Contact deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    description: |
                      Hard delete (`archive` omitted or `false`): returns `1` on success.
                      Soft delete (`archive=true`): returns a single-element array containing the archived contact object.
                    oneOf:
                      - type: integer
                        example: 1
                      - type: array
                        items:
                          $ref: '#/components/schemas/Contact'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/deals:
    get:
      tags:
        - Deals
      summary: List deals
      description: |
        Returns deals the user has access to. Filter by workspace using the `where` parameter: `{"workspaceId": "<WORKSPACE_UUID>"}`.
      operationId: listDeals
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Deal'
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Deals
      summary: Create a deal
      description: |
        Create a new deal in a workspace.
        
        Note: If stage is not provided, it will default to the workspace's defaultDealStage setting.
      operationId: createDeal
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DealCreate'
            example:
              workspaceId: "workspace-uuid"
              name: "Enterprise Deal"
              companyId: "company-uuid"
              contactIds:
                - "contact-uuid-1"
                - "contact-uuid-2"
              value: 50000
              closeDate: "2024-06-30"
      responses:
        '200':
          description: Deal created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Deal'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/deals/{dealId}:
    get:
      tags:
        - Deals
      summary: Get a deal
      description: Returns a single deal by ID.
      operationId: getDeal
      parameters:
        - name: dealId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: |
            Successful response. If the deal does not exist, the API returns HTTP 200 with an empty body (`{}`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Deal'
        '401':
          $ref: '#/components/responses/Unauthorized'
    patch:
      tags:
        - Deals
      summary: Update a deal
      description: |
        Update an existing deal.
        
        ### Updating custom properties
        
        > **⚠️ Important:** Do **not** pass the entire `custom` object when updating custom properties — this will overwrite all existing custom property values on the record.
        
        Instead, use **dot-notation** to update individual custom properties:
        
        ```json
        // ✅ Correct — only updates the specific custom property
        {"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}
        
        // ❌ Wrong — overwrites the entire custom object, deleting all other values
        {"custom": {"54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}}
        ```
        
        Use `GET /api/columns` to discover the available custom property IDs and types for the workspace.
      operationId: updateDeal
      parameters:
        - name: dealId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DealUpdate'
      responses:
        '200':
          description: Deal updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Deal'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        # Note: If the deal does not exist, the API returns HTTP 200 with {"sideEffects": []} rather than a 404.
    delete:
      tags:
        - Deals
      summary: Delete a deal
      description: Delete a deal. Use archive=true for soft delete.
      operationId: deleteDeal
      parameters:
        - name: dealId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: archive
          in: query
          description: If true, soft deletes (archives) the deal instead of permanently deleting it.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Deal deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    description: |
                      Hard delete (`archive` omitted or `false`): returns `1` on success.
                      Soft delete (`archive=true`): returns a single-element array containing the archived deal object.
                    oneOf:
                      - type: integer
                        example: 1
                      - type: array
                        items:
                          $ref: '#/components/schemas/Deal'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/columns:
    get:
      tags:
        - Columns
      summary: List custom property definitions
      description: |
        Returns the custom property (column) definitions for a workspace. Filter by workspace using the `where` parameter: `{"workspaceId": "<WORKSPACE_UUID>"}`. Strongly recommended — omitting it returns columns across all workspaces the user has access to.

        Use this endpoint to discover the IDs, types, and available options of custom fields before filtering or updating records.
        
        Custom properties are stored in the `custom` object on companies, contacts, and deals. Each custom property is keyed by its UUID (`id` from this response).
        
        For example, if this endpoint returns a column with `"id": "54e1ca7d-69c3-4b77-8266-8085b5834116"` and `"type": "select"`, you can:
        - **Filter** records using: `{"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": {"$in": ["<OPTION_KEY_UUID>", "<OPTION_KEY_UUID>"]}}`
        - **Update** a record using: `{"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": "<OPTION_KEY_UUID>"}`

        > **Note:** For `select` and `multiselect` columns, option values are UUIDs stored in the `key` field of each option object. Use the `key` UUID (not the human-readable `name`) when filtering or updating records.
      operationId: listColumns
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Column'
                  total:
                    type: integer
                    description: Total number of custom property definitions
              example:
                data:
                  - id: "54e1ca7d-69c3-4b77-8266-8085b5834116"
                    workspaceId: "dd66c03b-1a84-4c9d-b118-3cc54e21d019"
                    name: "Industry"
                    type: "select"
                    entity: "companies"
                    options:
                      - key: "3e839b5c-b311-4887-b2da-727d2d75cdd6"
                        name: "SaaS"
                        color: "#4CAF50"
                      - key: "7f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"
                        name: "Fintech"
                        color: "#2196F3"
                  - id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    workspaceId: "dd66c03b-1a84-4c9d-b118-3cc54e21d019"
                    name: "ARR"
                    type: "currency"
                    entity: "companies"
                    options: null
                  - id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    workspaceId: "dd66c03b-1a84-4c9d-b118-3cc54e21d019"
                    name: "Tags"
                    type: "multiselect"
                    entity: "deals"
                    options:
                      - key: "a1b1c1d1-e1f1-1111-aaaa-111111111111"
                        name: "Enterprise"
                        color: "#FF9800"
                      - key: "b2b2c2d2-e2f2-2222-bbbb-222222222222"
                        name: "Strategic"
                        color: "#9C27B0"
                total: 3
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/notes:
    get:
      tags:
        - Notes
      summary: List notes
      description: |
        Returns notes the user has access to. Filter by workspace using the `where` parameter: `{"workspaceId": "<WORKSPACE_UUID>"}`.

        Notes can also be filtered by `contactId`, `companyId`, or `dealId` to retrieve notes for a specific record.
      operationId: listNotes
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Note'
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Notes
      summary: Create a note
      description: |
        Create a new note in a workspace. Notes can be associated with a company, contact, or deal (or any combination).

        The `content` field uses the [Tiptap](https://tiptap.dev/) document format — a JSON structure with a `type: "doc"` root node containing an array of block nodes (paragraphs, headings, etc.).

        ### Minimal Tiptap document

        An empty note body:
        ```json
        {"type": "doc", "content": []}
        ```

        ### Tiptap document with text

        ```json
        {
          "type": "doc",
          "content": [
            {
              "type": "paragraph",
              "content": [
                {"type": "text", "text": "Your note text here"}
              ]
            }
          ]
        }
        ```

        The `content` field can be omitted on creation and set later via `PATCH`.
      operationId: createNote
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteCreate'
            example:
              workspaceId: "workspace-uuid"
              name: "Meeting notes"
              emoji: "🗒️"
              contactId: "contact-uuid"
              companyId: "company-uuid"
              content:
                type: "doc"
                content:
                  - type: "paragraph"
                    content:
                      - type: "text"
                        text: "Discussed Q2 roadmap and pricing."
      responses:
        '200':
          description: Note created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Note'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/notes/{noteId}:
    patch:
      tags:
        - Notes
      summary: Update a note
      description: |
        Update an existing note.

        > **Note:** Unlike other PATCH endpoints, the response only returns the fields that were changed — not the full note object.

        To update the note body, pass a complete Tiptap document in `content` — this replaces the existing content entirely.
      operationId: updateNote
      parameters:
        - name: noteId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteUpdate'
            example:
              name: "Updated meeting notes"
              emoji: "🔥"
              content:
                type: "doc"
                content:
                  - type: "paragraph"
                    content:
                      - type: "text"
                        text: "Updated note content."
      responses:
        '200':
          description: |
            Note updated successfully. Only the fields that were changed are returned in `data` (not the full note object).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    description: Partial note object containing only the updated fields plus `updatedAt` and `updatedById`.
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
    delete:
      tags:
        - Notes
      summary: Delete a note
      description: Delete a note. Use `archive=true` for soft delete (recoverable), or omit for permanent deletion.
      operationId: deleteNote
      parameters:
        - name: noteId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: archive
          in: query
          description: If true, soft deletes (archives) the note instead of permanently deleting it.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Note deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    description: |
                      Hard delete (`archive` omitted or `false`): returns `1` on success.
                      Soft delete (`archive=true`): returns a single-element array containing the archived note object.
                    oneOf:
                      - type: integer
                        example: 1
                      - type: array
                        items:
                          $ref: '#/components/schemas/Note'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'


  /api/tasks:
    get:
      tags:
        - Tasks
      summary: List tasks
      description: |
        Returns tasks the user has access to. Filter by workspace using the `where` parameter: `{"workspaceId": "<WORKSPACE_UUID>"}`.

        Common filters:
        - Open tasks: `{"workspaceId": "<WS>", "done": false}`
        - Tasks for a contact: `{"workspaceId": "<WS>", "contactIds": {"$overlaps": ["<CONTACT_UUID>"]}}`
        - Tasks by deadline: `{"workspaceId": "<WS>", "deadline": {"$gte": "now()"}}`
      operationId: listTasks
      parameters:
        - $ref: '#/components/parameters/fields'
        - $ref: '#/components/parameters/where'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/orderBy'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Task'
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Tasks
      summary: Create a task
      description: |
        Create a new task in a workspace.

        ### content vs description

        Tasks have two Tiptap fields with distinct purposes:

        - **`content`** — The task title as a Tiptap document. If omitted, it is automatically generated from the `name` field.
        - **`description`** — Extended notes or context for the task. Supports rich formatting including headings, bullet lists, and task lists. Optional.

        ### Tiptap document format

        Both `content` and `description` use the same Tiptap document structure:

        ```json
        {
          "type": "doc",
          "content": [
            {
              "type": "paragraph",
              "content": [{"type": "text", "text": "Your text here"}]
            }
          ]
        }
        ```

        Rich formatting example with headings, bold text, bullet lists and task lists:

        ```json
        {
          "type": "doc",
          "content": [
            {
              "type": "heading",
              "attrs": {"level": 3},
              "content": [{"type": "text", "text": "Goal"}]
            },
            {
              "type": "paragraph",
              "content": [
                {"type": "text", "text": "Complete the "},
                {"type": "text", "text": "onboarding proposal", "marks": [{"type": "bold"}]},
                {"type": "text", "text": " before Friday."}
              ]
            },
            {
              "type": "taskList",
              "content": [
                {
                  "type": "taskItem",
                  "attrs": {"checked": false},
                  "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Review requirements"}]}]
                },
                {
                  "type": "taskItem",
                  "attrs": {"checked": false},
                  "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Draft document"}]}]
                }
              ]
            },
            {
              "type": "bulletList",
              "content": [
                {
                  "type": "listItem",
                  "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Keep it concise"}]}]
                }
              ]
            }
          ]
        }
        ```

        ### Priority

        | Value | Label |
        |-------|-------|
        | `null` | No priority |
        | `1` | Low |
        | `2` | Medium |
        | `3` | High |
        | `4` | Urgent |

        ### Linking records

        Tasks can be linked to multiple companies, contacts, and deals simultaneously via `companyIds`, `contactIds`, and `dealIds`.

        > **⚠️ Important:** When updating array fields via `PATCH`, the entire array is **replaced** — not merged. Always include all desired IDs when patching these fields.
      operationId: createTask
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskCreate'
            example:
              workspaceId: "workspace-uuid"
              name: "Follow up with contact"
              contactIds: ["contact-uuid"]
              companyIds: ["company-uuid"]
              assignedToIds: ["user-uuid"]
              deadline: "2026-03-10T10:00:00.000Z"
              priority: 3
              description:
                type: "doc"
                content:
                  - type: "paragraph"
                    content:
                      - type: "text"
                        text: "Send a follow-up email after the demo call."
      responses:
        '200':
          description: Task created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Task'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/tasks/{taskId}:
    get:
      tags:
        - Tasks
      summary: Get a task
      description: |
        Retrieve a single task by ID.

        If the task does not exist, the API returns HTTP 200 with an empty body (`{}`).
      operationId: getTask
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Task'
        '401':
          $ref: '#/components/responses/Unauthorized'
    patch:
      tags:
        - Tasks
      summary: Update a task
      description: |
        Update an existing task.

        > **Note:** The response only returns the fields that were changed — not the full task object.

        > **⚠️ Important:** Array fields (`contactIds`, `companyIds`, `dealIds`, `assignedToIds`) are **replaced** on PATCH, not merged. Always include all desired IDs.

        To mark a task as complete: `{"done": true}`
      operationId: updateTask
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskUpdate'
            examples:
              markDone:
                summary: Mark as done
                value:
                  done: true
              updateDeadline:
                summary: Update deadline and priority
                value:
                  deadline: "2026-03-15T10:00:00.000Z"
                  priority: 4
              reassign:
                summary: Reassign to different users
                value:
                  assignedToIds: ["user-uuid-1", "user-uuid-2"]
      responses:
        '200':
          description: |
            Task updated successfully. Only the fields that were changed are returned in `data`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    description: Partial task object containing only the updated fields plus `updatedAt` and `updatedById`.
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
    delete:
      tags:
        - Tasks
      summary: Delete a task
      description: Delete a task. Use `archive=true` for soft delete (recoverable), or omit for permanent deletion.
      operationId: deleteTask
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: archive
          in: query
          description: If true, soft deletes (archives) the task instead of permanently deleting it.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Task deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    description: |
                      Hard delete (`archive` omitted or `false`): returns `1` on success.
                      Soft delete (`archive=true`): returns a single-element array containing the archived task object.
                    oneOf:
                      - type: integer
                        example: 1
                      - type: array
                        items:
                          $ref: '#/components/schemas/Task'
                  sideEffects:
                    type: array
                    items: {}
        '401':
          $ref: '#/components/responses/Unauthorized'


components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: All API requests require a Bearer token in the Authorization header. Contact the Zero team to obtain your API token.
      x-gitbook-token-placeholder: YOUR_API_TOKEN

  parameters:
    fields:
      name: fields
      in: query
      description: Comma-separated list of fields to return. Defaults to all fields.
      schema:
        type: string
      example: id,name,domain
    where:
      name: where
      in: query
      description: |
        JSON-encoded filter object. All top-level conditions are combined with AND logic. Use `$or` for OR logic.
        
        The available operators depend on the **data type** of the field you are filtering on.

        ---

        ## String fields
        Fields like `name`, `domain`, `description`, `linkedin`, `source`, `externalId`, `location`.

        | Operator | Description | Example |
        |----------|-------------|---------|
        | *(exact)* | Exact match | `{"name": "Linear"}` |
        | `$eq` | Explicit exact match | `{"name": {"$eq": "Linear"}}` |
        | `$not` | Not equal | `{"source": {"$not": "import"}}` |
        | `$in` | Matches any value in array | `{"domain": {"$in": ["linear.app", "granola.so"]}}` |
        | `$notIn` | Matches none of the values | `{"source": {"$notIn": ["import", "api"]}}` |
        | `$contains` | Case-insensitive word-boundary substring match | `{"name": {"$contains": "YC"}}` |
        | `$notContains` | Does not contain | `{"name": {"$notContains": "Test"}}` |
        | `$containsAny` | Contains any of the given strings | `{"name": {"$containsAny": ["YC", "Techstars"]}}` |
        | `$startsWith` | Starts with prefix | `{"domain": {"$startsWith": "app."}}` |
        | `$endsWith` | Ends with suffix | `{"email": {"$endsWith": "@zero.inc"}}` |
        | `$exists` | Field is present and truthy | `{"linkedin": {"$exists": true}}` |
        | `$notExists` | Field is absent, null, or empty | `{"linkedin": {"$notExists": true}}` |

        ---

        ## Number fields
        Fields like `value`, `confidence`.

        | Operator | Description | Example |
        |----------|-------------|---------|
        | *(exact)* | Exact match | `{"value": 5000}` |
        | `$eq` | Explicit exact match | `{"value": {"$eq": 5000}}` |
        | `$not` | Not equal | `{"value": {"$not": 0}}` |
        | `$gt` | Greater than | `{"value": {"$gt": 10000}}` |
        | `$gte` | Greater than or equal | `{"value": {"$gte": 5000}}` |
        | `$lt` | Less than | `{"value": {"$lt": 10000}}` |
        | `$lte` | Less than or equal | `{"value": {"$lte": 50000}}` |
        | `$in` | Matches any value in array | `{"confidence": {"$in": [0.25, 0.5, 0.75]}}` |
        | `$notIn` | Matches none of the values | `{"confidence": {"$notIn": [0, 1]}}` |
        | `$exists` | Field is present and truthy | `{"value": {"$exists": true}}` |
        | `$notExists` | Field is absent, null, or zero | `{"value": {"$notExists": true}}` |

        Multiple operators can be combined on one field:
        ```json
        {"value": {"$gte": 5000, "$lt": 10000}}
        ```

        ---

        ## Date fields
        Fields like `closeDate`, `startDate`, `endDate`, `createdAt`, `updatedAt`.

        Values can be ISO 8601 strings (`"2026-01-01"`, `"2026-01-01T00:00:00Z"`) or **relative time macros**.

        **Relative time macros:** `+Nd` / `-Nd` (days), `+Nw` / `-Nw` (weeks), `+Nm` / `-Nm` (months), `+Ny` / `-Ny` (years), `+Nh` / `-Nh` (hours), `+Ns` / `-Ns` (seconds), `now()`.

        | Operator | Description | Example |
        |----------|-------------|---------|
        | `$gte` | On or after | `{"closeDate": {"$gte": "2026-01-01"}}` |
        | `$lte` | On or before | `{"closeDate": {"$lte": "2026-03-31"}}` |
        | `$gt` | After | `{"createdAt": {"$gt": "2026-01-01T00:00:00Z"}}` |
        | `$lt` | Before | `{"createdAt": {"$lt": "now()"}}` |
        | `$date` | Exact date match (compares date portion only) | `{"closeDate": {"$date": "2026-01-15"}}` |
        | `$exists` | Field is present and truthy | `{"closeDate": {"$exists": true}}` |
        | `$notExists` | Field is absent or null | `{"closeDate": {"$notExists": true}}` |

        Date range example:
        ```json
        {"closeDate": {"$gte": "2026-01-01", "$lte": "2026-03-31"}}
        ```
        Relative date example (closing in next 30 days):
        ```json
        {"closeDate": {"$gte": "now()", "$lte": "+30d"}}
        ```

        ---

        ## Array fields
        Fields like `listIds`, `ownerIds`, `contactIds`.

        | Operator | Description | Example |
        |----------|-------------|---------|
        | `$includes` | Array contains the given value (use this — bare exact match is not supported) | `{"listIds": {"$includes": "<LIST_UUID>"}}` |
        | `$notIncludes` | Array does not contain the given value | `{"ownerIds": {"$notIncludes": "<USER_UUID>"}}` |
        | `$overlaps` | Array contains at least one of the given values | `{"ownerIds": {"$overlaps": ["<UUID_1>", "<UUID_2>"]}}` |
        | `$notOverlaps` | Array contains none of the given values | `{"listIds": {"$notOverlaps": ["<UUID_1>", "<UUID_2>"]}}` |
        | `$all` | Array contains all of the given values | `{"listIds": {"$all": ["<UUID_1>", "<UUID_2>"]}}` |
        | `$length` | Filter by array length (supports nested operators) | `{"contactIds": {"$length": {"$gte": 2}}}` |
        | `$exists` | Field is present and non-empty | `{"ownerIds": {"$exists": true}}` |
        | `$notExists` | Field is absent or empty | `{"ownerIds": {"$notExists": true}}` |

        ---

        ## UUID / ID fields
        Fields like `id`, `workspaceId`, `companyId`, `stage`.

        | Operator | Description | Example |
        |----------|-------------|---------|
        | *(exact)* | Exact match | `{"stage": "<PIPELINE_STAGE_UUID>"}` |
        | `$in` | Matches any value in array | `{"stage": {"$in": ["<UUID_1>", "<UUID_2>"]}}` |
        | `$notIn` | Matches none of the values | `{"stage": {"$notIn": ["<UUID_1>"]}}` |
        | `$not` | Not equal | `{"stage": {"$not": "<UUID>"}}` |

        ---

        ## Boolean fields
        Fields like `archived`.

        | Operator | Description | Example |
        |----------|-------------|---------|
        | *(exact)* | Exact match | `{"archived": false}` |
        | `$not` | Not equal | `{"archived": {"$not": true}}` |

        ---

        ## Logical operators
        These work across all data types.

        `$or` — matches if **any** sub-condition is true:
        ```json
        {
          "workspaceId": "<WORKSPACE_UUID>",
          "$or": [
            {"ownerIds": {"$overlaps": ["<USER_UUID_1>", "<USER_UUID_2>"]}},
            {"closeDate": {"$gte": "2026-01-01"}}
          ]
        }
        ```

        `$and` — matches if **all** sub-conditions are true (useful when you need multiple conditions on the same field):
        ```json
        {
          "$and": [
            {"name": {"$contains": "Enterprise"}},
            {"name": {"$notContains": "Test"}}
          ]
        }
        ```

        ---

        ## Dot-notation (relation filtering)
        Use dot-syntax to filter records based on properties of related objects:
        ```json
        {"company.name": "Linear"}
        {"company.domain": {"$in": ["linear.app", "granola.so"]}}
        {"company.location.city": "San Francisco"}
        {"companyProfile.categories": {"$overlaps": ["Sales", "Marketing"]}}
        ```
        All operators available for the target field's data type can be used with dot-notation.

        ---

        ## Custom property filtering
        Custom properties are stored in the `custom` object on records, keyed by UUID. Use `GET /api/columns` to discover the available custom property IDs, types, and options for a workspace.
        
        Filter on custom properties using `custom.<COLUMN_ID>` with operators appropriate for the column's type.
        
        > **Note:** For `select` and `multiselect` columns, option values are UUIDs (the `key` field from the column's `options` array). Use the UUID, not the human-readable name.
        
        ```json
        // Text custom property
        {"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": {"$contains": "enterprise"}}
        
        // Select custom property (use the option key UUID)
        {"custom.a1b2c3d4-e5f6-7890-abcd-ef1234567890": "3e839b5c-b311-4887-b2da-727d2d75cdd6"}
        
        // Multi-select custom property (use option key UUIDs)
        {"custom.b2c3d4e5-f6a7-8901-bcde-f12345678901": {"$overlaps": ["a1b1c1d1-e1f1-1111-aaaa-111111111111", "b2b2c2d2-e2f2-2222-bbbb-222222222222"]}}
        
        // Currency/number custom property
        {"custom.c3d4e5f6-a7b8-9012-cdef-123456789012": {"$gte": 100000}}
        
        // Date custom property
        {"custom.d4e5f6a7-b8c9-0123-defa-234567890123": {"$gte": "2026-01-01"}}
        
        // Boolean custom property
        {"custom.e5f6a7b8-c9d0-1234-efab-345678901234": true}
        
        // Check if custom property has a value
        {"custom.f6a7b8c9-d0e1-2345-fabc-456789012345": {"$exists": true}}
        ```

        ---

        ## Complex example
        All top-level keys are ANDed together:
        ```json
        {
          "name": {"$contains": "Zero"},
          "location.city": "Helsinki",
          "location.country": {"$in": ["United Kingdom", "Germany", "Sweden"]},
          "value": {"$gte": 10000},
          "closeDate": {"$gte": "2026-01-01", "$lte": "2026-03-31"},
          "ownerIds": {"$includes": "<USER_UUID>"},
          "stage": {"$in": ["<UUID_1>", "<UUID_2>"]},
          "lastActivity": {"$exists": true},
          "companyProfile.categories": {"$overlaps": ["Sales", "Marketing"]},
          "custom.54e1ca7d-69c3-4b77-8266-8085b5834116": {"$contains": "enterprise"}
        }
        ```
      schema:
        type: string
      example: '{"stage":"<PIPELINE_STAGE_UUID>"}'
    limit:
      name: limit
      in: query
      description: Maximum number of records to return
      schema:
        type: integer
        default: 100
    offset:
      name: offset
      in: query
      description: Pagination offset
      schema:
        type: integer
        default: 0
    orderBy:
      name: orderBy
      in: query
      description: JSON string for sort order
      schema:
        type: string
      example: '{"name":"asc"}'

  responses:
    Unauthorized:
      description: Authentication failed or token is invalid/expired
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Session not found
    NotFound:
      description: The requested resource was not found
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Not found

  schemas:
    Workspace:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        key:
          type: string
        domain:
          type: string
        avatar:
          type: string
          format: uri
        color:
          type: string
        settings:
          type: object
        type:
          type: string
        trialEndsAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        archived:
          type: boolean

    Company:
      type: object
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
        domain:
          type: string
        logo:
          type: string
          format: uri
        description:
          type: string
        linkedin:
          type: string
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        location:
          type: object
          description: |
            Geographic location of the company. Stored as a structured object, not a plain string.
          properties:
            city:
              type: string
            state:
              type: string
            country:
              type: string
            continent:
              type: string
            countryCode:
              type: string
            stateCode:
              type: string
            coordinates:
              type: object
              properties:
                lat:
                  type: number
                lng:
                  type: number
        parentCompanyId:
          type: string
          format: uuid
          nullable: true
          description: ID of the parent company, for hierarchical company structures.
        custom:
          type: object
          description: |
            Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `"custom.<COLUMN_ID>": "value"`) to avoid overwriting other custom properties.
        externalId:
          type: string
          description: ID from external system for integrations
        source:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        archived:
          type: boolean
        createdById:
          type: string
          format: uuid

    CompanyCreate:
      type: object
      required:
        - workspaceId
      properties:
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
        domain:
          type: string
        logo:
          type: string
          format: uri
        description:
          type: string
        linkedin:
          type: string
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        location:
          type: object
          description: Geographic location as a structured object. Do not pass a plain string.
          properties:
            city:
              type: string
            state:
              type: string
            country:
              type: string
            continent:
              type: string
        parentCompanyId:
          type: string
          format: uuid
          description: ID of the parent company.
        custom:
          type: object
        externalId:
          type: string
        source:
          type: string

    CompanyUpdate:
      type: object
      properties:
        name:
          type: string
        domain:
          type: string
        logo:
          type: string
          format: uri
        description:
          type: string
        linkedin:
          type: string
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        location:
          type: object
          description: Geographic location as a structured object. Do not pass a plain string.
          properties:
            city:
              type: string
            state:
              type: string
            country:
              type: string
            continent:
              type: string
        parentCompanyId:
          type: string
          format: uuid
        custom:
          type: object
        externalId:
          type: string
        source:
          type: string

    Contact:
      type: object
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        companyId:
          type: string
          format: uuid
        name:
          type: string
        email:
          type: string
          format: email
        title:
          type: string
        phone:
          type: string
        linkedin:
          type: string
        x:
          type: string
          description: X (formerly Twitter) handle
        facebook:
          type: string
        github:
          type: string
        avatar:
          type: string
          format: uri
        location:
          type: object
          description: |
            Geographic location of the contact. Stored as a structured object, not a plain string.
          properties:
            city:
              type: string
            state:
              type: string
            country:
              type: string
            continent:
              type: string
            countryCode:
              type: string
            stateCode:
              type: string
            coordinates:
              type: object
              properties:
                lat:
                  type: number
                lng:
                  type: number
        type:
          type: string
        custom:
          type: object
          description: |
            Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `"custom.<COLUMN_ID>": "value"`) to avoid overwriting other custom properties.
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        externalId:
          type: string
        source:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        archived:
          type: boolean
        createdById:
          type: string
          format: uuid

    ContactCreate:
      type: object
      required:
        - workspaceId
      properties:
        workspaceId:
          type: string
          format: uuid
        companyId:
          type: string
          format: uuid
          description: If not provided but email or linkedin is given, the system will automatically find or create the associated company.
        name:
          type: string
        email:
          type: string
          format: email
        title:
          type: string
        phone:
          type: string
        linkedin:
          type: string
        x:
          type: string
          description: X (formerly Twitter) handle
        facebook:
          type: string
        github:
          type: string
        avatar:
          type: string
          format: uri
        location:
          type: object
          description: Geographic location as a structured object. Do not pass a plain string.
          properties:
            city:
              type: string
            state:
              type: string
            country:
              type: string
            continent:
              type: string
        type:
          type: string
        custom:
          type: object
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        externalId:
          type: string
        source:
          type: string

    ContactUpdate:
      type: object
      properties:
        companyId:
          type: string
          format: uuid
        name:
          type: string
        email:
          type: string
          format: email
        title:
          type: string
        phone:
          type: string
        linkedin:
          type: string
        x:
          type: string
          description: X (formerly Twitter) handle
        facebook:
          type: string
        github:
          type: string
        avatar:
          type: string
          format: uri
        location:
          type: object
          description: Geographic location as a structured object. Do not pass a plain string.
          properties:
            city:
              type: string
            state:
              type: string
            country:
              type: string
            continent:
              type: string
        type:
          type: string
        custom:
          type: object
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        externalId:
          type: string
        source:
          type: string

    Deal:
      type: object
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        pipelineId:
          type: string
          format: uuid
          description: The pipeline this deal belongs to.
        companyId:
          type: string
          format: uuid
        contactIds:
          type: array
          items:
            type: string
            format: uuid
        name:
          type: string
        stage:
          type: string
          description: UUID of the pipeline stage.
        value:
          type: number
        confidence:
          type: number
          minimum: 0
          maximum: 1
          description: |
            Win probability as a decimal fraction between 0 and 1 (e.g. `0.75` = 75%).
            Do not use percentages (0–100) — values outside the 0–1 range will fail.
        closeDate:
          type: string
          format: date
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        custom:
          type: object
          description: |
            Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `"custom.<COLUMN_ID>": "value"`) to avoid overwriting other custom properties.
        externalId:
          type: string
        source:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        archived:
          type: boolean
        createdById:
          type: string
          format: uuid

    DealCreate:
      type: object
      required:
        - workspaceId
      properties:
        workspaceId:
          type: string
          format: uuid
        companyId:
          type: string
          format: uuid
        contactIds:
          type: array
          items:
            type: string
            format: uuid
        name:
          type: string
        stage:
          type: string
          description: If not provided, defaults to the workspace defaultDealStage setting.
        value:
          type: number
        confidence:
          type: number
          minimum: 0
          maximum: 1
          description: Win probability as a decimal fraction between 0 and 1 (e.g. `0.75` = 75%). Do not use percentages.
        closeDate:
          type: string
          format: date
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        custom:
          type: object
        externalId:
          type: string
        source:
          type: string

    DealUpdate:
      type: object
      properties:
        companyId:
          type: string
          format: uuid
        contactIds:
          type: array
          items:
            type: string
            format: uuid
        name:
          type: string
        stage:
          type: string
        value:
          type: number
        confidence:
          type: number
          minimum: 0
          maximum: 1
          description: Win probability as a decimal fraction between 0 and 1 (e.g. `0.75` = 75%). Do not use percentages.
        closeDate:
          type: string
          format: date
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
        listIds:
          type: array
          items:
            type: string
            format: uuid
        ownerIds:
          type: array
          items:
            type: string
            format: uuid
        custom:
          type: object
        externalId:
          type: string
        source:
          type: string

    Column:
      type: object
      description: A custom property definition. The `id` is used as the key inside the `custom` object on records.
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier for this custom property. Use this as the key when filtering or updating custom fields on records.
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
          description: Human-readable name of the custom property
        description:
          type: string
          nullable: true
          description: Optional description of the custom property
        key:
          type: string
          description: Dot-notation path for this column, e.g. `custom.<UUID>`. Convenient for constructing filter and update payloads.
        type:
          type: string
          enum:
            - text
            - richtext
            - number
            - currency
            - date
            - timestamp
            - select
            - multiselect
            - boolean
            - url
            - email
            - phone
            - user
            - company
            - companies
            - contact
            - contacts
          description: |
            The data type of the custom property.
            
            | Type | Description |
            |------|-------------|
            | `text` | Plain text string |
            | `richtext` | Rich-text / formatted content |
            | `number` | Numeric value |
            | `currency` | Monetary value |
            | `date` | Date (no time) |
            | `timestamp` | Date and time |
            | `select` | Single choice from a predefined list — use option `key` UUIDs |
            | `multiselect` | Multiple choices from a predefined list — use option `key` UUIDs |
            | `boolean` | True/false |
            | `url` | URL string |
            | `email` | Email address |
            | `phone` | Phone number |
            | `user` | Reference to a workspace user |
            | `company` | Relation to a single company record |
            | `companies` | Relation to multiple company records |
            | `contact` | Relation to a single contact record |
            | `contacts` | Relation to multiple contact records |
        entity:
          type: string
          enum:
            - companies
            - contacts
            - deals
          description: Which record type this custom property belongs to (plural form).
        options:
          type: array
          nullable: true
          description: |
            Available options for `select` and `multiselect` type columns. `null` for all other types.
            
            > **Important:** Use the option `key` UUID (not the `name`) when filtering or updating records.
          items:
            type: object
            properties:
              key:
                type: string
                format: uuid
                description: The UUID value stored on records. Use this when filtering (`$in`, `$overlaps`, exact match) or writing to a select/multiselect field.
              name:
                type: string
                description: Human-readable display name for the option
              color:
                type: string
                description: Hex color code for UI display (e.g. `#4CAF50`)
        ai:
          type: boolean
          description: Whether this column is AI-generated
        archived:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        createdById:
          type: string
          format: uuid

    Note:
      type: object
      description: A note attached to one or more records (company, contact, or deal).
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
          description: Title of the note
        emoji:
          type: string
          description: Emoji icon for the note (e.g. "🗒️", "🔥")
        content:
          type: object
          nullable: true
          description: |
            Note body in [Tiptap](https://tiptap.dev/) document format. A JSON object with `type: "doc"` at the root containing an array of block nodes.

            ```json
            {
              "type": "doc",
              "content": [
                {
                  "type": "paragraph",
                  "content": [
                    {"type": "text", "text": "Your note text here"}
                  ]
                }
              ]
            }
            ```
        companyId:
          type: string
          format: uuid
          nullable: true
        contactId:
          type: string
          format: uuid
          nullable: true
        dealId:
          type: string
          format: uuid
          nullable: true
        archived:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        createdById:
          type: string
          format: uuid
        externalId:
          type: string
          nullable: true
        source:
          type: string
          nullable: true

    NoteCreate:
      type: object
      required:
        - workspaceId
      properties:
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
          description: Title of the note
        emoji:
          type: string
          description: Emoji icon for the note
        content:
          type: object
          description: Note body in Tiptap document format. Can be omitted and set later via PATCH.
        companyId:
          type: string
          format: uuid
          description: Associate the note with a company
        contactId:
          type: string
          format: uuid
          description: Associate the note with a contact
        dealId:
          type: string
          format: uuid
          description: Associate the note with a deal
        externalId:
          type: string
        source:
          type: string

    NoteUpdate:
      type: object
      properties:
        name:
          type: string
        emoji:
          type: string
        content:
          type: object
          description: Replaces the entire note body. Must be a valid Tiptap document.
        companyId:
          type: string
          format: uuid
        contactId:
          type: string
          format: uuid
        dealId:
          type: string
          format: uuid
        externalId:
          type: string
        source:
          type: string

    Task:
      type: object
      description: A task that can be assigned to users and linked to companies, contacts, and deals.
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
          description: Title of the task
        done:
          type: boolean
          description: Whether the task has been completed
          default: false
        priority:
          type: integer
          nullable: true
          description: |
            Task priority level.
            - `null` — No priority
            - `1` — Low
            - `2` — Medium
            - `3` — High
            - `4` — Urgent
          enum: [null, 1, 2, 3, 4]
        deadline:
          type: string
          format: date-time
          nullable: true
          description: Due date and time (ISO 8601)
        content:
          type: object
          description: Task title as a Tiptap document. Auto-generated from `name` if not provided on creation.
        description:
          type: object
          description: Extended notes or context for the task, in Tiptap document format. Supports rich formatting.
        companyIds:
          type: array
          items:
            type: string
            format: uuid
          description: Companies linked to this task
        contactIds:
          type: array
          items:
            type: string
            format: uuid
          description: Contacts linked to this task
        dealIds:
          type: array
          items:
            type: string
            format: uuid
          description: Deals linked to this task
        assignedToIds:
          type: array
          items:
            type: string
            format: uuid
          description: User IDs this task is assigned to
        type:
          type: string
          nullable: true
          description: |
            Set automatically by integrations (e.g. `circleback` for meeting-generated tasks, `ai-generated` for AI-created tasks). Not intended to be set manually via the API.
        message:
          type: string
          nullable: true
        archived:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        createdById:
          type: string
          format: uuid
          nullable: true
        externalId:
          type: string
          nullable: true
        source:
          type: string
          nullable: true

    TaskCreate:
      type: object
      required:
        - workspaceId
        - name
      properties:
        workspaceId:
          type: string
          format: uuid
        name:
          type: string
          description: Title of the task. Also used to auto-generate `content` if `content` is not provided.
        content:
          type: object
          description: Task title as a Tiptap document. If omitted, auto-generated from `name`.
        description:
          type: object
          description: Extended notes in Tiptap document format. Supports headings, bullet lists, task lists, and inline formatting.
        done:
          type: boolean
          default: false
        priority:
          type: integer
          nullable: true
          description: "`null` = No priority, `1` = Low, `2` = Medium, `3` = High, `4` = Urgent"
          enum: [null, 1, 2, 3, 4]
        deadline:
          type: string
          format: date-time
        companyIds:
          type: array
          items:
            type: string
            format: uuid
        contactIds:
          type: array
          items:
            type: string
            format: uuid
        dealIds:
          type: array
          items:
            type: string
            format: uuid
        assignedToIds:
          type: array
          items:
            type: string
            format: uuid
        externalId:
          type: string
        source:
          type: string

    TaskUpdate:
      type: object
      description: |
        All fields are optional. Array fields (`contactIds`, `companyIds`, `dealIds`, `assignedToIds`) replace the existing array entirely on update.
      properties:
        name:
          type: string
        content:
          type: object
          description: Replaces the entire task content. Must be a valid Tiptap document.
        description:
          type: object
          description: Replaces the entire description. Must be a valid Tiptap document.
        done:
          type: boolean
        priority:
          type: integer
          nullable: true
          enum: [null, 1, 2, 3, 4]
        deadline:
          type: string
          format: date-time
          nullable: true
        companyIds:
          type: array
          items:
            type: string
            format: uuid
        contactIds:
          type: array
          items:
            type: string
            format: uuid
        dealIds:
          type: array
          items:
            type: string
            format: uuid
        assignedToIds:
          type: array
          items:
            type: string
            format: uuid
        externalId:
          type: string
        source:
          type: string
