openapi: 3.1.0
info:
  title: Concrete API
  description: >
    The Concrete API provides programmatic access to view and create select data in Concrete. 
    The API is primarily intended to facilitate data pipeline automations for portfolio management.
  version: 1.0.0
  license:
    name: MIT
  contact:
    name: Concrete Support
    email: support@concretehq.com
    url: https://concretehq.com

servers:
  - url: https://app.concretehq.com/api/v1
    description: Production server

security:
  - bearerAuth: []

paths:
  /me:
    get:
      summary: Get current member information
      description: Get information about the currently authenticated member
      operationId: getCurrentMember
      tags:
        - Authentication
      responses:
        '200':
          description: Current member information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /companies:
    get:
      summary: List companies
      description: List or search companies with pagination support
      operationId: listCompanies
      tags:
        - Companies
      parameters:
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
        - name: term
          in: query
          description: Search term to filter companies
          required: false
          schema:
            type: string
      responses:
        '200':
          description: List of companies
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompaniesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create company
      description: Create a new company
      operationId: createCompany
      tags:
        - Companies
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyInput'
      responses:
        '201':
          description: Company created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /companies/{companyPublicId}:
    get:
      summary: Get company
      description: Get a specific company by its public ID
      operationId: getCompany
      tags:
        - Companies
      parameters:
        - name: companyPublicId
          in: path
          description: Company public ID
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Company details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Company'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

    put:
      summary: Update company
      description: Update an existing company
      operationId: updateCompany
      tags:
        - Companies
      parameters:
        - name: companyPublicId
          in: path
          description: Company public ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyUpdateInput'
      responses:
        '200':
          description: Company updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Company'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

    delete:
      summary: Delete company
      description: Delete a company by its public ID
      operationId: deleteCompany
      tags:
        - Companies
      parameters:
        - name: companyPublicId
          in: path
          description: Company public ID
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Company deleted successfully
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /companies/{companyPublicId}/touchpoints:
    get:
      summary: List company touchpoints
      description: |
        List a company's touchpoints, newest first, with pagination support.
        A touchpoint groups the activity around one company event, such as an
        investor update, a board meeting, or an announcement, and carries the
        insights extracted from that activity with citations back to the
        source material.
      operationId: listCompanyTouchpoints
      tags:
        - Companies
      parameters:
        - name: companyPublicId
          in: path
          description: Company public ID
          required: true
          schema:
            type: string
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
        - name: topic
          in: query
          description: |
            Only touchpoints with these topics. Repeat the parameter to pass
            multiple (e.g. `?topic=board-meeting&topic=company-announcement`).
          required: false
          style: form
          explode: true
          schema:
            type: array
            items:
              type: string
              enum: [periodic-company-update, company-announcement, board-meeting, adhoc-company-update, legal-communication, other]
        - name: category
          in: query
          description: |
            Only touchpoints with insights in these categories. Repeat the
            parameter to pass multiple. Also narrows the `insights` returned
            on each touchpoint to the matching categories.
          required: false
          style: form
          explode: true
          schema:
            type: array
            items:
              type: string
              enum: [funding, growth, product, go_to_market, team, operations, financial_health, marketing, governance, exit, other]
        - name: sort
          in: query
          description: |
            Sort order. May be `occurredAt`, when the underlying activity
            happened, or `updatedAt`, when the touchpoint last changed
            (content edits, insights re-derived as new activity arrived).
            Both newest first. Page tokens are only valid for the sort they
            were issued under.
          required: false
          schema:
            type: string
            enum: [occurredAt, updatedAt]
            default: occurredAt
      responses:
        '200':
          description: List of touchpoints
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TouchpointsResponse'
              examples:
                investor-update:
                  summary: Periodic investor update
                  value:
                    items:
                      - publicId: "tch123abc456"
                        headline: "Acme May 2026 investor update"
                        topics: ["periodic-company-update"]
                        occurredAt: "2026-06-01T12:00:00Z"
                        updatedAt: "2026-06-02T08:14:09Z"
                        insights:
                          - publicId: "ins789ghi012"
                            category: "growth"
                            content: "ARR reached $24M in May, up 18% since the February update."
                            occurredAt: "2026-06-01T12:00:00Z"
                            citations:
                              - type: "file"
                                publicId: "fil789ghi012"
                                name: "Acme May 2026 Investor Update.pdf"
                                extension: "pdf"
                                permalink: "https://app.concretehq.com/v/fil789ghi012"
                board-meeting:
                  summary: Board meeting with a filtered category
                  value:
                    items:
                      - publicId: "tch234bcd567"
                        headline: "Beta Q2 board meeting"
                        topics: ["board-meeting"]
                        occurredAt: "2026-07-15T17:00:00Z"
                        updatedAt: "2026-07-16T09:30:00Z"
                        insights:
                          - publicId: "ins345cde678"
                            category: "funding"
                            content: "The board approved raising a $5M bridge round targeting a September close."
                            occurredAt: "2026-07-15T17:00:00Z"
                            citations:
                              - type: "meeting"
                                publicId: "mtg456def789"
                                title: "Beta Q2 board meeting"
                                permalink: "https://app.concretehq.com/v/mtg456def789"
                    nextPageToken: "next_page_token_here"
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /funds:
    get:
      summary: List funds
      description: List all funds with pagination support
      operationId: listFunds
      tags:
        - Funds
      parameters:
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
      responses:
        '200':
          description: List of funds
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FundsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /funds/{fundPublicId}/companies:
    get:
      summary: Get fund companies
      description: Get all companies associated with a specific fund
      operationId: getFundCompanies
      tags:
        - Funds
      parameters:
        - name: fundPublicId
          in: path
          description: Fund public ID
          required: true
          schema:
            type: string
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
      responses:
        '200':
          description: List of companies in the fund
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompaniesResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Add companies to fund
      description: Add one or more companies to a fund
      operationId: addCompaniesToFund
      tags:
        - Funds
      parameters:
        - name: fundPublicId
          in: path
          description: Fund public ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyIdsInput'
      responses:
        '204':
          description: Companies added to fund successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

    delete:
      summary: Remove companies from fund
      description: Remove one or more companies from a fund
      operationId: removeCompaniesFromFund
      tags:
        - Funds
      parameters:
        - name: fundPublicId
          in: path
          description: Fund public ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyIdsInput'
      responses:
        '204':
          description: Companies removed from fund successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /funds/{fundPublicId}/companies/{organizationPublicId}/status:
    patch:
      summary: Update portfolio company status
      description: |
        Update the status of a company within a fund. Use this to mark companies as
        active, exited, or dissolved within a specific fund's portfolio.
      operationId: updatePortfolioCompanyStatus
      tags:
        - Funds
      parameters:
        - name: fundPublicId
          in: path
          description: Fund public ID
          required: true
          schema:
            type: string
        - name: organizationPublicId
          in: path
          description: Company public ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PortfolioCompanyStatusInput'
      responses:
        '204':
          description: Status updated successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /meetings:
    post:
      summary: Create meeting
      description: |
        Create and manage meetings imported from external sources like meeting recorders and note-taking apps.

        This endpoint is designed to integrate with external meeting platforms and note-taking applications,
        allowing you to import meeting data into Concrete for portfolio intelligence analysis.

        **Key Features:**
        - Import from various providers (Fireflies, Granola, Notion, etc.)
        - Update existing meetings using meetingNoteId
        - Flexible content requirements (need at least one of: title, notes, or transcript)
        - Automatic workspace member matching via creatorEmail
        - Support for both meeting notes and calendar event data
      operationId: createMeeting
      tags:
        - Meetings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MeetingInput'
      responses:
        '201':
          description: Meeting created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeetingCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /notes:
    get:
      summary: List notes
      description: |
        List notes attached to a company or a person. Exactly one of `companyPublicId`
        or `personPublicId` must be provided.
      operationId: listNotes
      tags:
        - Notes
      parameters:
        - name: companyPublicId
          in: query
          description: Public ID of the company whose notes to list
          required: false
          schema:
            type: string
        - name: personPublicId
          in: query
          description: Public ID of the person whose notes to list
          required: false
          schema:
            type: string
      responses:
        '200':
          description: List of notes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotesResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create note
      description: |
        Create a new note attached to a company or a person. Exactly one of
        `companyPublicId` or `personPublicId` must be provided.
      operationId: createNote
      tags:
        - Notes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteCreateInput'
      responses:
        '201':
          description: Note created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NoteCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /notes/{notePublicId}:
    get:
      summary: Get note
      description: Get a specific note by its public ID, including creator and parent company.
      operationId: getNote
      tags:
        - Notes
      parameters:
        - name: notePublicId
          in: path
          description: Note public ID
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Note details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NoteWithCreator'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

    put:
      summary: Update note
      description: |
        Update an existing note's content or `occurredAt` timestamp. The note's
        parent company or person cannot be changed.
      operationId: updateNote
      tags:
        - Notes
      parameters:
        - name: notePublicId
          in: path
          description: Note public ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteUpdateInput'
      responses:
        '200':
          description: Note updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

    delete:
      summary: Delete note
      description: Delete a note by its public ID.
      operationId: deleteNote
      tags:
        - Notes
      parameters:
        - name: notePublicId
          in: path
          description: Note public ID
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Note deleted successfully
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /connections:
    get:
      summary: List connections
      description: |
        List the workspace's data connections with cursor-based pagination.
      operationId: listConnections
      tags:
        - Connections
      parameters:
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
      responses:
        '200':
          description: List of connections
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /trackers:
    get:
      summary: List trackers
      description: |
        List the trackers (tracked metrics and topics) visible to the authenticated member,
        with pagination support.
      operationId: listTrackers
      tags:
        - Trackers
      parameters:
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
        - name: companyPublicId
          in: query
          description: Only trackers that apply to this company
          required: false
          schema:
            type: string
      responses:
        '200':
          description: List of trackers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackersResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /trackers/{trackerPublicId}:
    get:
      summary: Get tracker
      description: Get a specific tracker by its public ID
      operationId: getTracker
      tags:
        - Trackers
      parameters:
        - name: trackerPublicId
          in: path
          description: Tracker public ID
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Tracker details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tracker'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /trackers/{trackerPublicId}/values:
    get:
      summary: List tracker values
      description: |
        List the value history for one tracker across all portfolio companies,
        with pagination support.

        **Statuses**: by default both `suggested` (extracted, awaiting review) and
        `confirmed` values are returned; `rejected` values are excluded unless
        explicitly requested via the `status` parameter.
      operationId: listTrackerValues
      tags:
        - Trackers
      parameters:
        - name: trackerPublicId
          in: path
          description: Tracker public ID
          required: true
          schema:
            type: string
        - name: pageToken
          in: query
          description: Token for pagination to get next page
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: "Number of results per page (default: 100, max: 100)"
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
        - name: companyPublicId
          in: query
          description: Only values for this company
          required: false
          schema:
            type: string
        - name: status
          in: query
          description: |
            Only values with these review statuses. Repeat the parameter to pass
            multiple (e.g. `?status=confirmed&status=rejected`).
          required: false
          style: form
          explode: true
          schema:
            type: array
            items:
              type: string
              enum: [suggested, confirmed, rejected]
            default: [suggested, confirmed]
        - name: effectiveAfter
          in: query
          description: "Only values effective on or after this date (inclusive, YYYY-MM-DD)"
          required: false
          schema:
            type: string
            format: date
        - name: effectiveBefore
          in: query
          description: "Only values effective on or before this date (inclusive, YYYY-MM-DD)"
          required: false
          schema:
            type: string
            format: date
        - name: updatedAfter
          in: query
          description: |
            Only values changed at or after this instant (inclusive, ISO 8601).
            "Changed" covers content edits and review-status changes.
          required: false
          schema:
            type: string
            format: date-time
        - name: sort
          in: query
          description: Sort order. May be `effectiveAt`, the date the value is valid for, or `updatedAt`, the date the value was last changed. Both newest first.
          required: false
          schema:
            type: string
            enum: [effectiveAt, updatedAt]
            default: effectiveAt
      responses:
        '200':
          description: List of tracker values
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackerValuesResponse'
              examples:
                money-arr:
                  summary: "Money: ARR (run-rate with denomination)"
                  value:
                    items:
                      - publicId: "val123abc456"
                        companyPublicId: "cmp456def789"
                        kind: "money"
                        value: 24000000
                        unit: "USD"
                        period:
                          kind: "point_in_time"
                          denomination: "year"
                        note: "We closed May at $24M ARR, up 18% since our February update, with net revenue retention holding at 124%."
                        origin: "system"
                        status: "confirmed"
                        citations:
                          - type: "file"
                            publicId: "fil789ghi012"
                            name: "Acme May 2026 Investor Update.pdf"
                            extension: "pdf"
                            permalink: "https://app.concretehq.com/v/fil789ghi012"
                        effectiveAt: "2026-05-31"
                        reportedAt: "2026-06-01T12:00:00Z"
                        createdAt: "2026-06-02T08:14:09Z"
                        updatedAt: "2026-06-02T08:14:09Z"
                        statusUpdatedAt: "2026-06-03T16:40:00Z"
                money-quarterly-revenue:
                  summary: "Money: quarterly revenue (period total with cadence)"
                  value:
                    items:
                      - publicId: "val234bcd567"
                        companyPublicId: "cmp456def789"
                        kind: "money"
                        value: 5200000
                        unit: "USD"
                        period:
                          kind: "period_total"
                          cadence: { unit: "quarter", length: 1 }
                          rawText: "Q1 2026"
                        note: "Q1 revenue came in at $5.2M, about 4% ahead of plan, driven by two enterprise renewals landing early."
                        origin: "system"
                        status: "confirmed"
                        citations:
                          - type: "email"
                            publicId: "eml345cde678"
                            subject: "Q1 financials"
                            permalink: "https://app.concretehq.com/v/eml345cde678"
                        effectiveAt: "2026-03-31"
                        reportedAt: "2026-04-12T09:30:00Z"
                        createdAt: "2026-04-12T10:01:22Z"
                        updatedAt: "2026-04-12T10:01:22Z"
                        statusUpdatedAt: "2026-04-14T08:00:00Z"
                number-headcount:
                  summary: "Number: headcount (pure snapshot, no period qualifiers)"
                  value:
                    items:
                      - publicId: "val345cde678"
                        companyPublicId: "cmp456def789"
                        kind: "number"
                        value: 143
                        unit: "employees"
                        period:
                          kind: "point_in_time"
                        note: null
                        origin: "member"
                        status: "confirmed"
                        citations: []
                        effectiveAt: "2026-06-01"
                        reportedAt: "2026-06-01T17:45:00Z"
                        createdAt: "2026-06-01T17:45:00Z"
                        updatedAt: "2026-06-01T17:45:00Z"
                        statusUpdatedAt: "2026-06-01T17:45:00Z"
                percent-gross-margin:
                  summary: "Percent: gross margin (snapshot with underlying cadence)"
                  value:
                    items:
                      - publicId: "val456def789"
                        companyPublicId: "cmp456def789"
                        kind: "percent"
                        value: 62.5
                        unit: "%"
                        period:
                          kind: "point_in_time"
                          underlyingCadence: { unit: "quarter", length: 1 }
                        note: "Gross margin improved to 62.5% this quarter as we migrated the remaining workloads off dedicated GPUs."
                        origin: "system"
                        status: "suggested"
                        citations:
                          - type: "element"
                            publicId: "ele567efg890"
                            label: "P&L summary table"
                            index: 4
                            permalink: "https://app.concretehq.com/v/ele567efg890"
                        effectiveAt: "2026-03-31"
                        reportedAt: "2026-04-20T11:00:00Z"
                        createdAt: "2026-04-21T03:12:45Z"
                        updatedAt: "2026-04-21T03:12:45Z"
                        statusUpdatedAt: null
                boolean-fundraising:
                  summary: "Boolean: actively fundraising (no period semantics)"
                  value:
                    items:
                      - publicId: "val567efg890"
                        companyPublicId: "cmp456def789"
                        kind: "boolean"
                        value: true
                        unit: null
                        note: "We're officially kicking off our Series B this month and hope to wrap up by end of summer."
                        origin: "system"
                        status: "suggested"
                        citations:
                          - type: "email"
                            publicId: "eml678fgh901"
                            subject: "Kicking off our Series B"
                            permalink: "https://app.concretehq.com/v/eml678fgh901"
                        effectiveAt: "2026-05-20"
                        reportedAt: "2026-05-20T15:30:00Z"
                        createdAt: "2026-05-21T02:45:10Z"
                        updatedAt: "2026-05-21T02:45:10Z"
                        statusUpdatedAt: null
                text-investor-requests:
                  summary: "Text: investor requests (no period semantics)"
                  value:
                    items:
                      - publicId: "val678fgh901"
                        companyPublicId: "cmp456def789"
                        kind: "text"
                        value: "Intros to enterprise design partners; recommendations for a fractional CFO"
                        unit: null
                        note: "Two asks this month: we'd love intros to enterprise design partners in logistics, and if you know a great fractional CFO, send them our way."
                        origin: "system"
                        status: "confirmed"
                        citations:
                          - type: "meeting"
                            publicId: "mtg789ghi012"
                            title: "Acme monthly check-in"
                            permalink: "https://app.concretehq.com/v/mtg789ghi012"
                        effectiveAt: "2026-05-15"
                        reportedAt: "2026-05-15T16:00:00Z"
                        createdAt: "2026-05-16T01:20:33Z"
                        updatedAt: "2026-05-16T01:20:33Z"
                        statusUpdatedAt: "2026-05-18T09:15:00Z"
                score-financial-health:
                  summary: "Score: financial health (note carries the reasoning)"
                  value:
                    items:
                      - publicId: "val789ghi012"
                        companyPublicId: "cmp456def789"
                        kind: "score"
                        value: 72
                        unit: null
                        note: "Strong revenue growth (3x YoY) and improving margins, offset by elevated burn and less than 18 months of runway."
                        summary: "Revenue 3x; burn rate limits ceiling"
                        origin: "system"
                        status: "suggested"
                        citations:
                          - type: "file"
                            publicId: "fil890hij123"
                            name: "Acme Q1 2026 Board Deck.pdf"
                            extension: "pdf"
                            permalink: "https://app.concretehq.com/v/fil890hij123"
                        effectiveAt: "2026-03-31"
                        reportedAt: "2026-04-18T14:00:00Z"
                        createdAt: "2026-04-19T05:42:18Z"
                        updatedAt: "2026-04-19T05:42:18Z"
                        statusUpdatedAt: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key authentication using Bearer token format

  schemas:
    Member:
      type: object
      required:
        - publicId
        - role
        - user
      properties:
        publicId:
          type: string
          description: Member public ID
          example: "abc123def456"
        role:
          type: string
          description: Member role in the workspace
          example: "admin"
        user:
          $ref: '#/components/schemas/User'

    User:
      type: object
      required:
        - publicId
        - email
        - firstName
        - lastName
      properties:
        publicId:
          type: string
          description: User public ID
          example: "654fed321cba"
        email:
          type: string
          format: email
          description: User email address
          example: "john@example.com"
        firstName:
          type: string
          description: User first name
          example: "John"
        lastName:
          type: string
          description: User last name
          example: "Doe"

    Company:
      type: object
      description: |
        Portfolio company object containing all company information and metadata.
        Companies are the core entities that Concrete tracks for portfolio intelligence.
      required:
        - publicId
        - name
      properties:
        publicId:
          type: string
          description: |
            Unique nanoid for the company.
          example: "abc123def456"
        name:
          type: string
          description: |
            Name of the company used for display purposes.
          example: "Acme"
          minLength: 1
          maxLength: 255
        primaryDomain:
          type: string
          description: |
            Primary website domain of the company. Used as the main domain for email identification
            and company matching. Should be the most commonly used domain.
          example: "acmecorp.com"
          pattern: "^[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
        domains:
          type: array
          items:
            type: string
            pattern: "^[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
          description: |
            Array of all company domains including historical and alternative domains.
            Concrete also inspects emails from these domains.
          example: ["acmecorp.com", "acme.io", "oldcompanyname.com"]
        headline:
          type: string
          description: |
            Brief description of what the company does. Periodically auto-generated using company context.
          maxLength: 256
          example: "Revolutionary AI software platform for enterprise automation"
        alternativeNames:
          type: array
          items:
            type: string
            maxLength: 100
          description: |
            Array of alternative company names including abbreviations, former names,
            and commonly used variations.
          example: ["Acme Corp", "OldName Co"]
        linkedinSlug:
          type: string
          description: |
            LinkedIn company page slug (the part after /company/ in LinkedIn URL).
            Used for social media integration and additional company context.
          example: "acme-corporation"
          pattern: "^[a-zA-Z0-9-]+$"
        twitterSlug:
          type: string
          description: |
            Twitter/X handle without the @ symbol.
          example: "acmecorp"
          pattern: "^[a-zA-Z0-9_]+$"
        crunchbaseSlug:
          type: string
          description: |
            Crunchbase company profile slug (the part after /organization/ in Crunchbase URL).
          example: "acme-corporation"
          pattern: "^[a-zA-Z0-9-]+$"

    CompanyInput:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Company name
          example: "New Company Name"
        primaryDomain:
          type: string
          description: Primary domain of the company
          example: "newcompany.com"
        domains:
          type: array
          items:
            type: string
          description: Array of company domains
          example: ["newcompany.com", "newco.io"]
        alternativeNames:
          type: array
          items:
            type: string
          description: Array of alternative company names
          example: ["NewCo", "New Co"]
        headline:
          type: string
          description: Company description
          maxLength: 256
          example: "Company description"
        linkedinSlug:
          type: string
          description: LinkedIn profile slug
          example: "new-company"
        twitterSlug:
          type: string
          description: Twitter profile slug
          example: "newcompany"
        crunchbaseSlug:
          type: string
          description: Crunchbase profile slug
          example: "new-company"

    CompanyUpdateInput:
      type: object
      description: All fields are optional for updates
      properties:
        name:
          type: string
          description: Company name
          example: "Updated Company Name"
        primaryDomain:
          type: string
          description: Primary domain of the company
          example: "newdomain.com"
        domains:
          type: array
          items:
            type: string
          description: Array of company domains
          example: ["newdomain.com", "new.io"]
        alternativeNames:
          type: array
          items:
            type: string
          description: Array of alternative company names
          example: ["Updated Corp"]
        headline:
          type: string
          description: Company description
          maxLength: 256
          example: "Updated company description"
        linkedinSlug:
          type: string
          description: LinkedIn profile slug
          example: "updated-company"
        twitterSlug:
          type: string
          description: Twitter profile slug
          example: "updatedco"
        crunchbaseSlug:
          type: string
          description: Crunchbase profile slug
          example: "updated-company"

    CompanyCreateResponse:
      type: object
      required:
        - publicId
      properties:
        publicId:
          type: string
          description: Created company public ID
          example: "abc123def456"

    CompaniesResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Company'
        nextPageToken:
          type: string
          description: Token for fetching next page
          example: "next_page_token_here"

    Fund:
      type: object
      required:
        - publicId
        - name
      properties:
        publicId:
          type: string
          description: Fund public ID
          example: "abc123def456"
        name:
          type: string
          description: Fund name
          example: "Growth Fund I"

    FundsResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Fund'
        nextPageToken:
          type: string
          description: Token for fetching next page
          example: "next_page_token_here"

    CompanyIdsInput:
      type: object
      required:
        - companyPublicIds
      properties:
        companyPublicIds:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 500
          description: Array of company public IDs
          example: ["abc123def456", "654fed321cba"]

    MeetingInput:
      type: object
      description: |
        Meeting data for import from external sources. At least one of the following fields must be provided: 
        `meetingNoteTitle`, `calendarEventTitle`, `notes`, or `transcript`.
      anyOf:
        - required: [meetingNoteTitle]
        - required: [calendarEventTitle]
        - required: [notes]
        - required: [transcript]
      properties:
        provider:
          type: string
          description: |
            External source (e.g., "fireflies", "granola"). Used to facilitate link backs using the meetingNoteId.
            Common providers include meeting recording services and note-taking applications.
          example: "fireflies"
        meetingNoteId:
          type: string
          description: |
            External system's meeting note identifier. Updates existing meeting if matched.
            This allows you to update the same meeting multiple times from your external system.
          example: "external_note_123"
        meetingNoteTitle:
          type: string
          description: |
            Title from the external meeting note. This is one of the four fields that can satisfy 
            the minimum content requirement.
          example: "Q4 Board Meeting Notes"
        creatorName:
          type: string
          description: Name of meeting creator
          example: "John Doe"
        creatorEmail:
          type: string
          format: email
          description: |
            Email of meeting creator. Matches on workspace members to take ownership of the meeting 
            from the API key holder. If this email matches a workspace member, they will become the 
            owner of the imported meeting.
          example: "john@example.com"
        attendees:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                description: Optional name of the attendee
                example: "Alice Jones"
              email:
                type: string
                format: email
                description: Optional email address of the attendee
                example: "alice@acmecorp.com"
          description: |
            Array of attendee objects. Each object may contain an optional name and/or email.
            At least one of name or email should be provided for each attendee.
          example: [{"name": "Alice Jones", "email": "alice@acmecorp.com"}, {"email": "carmen@sandiego.com"}]
        calendarEventId:
          type: string
          description: Calendar system's event identifier for linking to original calendar event
          example: "cal_event_456"
        calendarEventTitle:
          type: string
          description: |
            Title from calendar event. This is one of the four fields that can satisfy 
            the minimum content requirement.
          example: "Q4 Board Meeting"
        calendarEventTime:
          type: string
          format: date-time
          description: |
            Meeting date/time (ISO 8601 format). Defaults to current time if not provided.
            Used for chronological organization of meetings.
          example: "2024-12-15T14:00:00Z"
        notes:
          type: string
          description: |
            Meeting notes content in markdown format. This is one of the four fields that can 
            satisfy the minimum content requirement. Supports rich formatting for detailed meeting records.
          example: "Meeting notes markdown content here..."
        transcript:
          type: string
          description: |
            Meeting transcript content in markdown format. This is one of the four fields that can 
            satisfy the minimum content requirement. Full transcript for comprehensive meeting analysis.
          example: "Full meeting transcript markdown content here..."

    MeetingCreateResponse:
      type: object
      required:
        - publicId
      properties:
        publicId:
          type: string
          description: Created meeting public ID
          example: "abc123def456"

    PortfolioCompanyStatusInput:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum: [active, exited, dissolved]
          description: New status for the company within the fund's portfolio
          example: "active"

    Note:
      type: object
      description: A note attached to a company or person.
      required:
        - publicId
        - content
        - occurredAt
        - topics
      properties:
        publicId:
          type: string
          description: Note public ID
          example: "abc123def456"
        content:
          type: string
          description: Note content in Markdown. Rendered as rich text in the Concrete app.
          example: "Met with the founder; discussed **Q4 hiring plan**."
        occurredAt:
          type: string
          format: date-time
          description: When the note's subject matter occurred (ISO 8601)
          example: "2026-04-15T14:00:00Z"
        topics:
          type: array
          nullable: true
          description: Topics categorizing the note's subject matter
          items:
            $ref: '#/components/schemas/NoteTopic'
          example: ["board-meeting"]

    NoteWithCreator:
      allOf:
        - $ref: '#/components/schemas/Note'
        - type: object
          required:
            - creator
          properties:
            creator:
              $ref: '#/components/schemas/Member'
            companyName:
              type: string
              description: Name of the company the note is attached to (if any)
              example: "Acme"
            companyPublicId:
              type: string
              description: Public ID of the company the note is attached to (if any)
              example: "abc123def456"

    NoteCreateInput:
      type: object
      description: |
        Input for creating a note. Exactly one of `companyPublicId` or `personPublicId`
        must be provided.
      required:
        - content
      properties:
        content:
          type: string
          minLength: 1
          description: Note content in Markdown. Rendered as rich text in the Concrete app.
          example: "Kickoff call notes…"
        occurredAt:
          type: string
          format: date-time
          description: When the note's subject matter occurred (ISO 8601). Defaults to now.
          example: "2026-04-15T14:00:00Z"
        topics:
          type: array
          description: |
            Topics categorizing the note. Optional, defaults to `["other"]` if omitted.
          items:
            $ref: '#/components/schemas/NoteTopic'
          default: ["other"]
          example: ["board-meeting"]
        companyPublicId:
          type: string
          description: Public ID of the company to attach the note to
          example: "abc123def456"
        personPublicId:
          type: string
          description: Public ID of the person to attach the note to
          example: "654fed321cba"

    NoteTopic:
      type: string
      description: Categorization for a note's subject matter.
      enum:
        - periodic-company-update
        - company-announcement
        - board-meeting
        - adhoc-company-update
        - legal-communication
        - other

    NoteUpdateInput:
      type: object
      description: All fields are optional; the note's parent company/person cannot be changed.
      properties:
        content:
          type: string
          minLength: 1
          description: Updated note content in Markdown. Rendered as rich text in the Concrete app.
          example: "Revised meeting notes…"
        occurredAt:
          type: string
          format: date-time
          description: Updated occurredAt timestamp (ISO 8601)
          example: "2026-04-16T09:00:00Z"

    NoteCreateResponse:
      type: object
      required:
        - publicId
      properties:
        publicId:
          type: string
          description: Created note public ID
          example: "abc123def456"

    NotesResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/NoteWithCreator'

    Connection:
      type: object
      description: A workspace data connection.
      required:
        - publicId
        - provider
        - createdAt
        - errorId
        - errorAt
        - syncedAt
        - disabledAt
        - label
        - followLinks
        - followDocsend
      properties:
        publicId:
          type: string
          description: Connection public ID
          example: "abc123def456"
        provider:
          type: string
          description: Provider identifier (e.g., "upload")
          example: "upload"
        createdAt:
          type: string
          format: date-time
          description: When the connection was created
        errorId:
          type: string
          nullable: true
          description: Identifier for the most recent error, if any
        errorAt:
          type: string
          format: date-time
          nullable: true
          description: When the most recent error occurred
        syncedAt:
          type: string
          format: date-time
          nullable: true
          description: When the connection was last synced
        disabledAt:
          type: string
          format: date-time
          nullable: true
          description: When the connection was disabled, if applicable
        label:
          type: string
          nullable: true
          description: Human-readable label for the connection
          example: "Q4 Email Export"
        followLinks:
          type: boolean
          description: Whether to crawl and ingest content behind links found in messages
        followDocsend:
          type: boolean
          description: Whether to crawl and ingest DocSend links found in messages

    ConnectionsResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Connection'
        nextPageToken:
          type: string
          description: Token for fetching next page
          example: "next_page_token_here"

    Tracker:
      type: object
      description: |
        A tracker defines a metric monitored across portfolio companies
        (e.g. ARR, headcount, cash balance). Concrete extracts tracker values
        from incoming emails, files, and meetings automatically; values can
        also be entered manually.
      required:
        - publicId
        - name
        - kind
        - ingestionEnabled
        - createdAt
        - updatedAt
      properties:
        publicId:
          type: string
          description: Tracker public ID
          example: "abc123def456"
        name:
          type: string
          description: Tracker name
          example: "ARR"
        kind:
          type: string
          enum: [money, number, percent, boolean, text, score]
          description: |
            Data type of the tracker. Determines the JSON type of each value's
            `value` field: `boolean` → boolean, `text` → string,
            `money`/`number`/`percent`/`score` → number.
          example: "money"
        preferredUnit:
          type: string
          nullable: true
          description: Preferred display unit for values (e.g. "USD", "employees")
          example: "USD"
        description:
          type: string
          nullable: true
          description: What this tracker measures
          example: "Annual recurring revenue as most recently reported"
        ingestionEnabled:
          type: boolean
          description: Whether automatic extraction from incoming content is enabled
        valuePolicy:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/TrackerValuePolicy'
        createdAt:
          type: string
          format: date-time
          description: When the tracker was created
        updatedAt:
          type: string
          format: date-time
          description: When the tracker definition was last updated
      example:
        publicId: "trk123abc456"
        name: "ARR"
        kind: "money"
        preferredUnit: "USD"
        description: "Annual recurring revenue as most recently reported"
        ingestionEnabled: true
        valuePolicy:
          kind: "point_in_time"
          denomination:
            allowed: ["month", "year"]
            preferred: "year"
        createdAt: "2026-01-10T09:00:00Z"
        updatedAt: "2026-04-02T17:30:00Z"

    TrackerValuePolicy:
      description: |
        Constraints on how values for this tracker are reported. 
        Use it to interpret the period fields on
        values and the bounds of score trackers. Discriminated by `kind`;
        see each variant for details. Trackers without period semantics
        (e.g. boolean or text trackers) have a null valuePolicy.
      oneOf:
        - $ref: '#/components/schemas/PeriodTotalValuePolicy'
        - $ref: '#/components/schemas/PointInTimeValuePolicy'
        - $ref: '#/components/schemas/ScoreValuePolicy'
      discriminator:
        propertyName: kind
        mapping:
          period_total: '#/components/schemas/PeriodTotalValuePolicy'
          point_in_time: '#/components/schemas/PointInTimeValuePolicy'
          score: '#/components/schemas/ScoreValuePolicy'

    PeriodTotalValuePolicy:
      type: object
      description: |
        For metrics that accumulate over a bounded window, like quarterly
        revenue or monthly GMV. The key test: does the value grow as time
        passes within the period? Values for these trackers carry a
        `period` of kind `period_total` whose `cadence` describes the
        accumulation window.
      required:
        - kind
        - cadence
      properties:
        kind:
          type: string
          enum: [period_total]
        cadence:
          type: object
          description: |
            Which reporting cadences are permitted. `allowed: null` means any
            cadence is valid; an array restricts values to those windows.
            `preferred` is the default cadence and is always one of `allowed`
            when `allowed` is an array.
          required:
            - allowed
          properties:
            allowed:
              type: array
              nullable: true
              items:
                $ref: '#/components/schemas/PeriodWindow'
              description: "Permitted accumulation windows (null = any)"
            preferred:
              $ref: '#/components/schemas/PeriodWindow'
      example:
        kind: "period_total"
        cadence:
          allowed: [{ unit: "quarter", length: 1 }]
          preferred: { unit: "quarter", length: 1 }

    PointInTimeValuePolicy:
      type: object
      description: |
        For snapshot metrics, like ARR, headcount, cash balance, churn, or
        gross margin. The key test: if time froze, would the value still be
        valid? Ratios, percentages, and rates are always point-in-time.

        The three optional sub-fields qualify the snapshot in different ways
        and are omitted entirely when not applicable to the metric:

        - `observationWindow`: the intrinsic lookback that defines the metric.
          Used when the value cannot be interpreted without its window, e.g.
          weekly active users (7 days) or 30-day churn.
        - `underlyingCadence`: the reporting period the source data came from,
          pure provenance. Used when the metric concept needs no window but
          the inputs come from a period, e.g. gross margin from a quarterly
          report. Does not change the metric's meaning.
        - `denomination`: for run-rate metrics that can be quoted per month or
          per year (MRR vs ARR).

        In each sub-field, `allowed: null` means any value is permitted and an
        array restricts values to the listed options. A pure snapshot like
        headcount has none of the three.
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [point_in_time]
        observationWindow:
          type: object
          description: "Intrinsic lookback window that defines the metric (e.g. WAU's 7 days)"
          required:
            - allowed
          properties:
            allowed:
              type: array
              nullable: true
              items:
                $ref: '#/components/schemas/PeriodWindow'
              description: "Permitted observation windows (null = any)"
            preferred:
              $ref: '#/components/schemas/PeriodWindow'
        underlyingCadence:
          type: object
          description: "Reporting period the source data came from (provenance only)"
          required:
            - allowed
          properties:
            allowed:
              type: array
              nullable: true
              items:
                $ref: '#/components/schemas/PeriodWindow'
              description: "Permitted source cadences (null = any)"
            preferred:
              $ref: '#/components/schemas/PeriodWindow'
        denomination:
          type: object
          description: "For run-rate metrics: whether values are quoted per month (MRR) or per year (ARR)"
          required:
            - allowed
          properties:
            allowed:
              type: array
              nullable: true
              items:
                type: string
                enum: [month, year]
              description: "Permitted denominations (null = any)"
            preferred:
              type: string
              enum: [month, year]
      example:
        kind: "point_in_time"
        denomination:
          allowed: ["month", "year"]
          preferred: "year"

    ScoreValuePolicy:
      type: object
      description: |
        For bounded AI-generated ratings, like a financial health score or a
        risk score. Score values have no period semantics; the value's `note`
        field carries the reasoning behind the score.
      required:
        - kind
        - min
        - max
        - direction
      properties:
        kind:
          type: string
          enum: [score]
        min:
          type: number
          description: Lower bound of the scale
          example: 0
        max:
          type: number
          description: Upper bound of the scale
          example: 100
        direction:
          type: string
          enum: [higher_is_better, lower_is_better]
          description: "Whether higher scores are good (quality) or bad (risk)"
          example: "higher_is_better"
      example:
        kind: "score"
        min: 0
        max: 100
        direction: "higher_is_better"

    PeriodWindow:
      type: object
      description: |
        A time window expressed as a unit and a length, e.g.
        `{ "unit": "day", "length": 7 }` for a 7-day window or
        `{ "unit": "quarter", "length": 1 }` for one quarter.
        Cadences use month/quarter/year; observation windows may also use
        day and week.
      required:
        - unit
        - length
      properties:
        unit:
          type: string
          enum: [day, week, month, quarter, year]
          example: "month"
        length:
          type: integer
          minimum: 1
          example: 1

    TrackerValue:
      type: object
      description: |
        A single observation of a tracker for one portfolio company, either
        extracted automatically from source content (`origin: system`) or entered
        manually (`origin: member`).
      required:
        - publicId
        - companyPublicId
        - kind
        - origin
        - status
        - citations
        - effectiveAt
        - reportedAt
        - createdAt
        - updatedAt
      properties:
        publicId:
          type: string
          description: Value public ID
          example: "abc123def456"
        companyPublicId:
          type: string
          description: Public ID of the portfolio company this value belongs to
          example: "654fed321cba"
        kind:
          type: string
          enum: [money, number, percent, boolean, text, score]
          description: The tracker's data type; determines the JSON type of `value`
          example: "money"
        value:
          nullable: true
          oneOf:
            - type: number
            - type: string
            - type: boolean
          description: |
            The observed value. JSON type follows `kind`: number for
            money/number/percent/score, string for text, boolean for boolean.
          example: 24000000
        unit:
          type: string
          nullable: true
          description: Unit of the value (e.g. "USD", "employees")
          example: "USD"
        period:
          description: |
            As-reported period semantics, discriminated by `kind`. Sub-fields
            are omitted (not null) when they don't apply to the value, and the
            whole object is omitted for values without period semantics
            (text, boolean, and score trackers).
          oneOf:
            - $ref: '#/components/schemas/TrackerValuePeriodTotal'
            - $ref: '#/components/schemas/TrackerValuePointInTime'
          discriminator:
            propertyName: kind
            mapping:
              period_total: '#/components/schemas/TrackerValuePeriodTotal'
              point_in_time: '#/components/schemas/TrackerValuePointInTime'
        note:
          type: string
          nullable: true
          description: |
            Supporting context, typically the text from the source material
            that the value was extracted from. For score trackers this holds
            the reasoning behind the score.
        summary:
          type: string
          description: |
            Score values only: a short label capturing the key driver of the
            score (e.g. "Revenue 3x; burn rate limits ceiling"). Omitted for
            all other tracker kinds.
        origin:
          type: string
          enum: [member, system]
          description: "`system` = extracted automatically, `member` = entered manually"
          example: "system"
        status:
          type: string
          enum: [suggested, confirmed, rejected]
          description: |
            Review status. `suggested` values were extracted automatically and
            await human review; `confirmed` values have been validated;
            `rejected` values were marked wrong or outdated.
          example: "confirmed"
        citations:
          type: array
          items:
            $ref: '#/components/schemas/Citation'
          description: Source evidence the value was extracted from (empty for manual entries)
        effectiveAt:
          type: string
          format: date
          description: The date the value is valid for (often a period-end date)
          example: "2026-05-31"
        reportedAt:
          type: string
          format: date-time
          description: When the value was reported in the source content
        createdAt:
          type: string
          format: date-time
          description: When the value was recorded in Concrete
        updatedAt:
          type: string
          format: date-time
          description: When the value's content was last updated (excludes status-only changes)
        statusUpdatedAt:
          type: string
          format: date-time
          nullable: true
          description: When the review status last changed
      example:
        publicId: "val123abc456"
        companyPublicId: "cmp456def789"
        kind: "money"
        value: 24000000
        unit: "USD"
        period:
          kind: "point_in_time"
          denomination: "year"
        note: "We closed May at $24M ARR, up 18% since our February update, with net revenue retention holding at 124%."
        origin: "system"
        status: "confirmed"
        citations:
          - type: "file"
            publicId: "fil789ghi012"
            name: "Acme May 2026 Investor Update.pdf"
            extension: "pdf"
            permalink: "https://app.concretehq.com/v/fil789ghi012"
        effectiveAt: "2026-05-31"
        reportedAt: "2026-06-01T12:00:00Z"
        createdAt: "2026-06-02T08:14:09Z"
        updatedAt: "2026-06-02T08:14:09Z"
        statusUpdatedAt: "2026-06-03T16:40:00Z"

    TrackerValuePeriodTotal:
      type: object
      description: |
        Period semantics for a value accumulated over a bounded window, like
        quarterly revenue. `cadence` is the accumulation window the company
        reported the total for.
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [period_total]
        cadence:
          $ref: '#/components/schemas/PeriodWindow'
        rawText:
          type: string
          description: The period as stated in the source (e.g. "Q3 2026")
      example:
        kind: "period_total"
        cadence: { unit: "quarter", length: 1 }
        rawText: "Q3 2026"

    TrackerValuePointInTime:
      type: object
      description: |
        Period semantics for a snapshot value, like ARR or headcount. All
        qualifiers are omitted when they don't apply: `observationWindow` is
        the intrinsic lookback for metrics like weekly active users,
        `underlyingCadence` records the reporting period the source data came
        from, and `denomination` is the run-rate basis ("month" for MRR-style
        figures, "year" for ARR-style). A pure snapshot like headcount carries
        only `kind`.
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [point_in_time]
        observationWindow:
          $ref: '#/components/schemas/PeriodWindow'
        underlyingCadence:
          $ref: '#/components/schemas/PeriodWindow'
        denomination:
          type: string
          enum: [month, year]
          description: As-reported run-rate basis (never converted)
      example:
        kind: "point_in_time"
        denomination: "year"

    Citation:
      type: object
      description: |
        Source evidence backing an extracted value. `type` identifies the kind
        of source entity; the type-specific metadata fields below are populated
        where they apply. Citations may include a `parent` object linking to
        the source material that contains them, for example the email a cited
        file arrived on, or the file a cited element belongs to.
      required:
        - type
        - publicId
        - permalink
      properties:
        type:
          type: string
          enum: [touchpoint, note, email, file, element, meeting]
          description: |
            The kind of source entity cited: a touchpoint (related activity
            grouped around an event), a note, an email, a file, an element
            (a section of a file, such as a page or a spreadsheet tab), or a
            meeting.
          example: "file"
        publicId:
          type: string
          description: Public ID of the cited entity
          example: "abc123def456"
        permalink:
          type: string
          description: Link to view the source in the Concrete app
          example: "https://app.concretehq.com/v/abc123def456"
        occurredAt:
          type: string
          format: date-time
          nullable: true
          description: When the cited content occurred, where applicable
        subject:
          type: string
          nullable: true
          description: "Emails: subject line"
          example: "May investor update"
        name:
          type: string
          description: "Files: file name"
          example: "Acme May 2026 Investor Update.pdf"
        extension:
          type: string
          description: "Files: file extension"
          example: "pdf"
        title:
          type: string
          description: "Meetings: meeting title"
          example: "Acme monthly check-in"
        headline:
          type: string
          nullable: true
          description: "Touchpoints: headline summarizing the activity"
        companyPublicId:
          type: string
          description: "Touchpoints: public ID of the company the touchpoint belongs to"
        author:
          type: string
          description: "Notes: author display name"
        label:
          type: string
          nullable: true
          description: "Elements: label describing the section (e.g. a page or tab name)"
        index:
          type: integer
          description: "Elements: position of the section within the file"
        parent:
          type: object
          description: |
            The source material containing the cited entity, shaped like a
            citation itself but without a `permalink` (use the citing entity's
            permalink). Files carry the email they arrived on; elements carry
            their file, which may in turn carry its email.

    TrackersResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Tracker'
        nextPageToken:
          type: string
          description: Token for fetching next page
          example: "next_page_token_here"

    TrackerValuesResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/TrackerValue'
        nextPageToken:
          type: string
          description: Token for fetching next page
          example: "next_page_token_here"

    Insight:
      type: object
      description: One extracted insight from a touchpoint's activity, with citations to the source material backing it.
      required:
        - publicId
        - category
        - content
        - occurredAt
        - citations
      properties:
        publicId:
          type: string
          description: Insight public ID
          example: "ins789ghi012"
        category:
          type: string
          description: Insight category
          enum: [funding, growth, product, go_to_market, team, operations, financial_health, marketing, governance, exit, other]
          example: "growth"
        content:
          type: string
          description: The insight, as one or two sentences
          example: "ARR reached $24M in May, up 18% since the February update."
        occurredAt:
          type: string
          format: date-time
          description: When the underlying activity occurred
        citations:
          type: array
          description: Source evidence backing the insight
          items:
            $ref: '#/components/schemas/Citation'

    Touchpoint:
      type: object
      description: |
        A touchpoint groups the activity around one company event, such as an
        investor update, a board meeting, or an announcement. It summarizes
        the activity and carries the insights extracted from it.
      required:
        - publicId
        - headline
        - topics
        - occurredAt
        - updatedAt
        - insights
      properties:
        publicId:
          type: string
          description: Touchpoint public ID
          example: "tch123abc456"
        headline:
          type: string
          nullable: true
          description: Headline summarizing the activity
          example: "Acme May 2026 investor update"
        topics:
          type: array
          nullable: true
          description: Topics classifying the touchpoint
          items:
            type: string
            enum: [periodic-company-update, company-announcement, board-meeting, adhoc-company-update, legal-communication, other]
        occurredAt:
          type: string
          format: date-time
          description: When the activity occurred
        updatedAt:
          type: string
          format: date-time
          description: |
            When the touchpoint last changed: its summary or topics were
            updated, or its insights were re-derived as new activity arrived.
        insights:
          type: array
          description: Insights extracted from the touchpoint's activity
          items:
            $ref: '#/components/schemas/Insight'

    TouchpointsResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Touchpoint'
        nextPageToken:
          type: string
          description: Token for fetching next page
          example: "next_page_token_here"

    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable error message
          example: "Not found"

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

tags:
  - name: Authentication
    description: Authentication and member information
  - name: Companies
    description: Portfolio company management
  - name: Funds
    description: Fund and portfolio management
  - name: Meetings
    description: External meeting imports
  - name: Notes
    description: Notes attached to companies and people
  - name: Connections
    description: Data connections and message uploads
  - name: Trackers
    description: Tracked metrics and their per-company value history