openapi: 3.1.0
info:
  title: Govly Tools API (Alpha)
  version: "1.0.0-alpha"
  description: >
    ALPHA / UNSTABLE: This API is still in active development. Endpoint
    behavior, request fields, response fields, error codes, and operation names
    may change before the Tools API is declared stable.

    REST-callable tool surface for agent and automation workflows. Agents are
    the primary consumer, but integrations can be built on this API. Responses
    are JSON for typed clients; MCP tools may render action results into
    text-oriented formats separately.
servers:
  - url: https://app.govly.com
security:
  - bearerApiKey: []
  - headerApiKey: []
tags:
  - name: Opportunities
    description: Search, fetch, and inspect Govly opportunity records.
  - name: Workspaces
    description: Create, update, and inspect opportunity workspaces.
  - name: Workspace Members
    description: Add users and teams to workspaces.
  - name: Workspace Attachments
    description: List and upload workspace attachments.
  - name: Workspace Comments
    description: Post comments to workspaces.
  - name: Follows
    description: Follow opportunities and related workspace activity.
  - name: Saved Searches
    description: List saved opportunity searches and cached matches.
  - name: Attachments
    description: Fetch extracted or readable attachment text.
  - name: Quote Submissions
    description: Inspect quote submission requirements, submit quotes, and poll submission status.
  - name: Inbox
    description: Read and triage the authenticated user's matched inbox items.
paths:
  /api/tools/v1/opportunities:
    get:
      tags: [Opportunities]
      operationId: fetch_opportunities
      summary: Fetch opportunities by ID
      parameters:
        - name: ids
          in: query
          required: true
          style: form
          explode: true
          schema:
            type: array
            items:
              type: string
      responses:
        "200":
          description: Opportunities
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Opportunity"
                  meta:
                    $ref: "#/components/schemas/CountMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/opportunities/{id}:
    get:
      tags: [Opportunities]
      operationId: show_opportunity
      summary: Show an opportunity
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Opportunity
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Opportunity"
                  meta:
                    $ref: "#/components/schemas/ActionMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /api/tools/v1/opportunities/search:
    post:
      tags: [Opportunities]
      operationId: search_opportunities
      summary: Search opportunities
      description: >
        Search opportunities with the constrained public parameter set shared by
        MCP search_opportunities. Defaults to open opportunities and excludes
        prediction records from search results.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                query:
                  type: string
                cursor:
                  type: string
                  description: Opaque cursor from the previous response's meta.nextCursor. Omit for the first page.
                perPage:
                  type: integer
                  default: 25
                  maximum: 100
                status:
                  type: string
                  default: open
                  enum: [open, all, expired, cancelled, awarded, not_cancelled]
                  description: >
                    Defaults to open for actionable opportunities. Use all,
                    expired, cancelled, awarded, or not_cancelled for historical
                    or specific lookup requests. Prediction records are always
                    excluded.
                naics:
                  type: array
                  items:
                    type: string
                  description: NAICS codes to filter by, such as 541519.
                postedAfter:
                  type: string
                  description: Only include opportunities posted at or after this date/time. ISO 8601 dates work best.
                postedBefore:
                  type: string
                  description: Only include opportunities posted at or before this date/time. ISO 8601 dates work best.
                modifiedAfter:
                  type: string
                  description: Only include opportunities modified at or after this date/time. ISO 8601 dates work best.
                modifiedBefore:
                  type: string
                  description: Only include opportunities modified at or before this date/time. ISO 8601 dates work best.
                respondByAfter:
                  type: string
                  description: Only include opportunities with a response deadline at or after this date/time. ISO 8601 dates work best.
                respondByBefore:
                  type: string
                  description: Only include opportunities with a response deadline at or before this date/time. ISO 8601 dates work best.
                awardedAfter:
                  type: string
                  description: Only include awarded opportunities awarded at or after this date/time. ISO 8601 dates work best.
                awardedBefore:
                  type: string
                  description: Only include awarded opportunities awarded at or before this date/time. ISO 8601 dates work best.
                sort:
                  type: string
                  enum: [modified_at, posted_at, respond_by, relevance]
                sortDirection:
                  type: string
                  enum: [asc, desc]
      responses:
        "200":
          description: Matching opportunities
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Opportunity"
                  meta:
                    $ref: "#/components/schemas/CursorMeta"
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/workspaces:
    get:
      tags: [Workspaces]
      operationId: list_workspaces
      summary: List workspaces associated with an entity
      parameters:
        - $ref: "#/components/parameters/associatedType"
        - $ref: "#/components/parameters/associatedId"
      responses:
        "200":
          description: Matching workspaces
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Workspace"
                  meta:
                    type: object
                    required: [count]
                    properties:
                      count:
                        type: integer
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
    post:
      tags: [Workspaces]
      operationId: create_workspace
      summary: Create a workspace
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                primaryFocus:
                  $ref: "#/components/schemas/PrimaryFocusInput"
                privateAccess:
                  type: boolean
                workflowStage:
                  type: string
                statusCategory:
                  type: string
                status:
                  type: string
                  description: Human-readable workspace status name, status category, or legacy workflow stage. Prefer this for agent and MCP-style clients.
                workspaceStatusId:
                  type: string
                  description: Current-organization workspace status ID. Prefer status unless the caller already has this ID.
                organizationDefault:
                  type: boolean
                autoFollowCreator:
                  type: boolean
      responses:
        "201":
          description: Created workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/workspaces/{id}:
    get:
      tags: [Workspaces]
      operationId: show_workspace
      summary: Show a workspace
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
    patch:
      tags: [Workspaces]
      operationId: update_workspace
      summary: Update a workspace
      parameters:
        - $ref: "#/components/parameters/id"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                privateAccess:
                  type: boolean
                workflowStage:
                  type: string
                statusCategory:
                  type: string
                status:
                  type: string
                  description: Human-readable workspace status name, status category, or legacy workflow stage. Prefer this for agent and MCP-style clients.
                workspaceStatusId:
                  type: string
                  description: Current-organization workspace status ID. Prefer status unless the caller already has this ID.
      responses:
        "200":
          description: Updated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/workspaces/{workspaceId}/members:
    post:
      tags: [Workspace Members]
      operationId: add_workspace_member
      summary: Add a user or team member to a workspace
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                userId:
                  type: string
                teamId:
                  type: string
                notifications:
                  type: string
                state:
                  type: string
      responses:
        "201":
          description: Created workspace membership
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/WorkspaceMember"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/workspaces/{workspaceId}/attachments:
    get:
      tags: [Workspace Attachments]
      operationId: list_workspace_attachments
      summary: List workspace attachments
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      responses:
        "200":
          description: Workspace attachments
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WorkspaceAttachment"
                  meta:
                    $ref: "#/components/schemas/CountMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
    post:
      tags: [Workspace Attachments]
      operationId: upload_workspace_attachments
      summary: Upload workspace attachments
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [attachments]
              properties:
                attachments:
                  type: array
                  items:
                    type: string
                    format: binary
          application/json:
            schema:
              type: object
              required: [attachments]
              properties:
                attachments:
                  type: array
                  items:
                    type: string
                    description: ActiveStorage signed blob ID
      responses:
        "201":
          description: Created attachments
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WorkspaceAttachment"
                  meta:
                    $ref: "#/components/schemas/CountMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/workspaces/{workspaceId}/comments:
    post:
      tags: [Workspace Comments]
      operationId: create_workspace_comment
      summary: Post a Markdown comment to a workspace
      parameters:
        - $ref: "#/components/parameters/workspaceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body:
                  type: string
                  description: Markdown comment body.
      responses:
        "201":
          description: Created workspace comment
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Comment"
                  meta:
                    type: object
                    properties:
                      workspaceId:
                        type: string
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/follows:
    post:
      tags: [Follows]
      operationId: follow_entity
      summary: Follow a Govly entity
      description: >
        Follow, track, or subscribe to changes for a Govly entity. For
        opportunities, this creates or reuses the default opportunity
        workspace and follows that workspace for the authenticated user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, id]
              properties:
                type:
                  type: string
                  enum: [opportunity, opp]
                  description: Entity type to follow. opp is accepted as an alias for opportunity.
                id:
                  type: string
                  description: Govly entity ID.
                notifications:
                  type: string
                  description: Notification preference for this follow. Omit to use the user's default setting.
      responses:
        "201":
          description: Active follow on the entity's default workspace
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [workspace, membership]
                    properties:
                      workspace:
                        $ref: "#/components/schemas/Workspace"
                      membership:
                        $ref: "#/components/schemas/WorkspaceMember"
                  meta:
                    type: object
                    properties:
                      entityType:
                        type: string
                      entityId:
                        type: string
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/opportunities/{opportunityId}/not_interested:
    post:
      tags: [Opportunities]
      operationId: mark_opportunity_not_interested
      summary: Mark an opportunity not interested
      description: >
        Marks the opportunity not interested / dismissed for the authenticated
        user. Sets a team-visible disinterested status on the user's workspace
        follow, unfollows the user, and dismisses the user's matching inbox
        items.
      parameters:
        - name: opportunityId
          in: path
          required: true
          schema:
            type: string
          description: Govly opportunity ID.
      responses:
        "201":
          description: Opportunity marked not interested
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotInterestedResult"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
    delete:
      tags: [Opportunities]
      operationId: undo_opportunity_not_interested
      summary: Undo a not-interested mark
      description: >
        Removes a previous not-interested mark for the authenticated user and
        restores matching dismissed inbox items. The user returns to a neutral
        (not following) state.
      parameters:
        - name: opportunityId
          in: path
          required: true
          schema:
            type: string
          description: Govly opportunity ID.
      responses:
        "200":
          description: Not-interested mark removed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotInterestedResult"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/opportunities/saved_searches:
    get:
      tags: [Saved Searches]
      operationId: list_opportunity_saved_searches
      summary: List opportunity saved searches
      description: Latest match previews exclude prediction records.
      parameters:
        - $ref: "#/components/parameters/cursor"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200":
          description: Opportunity saved searches
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/OppSearch"
                  meta:
                    $ref: "#/components/schemas/CursorMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/opportunities/saved_searches/{id}:
    get:
      tags: [Saved Searches]
      operationId: show_opportunity_saved_search
      summary: Show an opportunity saved search
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Opportunity saved search
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/OppSearch"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /api/tools/v1/opportunities/saved_searches/{savedSearchId}/results:
    get:
      tags: [Saved Searches]
      operationId: list_opportunity_saved_search_results
      summary: List cached opportunity results for a saved search
      description: Returns cached saved-search matches, excluding prediction records.
      parameters:
        - $ref: "#/components/parameters/savedSearchId"
        - $ref: "#/components/parameters/cursor"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200":
          description: Cached saved-search matches
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/OppSearchMatch"
                  meta:
                    $ref: "#/components/schemas/CursorMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/attachments/{attachmentType}/{id}/text:
    get:
      tags: [Attachments]
      operationId: fetch_attachment_text
      summary: Fetch readable attachment text
      description: >
        Fetches extracted text for opportunity attachments, Document records,
        and raw text-like content for workspace attachments, including CSV, TSV,
        JSON, Markdown, HTML, XML, YAML, logs, and plain text. Opportunity
        attachment IDs are the IDs returned in opportunity aggregateAttachments
        and opportunitySources.attachments. Workspace attachment IDs are returned
        by workspace attachment endpoints. Document IDs come from document-backed
        surfaces.
      parameters:
        - $ref: "#/components/parameters/attachmentType"
        - $ref: "#/components/parameters/id"
        - name: startOffset
          in: query
          required: false
          schema:
            type: integer
            default: 0
            minimum: 0
        - name: maxChars
          in: query
          required: false
          schema:
            type: integer
            default: 100000
            maximum: 200000
      responses:
        "200":
          description: Attachment text, or a structured status if text is unavailable.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/AttachmentText"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/quote/submissions/requirements:
    get:
      tags: [Quote Submissions]
      operationId: get_quote_submission_requirements
      summary: Check quote submission requirements
      description: >
        Returns portal-specific quote submission requirements and blocking
        reasons for the authenticated actor and workspace.
      parameters:
        - name: workspaceId
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Quote submission requirements
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteSubmissionRequirementsEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/quote/submissions:
    get:
      tags: [Quote Submissions]
      operationId: list_quote_submissions
      summary: List quote submissions for a workspace
      parameters:
        - name: workspaceId
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Quote submissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteSubmissionListEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
    post:
      tags: [Quote Submissions]
      operationId: create_quote_submission
      summary: Submit a quote
      description: >
        Submit a quote to the portal for an opportunity workspace. Upload the
        quote file to the workspace attachments endpoint first, then pass the
        returned workspaceAttachmentId here. The response returns a submission
        ID that can be polled with show_quote_submission.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [workspaceId, workspaceAttachmentId, quoteTotal]
              properties:
                workspaceId:
                  type: string
                workspaceAttachmentId:
                  type: string
                quoteTotal:
                  type: string
                  description: Decimal quote total. Must be greater than 0.
                comment:
                  type: string
                  description: Optional portal submission comment.
      responses:
        "201":
          description: Created quote submission
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteSubmissionEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/quote/submissions/{id}:
    get:
      tags: [Quote Submissions]
      operationId: show_quote_submission
      summary: Show quote submission status
      description: >
        Poll this endpoint after create_quote_submission until meta.terminal is
        true. Completed submissions include confirmation details when the portal
        returns them; failed submissions include failureReason when available.
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Quote submission status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteSubmissionEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items:
    get:
      tags: [Inbox]
      operationId: list_inbox_items
      summary: List inbox items
      description: >
        List the authenticated user's active inbox items (Govly AI matches and
        saved-search matches), newest first.
      parameters:
        - $ref: "#/components/parameters/cursor"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200":
          description: Inbox items
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/InboxItem"
                  meta:
                    $ref: "#/components/schemas/CursorMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items/search:
    post:
      tags: [Inbox]
      operationId: search_inbox_items
      summary: Search inbox items
      description: >
        Full-text search and filter the authenticated user's inbox items.
        Returns matchable-type aggregation counts.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                query:
                  type: string
                cursor:
                  type: string
                  description: Opaque cursor from the previous response's meta.nextCursor. Omit for the first page.
                perPage:
                  type: integer
                  default: 25
                  maximum: 100
                sort:
                  type: string
                  enum: [ranked_score, created_at, deadline_at]
                sortDirection:
                  type: string
                  enum: [asc, desc]
                state:
                  type: array
                  items:
                    type: string
                    enum: [unread, seen, actioned, dismissed]
                matchableType:
                  type: array
                  items:
                    type: string
                    enum: [Opp, Award, "ProcurementSignal::Cluster"]
                recordType:
                  type: array
                  items:
                    type: string
                sourceType:
                  type: string
                  enum: [govly_ai, saved_search]
                naicsCodes:
                  type: array
                  items:
                    type: string
                createdAfter:
                  type: string
                createdBefore:
                  type: string
                deadlineAtFrom:
                  type: string
                deadlineAtTo:
                  type: string
      responses:
        "200":
          description: Matching inbox items
          content:
            application/json:
              schema:
                type: object
                required: [data, meta, aggregations]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/InboxItem"
                  meta:
                    $ref: "#/components/schemas/InboxSearchMeta"
                  aggregations:
                    $ref: "#/components/schemas/InboxAggregations"
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items/{id}:
    get:
      tags: [Inbox]
      operationId: show_inbox_item
      summary: Show an inbox item
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Inbox item
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InboxItemEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items/{id}/accept:
    post:
      tags: [Inbox]
      operationId: accept_inbox_item
      summary: Accept (follow) an inbox item
      description: >
        Follow the underlying opportunity, award, or signal and create or reuse
        its default workspace. Accept is FINAL and cannot be reversed with
        restore. The response meta includes the resulting workspaceId.
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Accepted inbox item
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    $ref: "#/components/schemas/InboxItem"
                  meta:
                    $ref: "#/components/schemas/InboxAcceptMeta"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items/{id}/dismiss:
    post:
      tags: [Inbox]
      operationId: dismiss_inbox_item
      summary: Dismiss an inbox item
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Dismissed inbox item
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InboxItemEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items/{id}/decline:
    post:
      tags: [Inbox]
      operationId: decline_inbox_item
      summary: Decline (reject) an inbox item as a bad fit
      description: >
        Mark the item a bad fit, optionally with a reason that feeds the
        organization's matching-exclusions memory. Reversible with restore.
      parameters:
        - $ref: "#/components/parameters/id"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                reason:
                  type: string
                  enum: [bad_fit, irrelevant, cannot_bid, bad_timing, other]
      responses:
        "200":
          description: Declined inbox item
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InboxItemEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
  /api/tools/v1/inbox_items/{id}/restore:
    post:
      tags: [Inbox]
      operationId: restore_inbox_item
      summary: Restore a dismissed or declined inbox item
      description: >
        Return a dismissed or bad-fit item to the active inbox. Only dismissed
        or bad-fit items can be restored — an accepted item cannot.
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Restored inbox item
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InboxItemEnvelope"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
        "422":
          $ref: "#/components/responses/Error"
components:
  securitySchemes:
    bearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: API key
    headerApiKey:
      type: apiKey
      in: header
      name: X-API-KEY
  parameters:
    id:
      name: id
      in: path
      required: true
      schema:
        type: string
    attachmentType:
      name: attachmentType
      in: path
      required: true
      schema:
        type: string
        enum: [opportunity, workspace, document]
    workspaceId:
      name: workspaceId
      in: path
      required: true
      schema:
        type: string
    savedSearchId:
      name: savedSearchId
      in: path
      required: true
      schema:
        type: string
    associatedType:
      name: associatedType
      in: query
      required: true
      schema:
        type: string
        enum: [opportunity]
    associatedId:
      name: associatedId
      in: query
      required: true
      schema:
        type: string
    cursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
    perPage:
      name: perPage
      in: query
      required: false
      schema:
        type: integer
        default: 25
        maximum: 100
  responses:
    Error:
      description: Error response
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
  schemas:
    NotInterestedResult:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id, type, notInterested]
          properties:
            id:
              type: string
              description: Govly opportunity ID.
            type:
              type: string
              enum: [opportunity]
            notInterested:
              type: boolean
              description: Whether the opportunity is now marked not interested for the authenticated user.
        meta:
          type: object
          properties:
            oppId:
              type: string
            undo:
              type: boolean
    WorkspaceEnvelope:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Workspace"
        meta:
          $ref: "#/components/schemas/ActionMeta"
    Workspace:
      type: object
      required: [id, name, status]
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        commentsCount:
          type: integer
        status:
          type: object
          required: [category, label]
          properties:
            category:
              type: string
              description: Coarse status bucket. Triage is the default for newly created workspaces.
            label:
              type: string
              description: Human-readable status name. Falls back to the category label when no custom status is set.
        primaryFocus:
          $ref: "#/components/schemas/AssociatedEntity"
        comments:
          type: array
          description: Recent comments. Only present on show/create/update responses, capped at the most recent 20.
          items:
            $ref: "#/components/schemas/Comment"
        attachments:
          type: array
          description: Workspace attachments. Only present on show/create/update responses.
          items:
            $ref: "#/components/schemas/WorkspaceAttachment"
    Comment:
      type: object
      required: [id, body, createdAt, attachments]
      properties:
        id:
          type: string
        body:
          type: string
          description: Markdown body for the comment.
        attachments:
          type: array
          description: Workspace attachments tied to this comment.
          items:
            $ref: "#/components/schemas/WorkspaceAttachment"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PrimaryFocusInput:
      type: object
      required: [type, id]
      properties:
        type:
          type: string
          enum: [opportunity]
        id:
          type: string
    AssociatedEntity:
      type: object
      required: [type, id]
      properties:
        type:
          type: string
          enum: [opportunity]
        id:
          type: string
    WorkspaceMember:
      type: object
      required: [id, member]
      properties:
        id:
          type: string
        state:
          type: string
        notifications:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        member:
          type: object
          required: [type, id]
          properties:
            type:
              type: string
              enum: [user, team]
            id:
              type: string
            name:
              type: string
            email:
              type: string
              format: email
            organization:
              type: object
              properties:
                id:
                  type: string
                name:
                  type: string
    WorkspaceAttachment:
      type: object
      required: [id, workspaceId, filename]
      properties:
        id:
          type: string
        workspaceId:
          type: string
        commentId:
          type: string
          nullable: true
        filename:
          type: string
        contentType:
          type: string
        byteSize:
          type: integer
        createdAt:
          type: string
          format: date-time
        file:
          description: Present only on the workspace attachments endpoint. Workspace show responses surface attachments without download metadata.
          $ref: "#/components/schemas/AttachmentFile"
    QuoteSubmissionEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/QuoteSubmission"
        meta:
          $ref: "#/components/schemas/QuoteSubmissionStatusMeta"
    QuoteSubmissionListEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/QuoteSubmission"
        meta:
          $ref: "#/components/schemas/CountMeta"
    QuoteSubmissionRequirementsEnvelope:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/QuoteSubmissionRequirements"
    QuoteSubmission:
      type: object
      required: [id, status, portal, workspaceId, opportunityId, quoteTotal, createdAt]
      properties:
        id:
          type: string
        status:
          type: string
          enum: [pending, running, completed, failed, cancelled]
        portal:
          type: string
          enum: [chess]
        workspaceId:
          type: string
        opportunityId:
          type: string
        quoteTotal:
          type: string
        comment:
          type: string
        confirmationNumber:
          type: string
        failureReason:
          type: string
        submittedAt:
          type: string
          format: date-time
        confirmedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        file:
          $ref: "#/components/schemas/QuoteSubmissionAttachment"
        evidence:
          $ref: "#/components/schemas/QuoteSubmissionAttachment"
    QuoteSubmissionAttachment:
      type: object
      required: [id, filename]
      properties:
        id:
          type: string
        filename:
          type: string
        contentType:
          type: string
        byteSize:
          type: integer
        file:
          $ref: "#/components/schemas/AttachmentFile"
    QuoteSubmissionStatusMeta:
      type: object
      required: [terminal]
      properties:
        terminal:
          type: boolean
          description: True when the submission no longer needs polling.
        nextPollAfterSeconds:
          type: integer
          description: Suggested delay before polling again. Omitted for terminal submissions.
        retryable:
          type: boolean
          description: Present for failed submissions when creating a new submission is reasonable.
    QuoteSubmissionRequirements:
      type: object
      required: [eligible, requiredFields, optionalFields, blockingReasons]
      properties:
        workspaceId:
          type: string
        opportunityId:
          type: string
        portal:
          type: string
          enum: [chess]
        eligible:
          type: boolean
          description: True when the authenticated actor can submit with the returned requirements.
        requiredFields:
          type: array
          items:
            $ref: "#/components/schemas/QuoteSubmissionField"
        optionalFields:
          type: array
          items:
            $ref: "#/components/schemas/QuoteSubmissionField"
        fileRules:
          $ref: "#/components/schemas/QuoteSubmissionFileRules"
        blockingReasons:
          type: array
          items:
            $ref: "#/components/schemas/QuoteSubmissionBlockingReason"
    QuoteSubmissionField:
      type: object
      required: [name, type]
      properties:
        name:
          type: string
        type:
          type: string
        description:
          type: string
    QuoteSubmissionFileRules:
      type: object
      required: [maxBytes, allowedExtensions]
      properties:
        maxBytes:
          type: integer
        allowedExtensions:
          type: array
          items:
            type: string
    QuoteSubmissionBlockingReason:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          enum:
            - feature_flag_disabled
            - quote_submission_not_available_for_portal
            - missing_or_inactive_portal_account
            - workspace_follow_required
            - workspace_not_linked_to_opportunity
        message:
          type: string
    InboxItemEnvelope:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/InboxItem"
    InboxItem:
      type: object
      required: [id, matchableId, matchableType, state, origins]
      properties:
        id:
          type: string
        matchableId:
          type: string
        matchableType:
          type: string
          enum: [Opp, Award, "ProcurementSignal::Cluster"]
        state:
          type: string
          enum: [unread, seen, actioned, dismissed]
        feedback:
          type: string
          enum: [followed, dismissed, bad_fit]
        feedbackReason:
          type: string
          enum: [bad_fit, irrelevant, cannot_bid, bad_timing, other]
        title:
          type: string
        description:
          type: string
        fitReason:
          type: string
        fitScore:
          type: number
        sourceType:
          type: string
          enum: [govly_ai, saved_search]
          description: How the item first reached the inbox.
        identifier:
          type: string
        recordType:
          type: string
        deadlineAt:
          type: string
          format: date-time
        postedOrAwardedDate:
          type: string
          format: date
        publicEntityNames:
          type: array
          items:
            type: string
        isoCode:
          type: string
        naicsCodes:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
        highlight:
          type: object
          description: Per-field search highlights. Only present on search results.
          additionalProperties:
            type: string
        origins:
          type: array
          description: Every source that surfaced this (deduped) item.
          items:
            $ref: "#/components/schemas/InboxItemOrigin"
    InboxItemOrigin:
      type: object
      required: [sourceType]
      properties:
        sourceType:
          type: string
          enum: [govly_ai, saved_search]
        savedSearchId:
          type: string
        savedSearchName:
          type: string
    InboxSearchMeta:
      type: object
      required: [total, nextCursor]
      properties:
        total:
          type: integer
          description: Total matching items (capped at 10000 by the search index).
        nextCursor:
          type:
            - string
            - "null"
    InboxAggregations:
      type: object
      required: [matchableType]
      properties:
        matchableType:
          type: object
          description: Count of matching items per matchable type.
          additionalProperties:
            type: integer
    InboxAcceptMeta:
      type: object
      required: [state, feedback, matchableType, matchableId, final]
      properties:
        state:
          type: string
        feedback:
          type: string
        matchableType:
          type: string
        matchableId:
          type: string
        workspaceId:
          type:
            - string
            - "null"
          description: Default workspace the accept followed, when one exists.
        final:
          type: boolean
          description: Always true — accept is irreversible.
    OppSearch:
      type: object
      required: [id, name, active, criteria]
      properties:
        id:
          type: string
        name:
          type: string
        active:
          type: boolean
        criteria:
          type: object
          description: Stored search criteria for this saved search.
        matchCount:
          type: integer
        lastMatchedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    OppSearchMatch:
      type: object
      required: [id, matchedAt, savedSearchId, opportunity]
      properties:
        id:
          type: string
        matchedAt:
          type: string
          format: date-time
        savedSearchId:
          type: string
        opportunity:
          $ref: "#/components/schemas/Opportunity"
    Opportunity:
      type: object
      required: [id]
      properties:
        id:
          type: string
          description: Govly opportunity ID. Use this ID for show requests, workspace associations, and links.
        title:
          type: string
        displayName:
          type: string
          description: Human-readable opportunity reference, usually source name plus identifier.
        identifier:
          type: string
          description: Public opportunity identifier, such as a solicitation or request number. This may differ from raw source-system IDs when Govly merges related notices.
        externalUrl:
          type: string
          format: uri
        recordType:
          type: string
        status:
          type: string
          enum: [open, expired, cancelled, awarded, forecasted, unknown]
        postedAt:
          type: string
          format: date-time
        modifiedAt:
          type: string
          format: date-time
        respondBy:
          type: string
          format: date-time
        cancelledAt:
          type: string
          format: date-time
        awardedAt:
          type: string
          format: date-time
        aiTitle:
          type: string
        aiSummary:
          type: string
        jurisdiction:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
            isoCode:
              type: string
        contractVehicle:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        followerCount:
          type: integer
          description: Count of active follow rows across visible workspaces for this opportunity.
        followedByCurrentUser:
          type: boolean
          description: Whether the authenticated user actively follows any visible workspace for this opportunity.
        followedByCurrentOrganization:
          type: boolean
          description: Whether the authenticated user's organization actively follows any visible workspace for this opportunity.
        workspaces:
          type: array
          description: Visible workspaces associated with this opportunity, including active follow context.
          items:
            $ref: "#/components/schemas/OpportunityWorkspace"
        aggregateAttachments:
          type: array
          items:
            $ref: "#/components/schemas/OpportunityAttachment"
        opportunitySources:
          type: array
          items:
            $ref: "#/components/schemas/OpportunitySource"
    OpportunityWorkspace:
      type: object
      required: [id, name, status, followerCount, follows]
      properties:
        id:
          type: string
        name:
          type: string
        organizationDefault:
          type: boolean
        organization:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        status:
          type: object
          required: [category, label]
          properties:
            category:
              type: string
            label:
              type: string
        followerCount:
          type: integer
          description: Count of active follow rows on this workspace.
        followedByCurrentUser:
          type: boolean
        followedByCurrentOrganization:
          type: boolean
        follows:
          type: array
          description: Active workspace follows. Inactive/unfollowed/disinterested rows are omitted.
          items:
            $ref: "#/components/schemas/WorkspaceMember"
    OpportunitySource:
      type: object
      required: [id, attachments]
      properties:
        id:
          type: string
        postedAt:
          type: string
          format: date-time
        externalUrl:
          type: string
          format: uri
        noticeType:
          type: string
        recordType:
          type: string
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/OpportunityAttachment"
    OpportunityAttachment:
      type: object
      required: [id, filename, redacted]
      properties:
        id:
          type: string
        filename:
          type: string
        contentType:
          type: string
        byteSize:
          type: integer
        redacted:
          type: boolean
        tags:
          type: array
          items:
            type: string
        file:
          $ref: "#/components/schemas/AttachmentFile"
    AttachmentText:
      type: object
      required: [id, attachmentType, filename, status, startOffset, maxChars, truncated, totalLength]
      properties:
        id:
          type: string
        attachmentType:
          type: string
          enum: [opportunity, workspace, document]
        filename:
          type: string
        contentType:
          type: string
        byteSize:
          type: integer
        status:
          type: string
          enum: [available, processing, failed, unavailable]
        source:
          type:
            - string
            - "null"
          enum: [text_extract, file, document, null]
          description: text_extract for stored extracted text; file for raw text-like files such as CSV; document for Document extracted/markdown content.
        text:
          type:
            - string
            - "null"
          description: Present when status is available. Long text is paginated with startOffset and maxChars.
        startOffset:
          type: integer
        maxChars:
          type: integer
        nextOffset:
          type:
            - integer
            - "null"
          description: Pass as startOffset to continue reading when truncated is true.
        truncated:
          type: boolean
        totalLength:
          type: integer
        message:
          type: string
    ActionMeta:
      type: object
      properties:
        availableActions:
          type: array
          description: Structured hints for useful follow-up actions. These are advisory; clients should still rely on tool schemas and authorization.
          items:
            $ref: "#/components/schemas/AvailableAction"
    AvailableAction:
      type: object
      required: [name, description, arguments, idPaths]
      properties:
        name:
          type: string
          description: MCP/tool operation name.
        description:
          type: string
        arguments:
          type: object
          description: Static arguments to pass to the action.
        idPaths:
          type: array
          description: JSON paths where IDs for this action can be found in the current response.
          items:
            type: string
    AttachmentFile:
      type: object
      description: Presigned download metadata. Omitted when the attachment is redacted or URLs are excluded.
      required: [url, expiresAt]
      properties:
        url:
          type: string
          format: uri
        expiresAt:
          type: string
          format: date-time
    CountMeta:
      type: object
      required: [count]
      properties:
        count:
          type: integer
    CursorMeta:
      type: object
      required: [count]
      properties:
        count:
          type: integer
        perPage:
          type: integer
        nextCursor:
          type:
            - string
            - "null"
    ErrorEnvelope:
      type: object
      required: [errors]
      properties:
        errors:
          type: array
          items:
            type: object
            required: [status, code, title, detail]
            properties:
              status:
                type: string
              code:
                type: string
              title:
                type: string
              detail:
                type: string
              source:
                type: object
                properties:
                  pointer:
                    type: string
