Coperniq opportunities API

The opportunities API from Coperniq — 3 operation(s) for opportunities.

OpenAPI Specification

coperniq-opportunities-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: API Key accounts opportunities API
  version: 1.0.0
servers:
- url: https://api.coperniq.io/v1
  description: Production server
tags:
- name: opportunities
paths:
  /opportunities:
    get:
      operationId: list-opportunities
      summary: List Opportunities
      description: 'Retrieve a paginated list of opportunities.


        Supports:

        - Pagination (`page_size`, `page`)

        - Date filtering (`updated_after`, `updated_before`)

        - Sorting (`order_by`, default: desc)

        - Field search (`title`, `address`, `primaryName`, `primaryPhone`, `primaryEmail`)

        - Full text search (`q`)


        **Note:** The `/requests` path is an alias for `/opportunities` and will continue to work until users are individually notified and migrated.


        **Note:** If an opportunity has no `title` (empty or whitespace-only), the response returns the parent account''s `title` instead. The stored value is not modified — this is a read-time fallback only.

        '
      tags:
      - opportunities
      parameters:
      - name: page_size
        in: query
        description: Number of items per page (max 100)
        required: false
        schema:
          type: integer
          default: 20
      - name: page
        in: query
        description: Page number (1-based)
        required: false
        schema:
          type: integer
          default: 1
      - name: updated_after
        in: query
        description: Filter items updated after this timestamp (ISO 8601)
        required: false
        schema:
          type: string
          format: date-time
      - name: updated_before
        in: query
        description: Filter items updated before this timestamp (ISO 8601)
        required: false
        schema:
          type: string
          format: date-time
      - name: order_by
        in: query
        description: Sort order for results
        required: false
        schema:
          $ref: '#/components/schemas/OpportunitiesGetParametersOrderBy'
      - name: include_virtual_properties
        in: query
        description: Whether to include virtual properties in the response. Defaults to false unless explicitly set to true.
        required: false
        schema:
          type: boolean
          default: false
      - name: include_archived
        in: query
        description: Whether to include archived (inactive) records in the response. By default only active records are returned.
        required: false
        schema:
          type: boolean
          default: false
      - name: q
        in: query
        description: Full text search query
        required: false
        schema:
          type: string
      - name: title
        in: query
        description: Title search query
        required: false
        schema:
          type: string
      - name: address
        in: query
        description: Address search query
        required: false
        schema:
          type: string
      - name: primaryName
        in: query
        description: Contact name search query
        required: false
        schema:
          type: string
      - name: primaryPhone
        in: query
        description: Contact phone search query
        required: false
        schema:
          type: string
      - name: primaryEmail
        in: query
        description: Contact email search query
        required: false
        schema:
          type: string
      - name: x-api-key
        in: header
        required: true
        schema:
          type: string
      responses:
        '200':
          description: List of opportunities
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Opportunity'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListOpportunitiesRequestUnauthorizedError'
    post:
      operationId: create-opportunity
      summary: Create Opportunity
      description: 'Create a new opportunity with required and optional fields.


        Required fields:

        - `title`: Opportunity title/name

        - `address`: Opportunity location

        - `trades`: Array of trade types


        Optional fields:

        - `accountId`: Link to existing account

        - `workflowId`: Link to workflow

        - `phaseId`: Link to phase

        - `value`: Opportunity value

        - `size`: Opportunity size

        - `confidence`: Confidence score (0-100)

        - `primaryEmail`/`primaryPhone`: Contact information

        - `contacts`: Contact IDs only (no name, email, phone, etc.). The contacts must be created first via POST /contacts.

        - `custom`: Custom fields object


        Note: If primaryEmail or primaryPhone is provided, a contact will be automatically created or matched.


        **Note:** The `/requests` path is an alias for `/opportunities` and will continue to work until users are individually notified and migrated.

        '
      tags:
      - opportunities
      parameters:
      - name: allow_new_options
        in: query
        description: Whether to allow creation of new dropdown options during record creation
        required: false
        schema:
          type: boolean
          default: false
      - name: match_by
        in: query
        description: Field to use for matching existing records
        required: false
        schema:
          $ref: '#/components/schemas/OpportunitiesPostParametersMatchBy'
      - name: match_found_strategy
        in: query
        description: Strategy to use when a match is found
        required: false
        schema:
          $ref: '#/components/schemas/OpportunitiesPostParametersMatchFoundStrategy'
      - name: x-api-key
        in: header
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Matching opportunity found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Opportunity'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateOpportunityRequestBadRequestError'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateOpportunityRequestUnauthorizedError'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpportunityUpsert'
  /opportunities/search:
    get:
      operationId: search-opportunities
      summary: Search Opportunities
      description: "Search opportunities using up to two filters (prop1/op1/value1 and optionally prop2/op2/value2), combined with `logic` (AND/OR).\n\nProperties:\n- `propX` can be a standard field (e.g., `status`, `title`, `city`, `type`, `accountId`, etc.) or a custom property key name.\n- Use the `property key` from your company settings.\n- Filter by parent account with `accountId`. `clientId` is accepted as a deprecated alias for backwards compatibility.\n\nOperators (`opX`):\n- Equality: `eq`, `neq`\n- Numeric/datetime comparisons: `gt`, `gte`, `lt`, `lte`\n- Text/string matching: `contains` (case-insensitive)\n- Lists: `in`, `nin` (CSV list in `valueX`)\n- Ranges: `between` (use `valueX` as `from,to`)\n- Existence: `exists` is not supported for custom properties due to performance limitations. Use `eq` or `neq` instead.\n\nValue formats:\n- `in`/`nin`: lists can be provided as:\n  - Plain CSV: `value1=OPEN,ACTIVE`\n  - Quoted CSV (to include commas inside a value): `value1=\"Last, First\",Other`\n  - JSON array: `value1=[\"Last, First\",\"Other\"]`\n- `between`: `from,to` (e.g., `value1=2025-01-01,2025-12-31` or `value1=10,20`)\n- Dates should be ISO 8601 strings; numeric-like values on custom properties are matched against both numeric and text representations.\n\nExamples:\n- status equals ACTIVE: `?prop1=status&op1=eq&value1=ACTIVE`\n- custom id equals 1234: `?prop1=legacy_tool_project_id&op1=eq&value1=1234`\n- title contains \"Solar\": `?prop1=name&op1=contains&value1=Solar`\n- status IN (ACTIVE, ON_HOLD) AND city = Austin: `?prop1=status&op1=in&value1=ACTIVE,ON_HOLD&logic=and&prop2=city&op2=eq&value2=Austin`\n\n**Note:** The `/requests` path is an alias for `/opportunities` and will continue to work until users are individually notified and migrated.\n\n**Note:** If an opportunity has no `title` (empty or whitespace-only), the response returns the parent account's `title` instead. The stored value is not modified — this is a read-time fallback only.\n"
      tags:
      - opportunities
      parameters:
      - name: prop1
        in: query
        description: First field to filter (standard or custom keyName)
        required: true
        schema:
          type: string
      - name: op1
        in: query
        description: Operator for prop1
        required: true
        schema:
          $ref: '#/components/schemas/OpportunitiesSearchGetParametersOp1'
      - name: value1
        in: query
        description: Value for prop1 (comma-separated values for in/nin or between)
        required: true
        schema:
          type: string
      - name: logic
        in: query
        description: Logical combination when both prop1 and prop2 are provided
        required: false
        schema:
          $ref: '#/components/schemas/OpportunitiesSearchGetParametersLogic'
      - name: prop2
        in: query
        description: Optional second field to filter
        required: false
        schema:
          type: string
      - name: op2
        in: query
        description: Operator for prop2
        required: false
        schema:
          $ref: '#/components/schemas/OpportunitiesSearchGetParametersOp2'
      - name: value2
        in: query
        description: Value for prop2 (comma-separated values for in/nin or between)
        required: false
        schema:
          type: string
      - name: page_size
        in: query
        description: Number of items per page (max 100)
        required: false
        schema:
          type: integer
          default: 20
      - name: page
        in: query
        description: Page number (1-based)
        required: false
        schema:
          type: integer
          default: 1
      - name: order_by
        in: query
        description: Sort order for results
        required: false
        schema:
          $ref: '#/components/schemas/OpportunitiesSearchGetParametersOrderBy'
      - name: include_archived
        in: query
        description: Whether to include archived (inactive) records in the response. By default only active records are returned.
        required: false
        schema:
          type: boolean
          default: false
      - name: x-api-key
        in: header
        required: true
        schema:
          type: string
      responses:
        '200':
          description: List of opportunities matching filters
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Opportunity'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchOpportunitiesRequestBadRequestError'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchOpportunitiesRequestUnauthorizedError'
  /opportunities/{opportunityId}:
    get:
      operationId: get-opportunity
      summary: Get Opportunity
      description: 'Retrieve a specific opportunity by ID


        **Note:** The `/requests` path is an alias for `/opportunities` and will continue to work until users are individually notified and migrated.


        **Note:** If the opportunity has no `title` (empty or whitespace-only), the response returns the parent account''s `title` instead. The stored value is not modified — this is a read-time fallback only.

        '
      tags:
      - opportunities
      parameters:
      - name: opportunityId
        in: path
        description: Opportunity identifier
        required: true
        schema:
          type: integer
      - name: include_virtual_properties
        in: query
        description: Whether to include virtual properties in the response. Defaults to false unless explicitly set to true.
        required: false
        schema:
          type: boolean
          default: false
      - name: include_archived
        in: query
        description: Whether to include archived (inactive) records in the response. By default only active records are returned.
        required: false
        schema:
          type: boolean
          default: false
      - name: x-api-key
        in: header
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Opportunity details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Opportunity'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetOpportunityRequestUnauthorizedError'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetOpportunityRequestNotFoundError'
    patch:
      operationId: update-opportunity
      summary: Update Opportunity
      description: 'Update an existing opportunity. Supports partial updates.


        Updatable fields:

        - Standard fields (value, size, status, confidence, etc.)

        - Workflow fields (workflowId, phaseId)

        - Contact information (primaryEmail, primaryPhone)

        - Contacts (contact IDs only, no name, email, phone, etc.)

        - Custom fields (through custom object)


        Note: Updates are atomic - either all fields update or none do.


        > **Changing account association is not supported via update.** To move an opportunity to a different account, delete and re-create it under the new account.


        **Note:** The `/requests` path is an alias for `/opportunities` and will continue to work until users are individually notified and migrated.

        '
      tags:
      - opportunities
      parameters:
      - name: opportunityId
        in: path
        description: Opportunity identifier
        required: true
        schema:
          type: integer
      - name: allow_new_options
        in: query
        description: Whether to allow creation of new dropdown options during record creation
        required: false
        schema:
          type: boolean
          default: false
      - name: x-api-key
        in: header
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Opportunity updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Opportunity'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateOpportunityRequestBadRequestError'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateOpportunityRequestUnauthorizedError'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateOpportunityRequestNotFoundError'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpportunityUpsert'
    delete:
      operationId: delete-opportunity
      summary: Delete Opportunity
      description: 'Delete a specific opportunity by ID


        **Note:** The `/requests` path is an alias for `/opportunities` and will continue to work until users are individually notified and migrated.

        '
      tags:
      - opportunities
      parameters:
      - name: opportunityId
        in: path
        description: Opportunity identifier
        required: true
        schema:
          type: integer
      - name: x-api-key
        in: header
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Successful response
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteOpportunityRequestUnauthorizedError'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteOpportunityRequestNotFoundError'
components:
  schemas:
    OpportunityProjectManager:
      type: object
      properties:
        id:
          type: integer
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
        phone:
          type:
          - string
          - 'null'
        avatarUrl:
          type:
          - string
          - 'null'
      title: OpportunityProjectManager
    OpportunitiesPostResponsesContentApplicationJsonSchemaCode:
      type: string
      enum:
      - UNAUTHORIZED
      title: OpportunitiesPostResponsesContentApplicationJsonSchemaCode
    GetOpportunityRequestUnauthorizedError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdGetResponsesContentApplicationJsonSchemaCode'
      title: GetOpportunityRequestUnauthorizedError
    OpportunitiesSearchGetParametersOp1:
      type: string
      enum:
      - eq
      - neq
      - gt
      - gte
      - lt
      - lte
      - contains
      - in
      - nin
      - between
      title: OpportunitiesSearchGetParametersOp1
    DeleteOpportunityRequestUnauthorizedError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdDeleteResponsesContentApplicationJsonSchemaCode'
      title: DeleteOpportunityRequestUnauthorizedError
    OpportunitiesOpportunityIdGetResponsesContentApplicationJsonSchemaCode:
      type: string
      enum:
      - NOT_FOUND
      title: OpportunitiesOpportunityIdGetResponsesContentApplicationJsonSchemaCode
    OpportunitiesGetResponsesContentApplicationJsonSchemaCode:
      type: string
      enum:
      - UNAUTHORIZED
      title: OpportunitiesGetResponsesContentApplicationJsonSchemaCode
    ProjectStatus:
      type: string
      enum:
      - ACTIVE
      - ON_HOLD
      - CANCELLED
      - COMPLETED
      description: 'Status of the project:

        * `ACTIVE` - Project is active and in progress

        * `ON_HOLD` - Project is temporarily paused

        * `CANCELLED` - Project has been cancelled

        * `COMPLETED` - Project has been completed

        '
      title: ProjectStatus
    CreateOpportunityRequestUnauthorizedError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesPostResponsesContentApplicationJsonSchemaCode'
      title: CreateOpportunityRequestUnauthorizedError
    DeleteOpportunityRequestNotFoundError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdDeleteResponsesContentApplicationJsonSchemaCode'
      title: DeleteOpportunityRequestNotFoundError
    OpportunitiesSearchGetParametersOrderBy:
      type: string
      enum:
      - asc
      - desc
      default: asc
      title: OpportunitiesSearchGetParametersOrderBy
    OpportunitiesPostParametersMatchBy:
      type: string
      enum:
      - title
      - primaryEmail
      - primaryPhone
      - address
      default: title
      title: OpportunitiesPostParametersMatchBy
    UpdateOpportunityRequestUnauthorizedError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdPatchResponsesContentApplicationJsonSchemaCode'
      title: UpdateOpportunityRequestUnauthorizedError
    Opportunity:
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp
        title:
          type: string
          description: Record title/name
        description:
          type:
          - string
          - 'null'
          description: Record description
        address:
          type: array
          items:
            type: string
          description: An array containing a single string, which represents the full opportunity location/address.
        isActive:
          type: boolean
          description: Whether the record is active
        primaryEmail:
          type:
          - string
          - 'null'
          format: email
          description: Primary contact email
        primaryPhone:
          type:
          - string
          - 'null'
          description: Primary contact phone
        number:
          type: integer
          description: Sequential opportunity number
        createdBy:
          oneOf:
          - $ref: '#/components/schemas/UserSummary'
          - type: 'null'
          description: User who created the record. Null when the record was created by a non-user actor (e.g. automation or a contact).
        updatedBy:
          oneOf:
          - $ref: '#/components/schemas/UserSummary'
          - type: 'null'
          description: User who last edited a field on the record, derived from the changelog. Null when the record has never been edited or the last edit was made by a non-user actor.
        custom:
          type: object
          additionalProperties:
            description: Any type
          description: Custom fields
        trades:
          type: array
          items:
            type: string
          description: Array of trade types
        value:
          type:
          - number
          - 'null'
          format: double
          description: Deal value
        size:
          type:
          - number
          - 'null'
          format: double
          description: Deal size
        confidence:
          type:
          - number
          - 'null'
          format: double
          description: Deal confidence score (0-100)
        workflowId:
          type:
          - integer
          - 'null'
          description: Associated workflow ID
        accountId:
          type:
          - integer
          - 'null'
          description: Associated account ID
        geoLocation:
          type: array
          items:
            type: string
          description: Latitude/Longitude in "lat,lon" format
        imageUrl:
          type:
          - string
          - 'null'
          description: Image URL for the opportunity
        streetViewUrl:
          type:
          - string
          - 'null'
          description: Street view image URL
        city:
          type: string
        zipcode:
          type: string
        state:
          type: string
        street:
          type: string
        phase:
          oneOf:
          - $ref: '#/components/schemas/OpportunityPhase'
          - type: 'null'
        phaseInstances:
          type: array
          items:
            $ref: '#/components/schemas/PhaseInstance'
          description: Ordered list of phase instances for the opportunity
        owner:
          oneOf:
          - $ref: '#/components/schemas/OpportunityOwner'
          - type: 'null'
        salesRep:
          oneOf:
          - $ref: '#/components/schemas/OpportunitySalesRep'
          - type: 'null'
        projectManager:
          oneOf:
          - $ref: '#/components/schemas/OpportunityProjectManager'
          - type: 'null'
        jurisdiction:
          oneOf:
          - $ref: '#/components/schemas/OpportunityJurisdiction'
          - type: 'null'
        lastActivity:
          type:
          - string
          - 'null'
          format: date-time
        phaseId:
          type:
          - integer
          - 'null'
        workflowName:
          type: string
          description: 'Name of the associated workflow.

            '
      title: Opportunity
    OpportunitiesOpportunityIdDeleteResponsesContentApplicationJsonSchemaCode:
      type: string
      enum:
      - NOT_FOUND
      title: OpportunitiesOpportunityIdDeleteResponsesContentApplicationJsonSchemaCode
    OpportunityPhase:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        type:
          type: string
      title: OpportunityPhase
    UpdateOpportunityRequestNotFoundError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdPatchResponsesContentApplicationJsonSchemaCode'
      title: UpdateOpportunityRequestNotFoundError
    ListOpportunitiesRequestUnauthorizedError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesGetResponsesContentApplicationJsonSchemaCode'
      title: ListOpportunitiesRequestUnauthorizedError
    PhaseTemplateSummary:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        type:
          type: string
        redSla:
          type:
          - integer
          - 'null'
        yellowSla:
          type:
          - integer
          - 'null'
      title: PhaseTemplateSummary
    GetOpportunityRequestNotFoundError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdGetResponsesContentApplicationJsonSchemaCode'
      title: GetOpportunityRequestNotFoundError
    OpportunitiesSearchGetParametersOp2:
      type: string
      enum:
      - eq
      - neq
      - gt
      - gte
      - lt
      - lte
      - contains
      - in
      - nin
      - between
      title: OpportunitiesSearchGetParametersOp2
    OpportunitiesSearchGetParametersLogic:
      type: string
      enum:
      - and
      - or
      default: and
      title: OpportunitiesSearchGetParametersLogic
    OpportunitiesPostParametersMatchFoundStrategy:
      type: string
      enum:
      - skip
      - replace
      - enrich
      default: skip
      description: '- skip: Return existing record without changes

        - replace: Replace existing record with new data

        - enrich: Update only empty fields in existing record

        '
      title: OpportunitiesPostParametersMatchFoundStrategy
    OpportunitiesGetParametersOrderBy:
      type: string
      enum:
      - asc
      - desc
      default: asc
      title: OpportunitiesGetParametersOrderBy
    PhaseInstanceStatus:
      type: string
      enum:
      - NOT_STARTED
      - IN_PROGRESS
      - COMPLETED
      title: PhaseInstanceStatus
    PhaseInstance:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        status:
          $ref: '#/components/schemas/PhaseInstanceStatus'
        position:
          type: integer
        type:
          type: string
        phaseTemplateId:
          type: integer
        phaseTemplate:
          $ref: '#/components/schemas/PhaseTemplateSummary'
      title: PhaseInstance
    UpdateOpportunityRequestBadRequestError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesOpportunityIdPatchResponsesContentApplicationJsonSchemaCode'
        field:
          type: string
          description: Field that caused the validation error (if applicable)
      title: UpdateOpportunityRequestBadRequestError
    OpportunityJurisdiction:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        uuid:
          type: string
      title: OpportunityJurisdiction
    SearchOpportunitiesRequestUnauthorizedError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesSearchGetResponsesContentApplicationJsonSchemaCode'
      title: SearchOpportunitiesRequestUnauthorizedError
    CreateOpportunityRequestBadRequestError:
      type: object
      properties:
        message:
          type: string
        code:
          $ref: '#/components/schemas/OpportunitiesPostResponsesContentApplicationJsonSchemaCode'
        field:
          type: string
          description: Field that caused the validation error (if applicable)
      title: CreateOpportunityRequestBadRequestError
    OpportunityOwner:
      type: object
      properties:
        id:
          type: integer
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
        phone:
          type:
          - string
          - 'null'
        avatarUrl:
          type:
          - string
          - 'null'
      title: OpportunityOwner
    OpportunitiesSearchGetResponsesContentApplicationJsonSchemaCode:
      type: string
      enum:
      - UNAUTHORIZED
      title: OpportunitiesSearchGetResponsesContentApplicationJsonSchemaCode
    UserSummary:
      type: object
      properties:
        id:
          type: integer
        firstName:
          type:
          - string
          - 'null'
        lastName:
          type:
          - string
          - 'null'
        email:
          type:
          - string
          - 'null'
          format: email
        avatarUrl:
          type:
          - string
          - 'null'
      description: Minimal user representation used in responses
      title: UserSummary
    OpportunityUpsert:
      type: object
      properties:
        title:
          type: string
          description: Opportunity title/name
        description:
          type:
          - string
          - 'null'
          description: Opportunity description
        address:
          type: array
          items:
            type: string
          description: An array containing a single string, which represents the full opportunity location/address.
        trades:
          type: array
          items:
            type: string
          description: Array of trade types
        workflowId:
          type:
          - integer
          - 'null'
          description: ID of associated workflow
        value:
          type: number
          format: double
          description: Project value
        size:
          type: number
          format: double
          description: Project size
        status:
          $ref: '#/components/schemas/ProjectStatus'
        primaryEmail:
          type: string
          format: email
          description: Primary contact email
        primaryPhone:
          type: string
          description: Primary contact phone
        contacts:
          type: array
          items:
            type: integer
          description: Contact IDs only (no name, email, phone, etc.). The contacts must be created first via POST /contacts.
        custom:
          type: object
          additionalProperties:
            description: Any type
          description: Custom fields
      required:
      - title
      - address
      - trades
      description: 'Used for both create and update. `accountId` is only accepted on create — passing it on a PATCH update will return a 400 error.

        '
      title: OpportunityUpsert


# --- truncated at 32 KB (33 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/coperniq/refs/heads/main/openapi/coperniq-opportunities-api-openapi.yml