Opply Recommendations API

The Recommendations API from Opply — 11 operation(s) for recommendations.

OpenAPI Specification

opply-recommendations-api-openapi.yml Raw ↑
openapi: 3.0.3
info:
  title: Opply Activity Feed Recommendations API
  version: 0.0.0
tags:
- name: Recommendations
paths:
  /api/v1/recommendations/:
    get:
      operationId: api_v1_recommendations_list
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      summary: List recommendations for the current buyer
      parameters:
      - name: cursor
        required: false
        in: query
        description: The pagination cursor value.
        schema:
          type: string
      - in: query
        name: state
        schema:
          type: string
        description: Filter by lifecycle state (`active`, `snoozed`, `dismissed`, `superseded`, `acted`, `stale`). Defaults to `active`.
      tags:
      - Recommendations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedRecommendationReadList'
          description: ''
  /api/v1/recommendations/{uuid}/:
    get:
      operationId: api_v1_recommendations_retrieve
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      summary: Fetch a single recommendation by UUID (any lifecycle state)
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationRead'
          description: ''
  /api/v1/recommendations/{uuid}/act/:
    post:
      operationId: api_v1_recommendations_act_create
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationActResponse'
          description: ''
  /api/v1/recommendations/{uuid}/dismiss/:
    post:
      operationId: api_v1_recommendations_dismiss_create
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationRead'
          description: ''
  /api/v1/recommendations/{uuid}/mark-stale/:
    post:
      operationId: api_v1_recommendations_mark_stale_create
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MarkStaleRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/MarkStaleRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/MarkStaleRequest'
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationRead'
          description: ''
  /api/v1/recommendations/{uuid}/restore/:
    post:
      operationId: api_v1_recommendations_restore_create
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationRead'
          description: ''
  /api/v1/recommendations/{uuid}/snooze/:
    post:
      operationId: api_v1_recommendations_snooze_create
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SnoozeRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/SnoozeRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/SnoozeRequest'
        required: true
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationRead'
          description: ''
  /api/v1/recommendations/{uuid}/supersede/:
    post:
      operationId: api_v1_recommendations_supersede_create
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      parameters:
      - in: path
        name: uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Recommendations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupersedeRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/SupersedeRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/SupersedeRequest'
        required: true
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationRead'
          description: ''
  /api/v1/recommendations/recent-actions/:
    get:
      operationId: api_v1_recommendations_recent_actions_retrieve
      description: 'Buyer-scoped recommendations (the agent-curated nudge feed).


        Scoped to ``audience=buyer`` recommendations whose subject is the

        authenticated user''s company. ``list`` defaults to ``state=active`` (the

        feed); pass ``?state=`` for the History view. The CTA/lifecycle actions

        (dismiss/snooze/act/restore) and the agent write-path actions

        (create/supersede/mark_stale, behind the MCP tools) all live here.


        Impact is never stored on a recommendation — it is read from the cited

        insight(s) at render time (see ``RecommendationReadSerializer``).'
      summary: Recent recipient actions (for the agent's cooldown check)
      parameters:
      - in: query
        name: days
        schema:
          type: integer
        description: Look-back window in days (default 30).
      tags:
      - Recommendations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          description: No response body
  /api/v1/recommendations/seed-recommendation/:
    post:
      operationId: api_v1_recommendations_seed_recommendation_create
      description: '``POST /api/v1/recommendations/seed-recommendation/`` — staff-only.


        Body ``{company_uuid?}``. Creates a ready-made ``supplier_concentration``

        recommendation directly — **no agent run** — for that buyer (defaulting to

        the caller''s own company). Use when the recommender agent declines to create

        one and you just want a fresh nudge in the feed to exercise the CTA. Returns

        the recommendation uuid. Targeting another company requires staff access.'
      tags:
      - Recommendations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/_DebugCompanyTargetRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/_DebugCompanyTargetRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/_DebugCompanyTargetRequest'
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SeedRecommendationResponse'
          description: ''
  /api/v1/recommendations/trigger-recommendations/:
    post:
      operationId: api_v1_recommendations_trigger_recommendations_create
      description: '``POST /api/v1/recommendations/trigger-recommendations/`` — staff-only.


        Body ``{company_uuid?}``. Creates the recommender-run Task for that buyer

        (defaulting to the caller''s own company); the existing dispatch path then

        runs the agent. Returns the Task + AgentTaskRun uuids so the debug UI can

        link to them. Targeting a company other than your own requires staff access.'
      tags:
      - Recommendations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/_DebugCompanyTargetRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/_DebugCompanyTargetRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/_DebugCompanyTargetRequest'
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerRecommenderRunResponse'
          description: ''
components:
  schemas:
    SeedRecommendationResponse:
      type: object
      properties:
        recommendation_uuid:
          type: string
          format: uuid
      required:
      - recommendation_uuid
    TriggerRecommenderRunResponse:
      type: object
      properties:
        task_uuid:
          type: string
          format: uuid
        run_uuid:
          type: string
          format: uuid
          nullable: true
      required:
      - run_uuid
      - task_uuid
    _DebugCompanyTargetRequest:
      type: object
      properties:
        company_uuid:
          type: string
          format: uuid
    RecommendationStateEnum:
      enum:
      - active
      - superseded
      - dismissed
      - snoozed
      - acted
      - stale
      type: string
      description: '* `active` - Active

        * `superseded` - Superseded

        * `dismissed` - Dismissed

        * `snoozed` - Snoozed

        * `acted` - Acted

        * `stale` - Stale'
    SupersedeRequest:
      type: object
      properties:
        new_recommendation_uuid:
          type: string
          format: uuid
      required:
      - new_recommendation_uuid
    RecommendationRead:
      type: object
      description: 'Buyer-facing read view of a recommendation, with its cited insights.


        ``cited_insights`` is ordered primary-first so the FE can read

        ``cited_insights[0]`` as the headline driver. Impact lives only on the

        cited insights — there is deliberately no impact field on the recommendation.'
      properties:
        uuid:
          type: string
          format: uuid
          readOnly: true
        kind:
          type: string
          description: Recommendation type code (e.g. `supplier_concentration`). The catalog of kinds + their templates lives in `@opply/agent-protocol`; this column stores whatever the agent emits.
          maxLength: 64
        audience:
          allOf:
          - $ref: '#/components/schemas/RecommendationAudienceEnum'
          description: 'Who should see/act on this recommendation. Agent-supplied (defaulted from the kind). The buyer feed filters to `buyer`; other audiences are served by their own (future) surfaces.


            * `buyer` - Buyer

            * `mor` - Merchant of Record

            * `supplier` - Supplier'
        title:
          type: string
          description: Agent-phrased headline.
          maxLength: 512
        description:
          type: string
          description: Agent-phrased body. No $ figure is stored here.
        evidence:
          type: string
          description: Agent-authored **markdown** for the card's "Why am I seeing this?" section (freeform bullets with ballpark figures the agent gathered from deterministic reads over ingested data). Rendered as sanitized markdown FE-side — never raw HTML. May carry ballpark numbers, but never the headline impact $ (that lives on the cited insight).
        visual:
          oneOf:
          - type: object
            title: ShareVisual
            properties:
              type:
                type: string
                enum:
                - share
              a:
                type: number
                minimum: 0
                maximum: 100
              left:
                type: string
              right:
                type: string
            required:
            - type
            - a
            - left
            - right
          - type: object
            title: PairVisual
            properties:
              type:
                type: string
                enum:
                - pair
              a:
                type: object
                additionalProperties: true
              b:
                type: object
                additionalProperties: true
            required:
            - type
            - a
            - b
          - type: object
            title: NoVisual
            description: Empty object when the kind has no visual (visualType 'none').
            additionalProperties: false
          readOnly: true
        severity:
          allOf:
          - $ref: '#/components/schemas/RecommendationSeverityEnum'
          description: 'Ranking weight 1..4 (agent-assigned, defaulted from the kind).


            * `1` - Low

            * `2` - Medium

            * `3` - High

            * `4` - Critical'
          minimum: 0
          maximum: 32767
        cta_label:
          type: string
          description: Optional CTA button label (agent-phrased).
          maxLength: 64
        cta_target:
          type: string
          description: Optional internal route the CTA navigates to (`/`-prefixed; validated FE-side).
          maxLength: 512
        state:
          allOf:
          - $ref: '#/components/schemas/RecommendationStateEnum'
          description: 'Per-recipient interaction lifecycle. See RecommendationState.


            * `active` - Active

            * `superseded` - Superseded

            * `dismissed` - Dismissed

            * `snoozed` - Snoozed

            * `acted` - Acted

            * `stale` - Stale'
        snoozed_until:
          type: string
          format: date-time
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When set and in the past, the expiry sweep flips the row to `stale`.
        created:
          type: string
          format: date-time
          readOnly: true
        cited_insights:
          type: array
          items:
            type: object
            additionalProperties: {}
          readOnly: true
      required:
      - cited_insights
      - created
      - description
      - kind
      - title
      - uuid
      - visual
    PaginatedRecommendationReadList:
      type: object
      required:
      - results
      properties:
        next:
          type: string
          nullable: true
          format: uri
          example: http://api.example.org/accounts/?cursor=cD00ODY%3D"
        previous:
          type: string
          nullable: true
          format: uri
          example: http://api.example.org/accounts/?cursor=cj0xJnA9NDg3
        results:
          type: array
          items:
            $ref: '#/components/schemas/RecommendationRead'
    MarkStaleRequest:
      type: object
      properties:
        reason:
          type: string
          default: ''
          maxLength: 512
    RecommendationSeverityEnum:
      enum:
      - 1
      - 2
      - 3
      - 4
      type: integer
      description: '* `1` - Low

        * `2` - Medium

        * `3` - High

        * `4` - Critical'
    SnoozeRequest:
      type: object
      properties:
        until:
          type: string
          format: date-time
      required:
      - until
    RecommendationActResponse:
      type: object
      description: 'The ``act`` response: the read view plus the uuid of the follow-up task the

        CTA created (``null`` for kinds with no task action), so the FE can show a

        "task created" toast that deep-links to it instead of navigating away.'
      properties:
        uuid:
          type: string
          format: uuid
          readOnly: true
        kind:
          type: string
          description: Recommendation type code (e.g. `supplier_concentration`). The catalog of kinds + their templates lives in `@opply/agent-protocol`; this column stores whatever the agent emits.
          maxLength: 64
        audience:
          allOf:
          - $ref: '#/components/schemas/RecommendationAudienceEnum'
          description: 'Who should see/act on this recommendation. Agent-supplied (defaulted from the kind). The buyer feed filters to `buyer`; other audiences are served by their own (future) surfaces.


            * `buyer` - Buyer

            * `mor` - Merchant of Record

            * `supplier` - Supplier'
        title:
          type: string
          description: Agent-phrased headline.
          maxLength: 512
        description:
          type: string
          description: Agent-phrased body. No $ figure is stored here.
        evidence:
          type: string
          description: Agent-authored **markdown** for the card's "Why am I seeing this?" section (freeform bullets with ballpark figures the agent gathered from deterministic reads over ingested data). Rendered as sanitized markdown FE-side — never raw HTML. May carry ballpark numbers, but never the headline impact $ (that lives on the cited insight).
        visual:
          oneOf:
          - type: object
            title: ShareVisual
            properties:
              type:
                type: string
                enum:
                - share
              a:
                type: number
                minimum: 0
                maximum: 100
              left:
                type: string
              right:
                type: string
            required:
            - type
            - a
            - left
            - right
          - type: object
            title: PairVisual
            properties:
              type:
                type: string
                enum:
                - pair
              a:
                type: object
                additionalProperties: true
              b:
                type: object
                additionalProperties: true
            required:
            - type
            - a
            - b
          - type: object
            title: NoVisual
            description: Empty object when the kind has no visual (visualType 'none').
            additionalProperties: false
          readOnly: true
        severity:
          allOf:
          - $ref: '#/components/schemas/RecommendationSeverityEnum'
          description: 'Ranking weight 1..4 (agent-assigned, defaulted from the kind).


            * `1` - Low

            * `2` - Medium

            * `3` - High

            * `4` - Critical'
          minimum: 0
          maximum: 32767
        cta_label:
          type: string
          description: Optional CTA button label (agent-phrased).
          maxLength: 64
        cta_target:
          type: string
          description: Optional internal route the CTA navigates to (`/`-prefixed; validated FE-side).
          maxLength: 512
        state:
          allOf:
          - $ref: '#/components/schemas/RecommendationStateEnum'
          description: 'Per-recipient interaction lifecycle. See RecommendationState.


            * `active` - Active

            * `superseded` - Superseded

            * `dismissed` - Dismissed

            * `snoozed` - Snoozed

            * `acted` - Acted

            * `stale` - Stale'
        snoozed_until:
          type: string
          format: date-time
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When set and in the past, the expiry sweep flips the row to `stale`.
        created:
          type: string
          format: date-time
          readOnly: true
        cited_insights:
          type: array
          items:
            type: object
            additionalProperties: {}
          readOnly: true
        created_task_uuid:
          type: string
          format: uuid
          nullable: true
          readOnly: true
      required:
      - cited_insights
      - created
      - created_task_uuid
      - description
      - kind
      - title
      - uuid
      - visual
    RecommendationAudienceEnum:
      enum:
      - buyer
      - mor
      - supplier
      type: string
      description: '* `buyer` - Buyer

        * `mor` - Merchant of Record

        * `supplier` - Supplier'
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: sessionid
    tokenAuth:
      type: apiKey
      in: header
      name: Authorization
      description: Token-based authentication with required prefix "Token"