# Proposed contract, version 1: endpoints, errors, paging, idempotency, security and the capability of every
# operation. Resource schemas were initialized from the data model (docs/06-data) and are kept aligned with it; input
# schemas (*Input, *Request) and views are specific to this contract. Status: proposal for review, not confirmed design.
# Conventions: docs/05-interfaces/choosing-an-interface.md. Vendor extensions: x-moonitoring-audience (who calls),
# x-moonitoring-capability (the capability checked), x-moonitoring-entitlement (the entitlement feature checked),
# x-moonitoring-requirements (the requirements of 1.requisiti-raffinati.txt the operation serves).
openapi: 3.1.0
info:
  title: Farm Entitlements API
  version: 1.0.0
  description: 'What each Organization has (an entitlement to a feature, a model or a plan, with its limits) and
    how much of a metered feature it used. Staff grant and revoke; Organization users read their own; the AI gateway
    reserves and settles usage through the gRPC service of this domain. A domain of `farm` since D2 (2026-10-30):
    every product reads its entitlements from here. A domain of `farm` since D2 (2026-10-30): every product reads
    its entitlements from here.'
servers:
- url: https://farm.moonitoring.com/entitlements/v1
  description: Prod
- url: https://stage-farm.moonitoring.com/entitlements/v1
  description: Stage, through IAP
security:
- bearer: []
tags:
- name: Features
  description: The features systems publish
- name: Entitlements
  description: What an Organization has
- name: Usage
  description: Metered use per period
paths:
  /features:
    get:
      summary: List features
      operationId: listFeatures
      description: The union of the features every system published in its capability catalog.
      tags:
      - Features
      parameters:
      - name: system
        in: query
        required: false
        schema:
          type: string
        description: Only features of this system.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Feature'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '68.3'
  /organizations/{organization_id}/entitlements:
    get:
      summary: List an Organization's entitlements
      operationId: listOrganizationEntitlements
      description: Members read their own Organization's entitlements, so the Organization Dashboard knows which
        features to show; staff read any Organization's.
      tags:
      - Entitlements
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      - name: include_expired
        in: query
        required: false
        schema:
          type: boolean
        description: Also entitlements whose validity ended.
      - name: feature
        in: query
        required: false
        schema:
          type: string
        description: Only this feature.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Entitlement'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - null
      - farm.entitlements.manage
      x-moonitoring-requirements:
      - '68'
      - '1006.24'
    post:
      summary: Grant an entitlement
      operationId: grantEntitlement
      description: (organization, feature) must not be active already. Publishes `entitlement.granted.v1`.
      tags:
      - Entitlements
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntitlementInput'
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Entitlement'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.entitlements.manage
      x-moonitoring-requirements:
      - '68'
      - '68.5'
      - '1006.24'
  /entitlements:
    get:
      summary: List entitlements
      operationId: listEntitlements
      tags:
      - Entitlements
      parameters:
      - name: organization
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this Organization.
      - name: feature
        in: query
        required: false
        schema:
          type: string
        description: Only this feature.
      - name: active
        in: query
        required: false
        schema:
          type: boolean
        description: Only entitlements valid now.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Entitlement'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.entitlements.manage
      x-moonitoring-requirements:
      - '68'
      - '1006.24'
  /entitlements/{entitlement_id}:
    get:
      summary: Read an entitlement
      operationId: getEntitlement
      tags:
      - Entitlements
      parameters:
      - name: entitlement_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Entitlement'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - null
      - farm.entitlements.manage
      x-moonitoring-requirements:
      - '68'
    patch:
      summary: Change an entitlement
      operationId: updateEntitlement
      description: Publishes `entitlement.changed.v1`.
      tags:
      - Entitlements
      parameters:
      - name: entitlement_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntitlementUpdate'
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Entitlement'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.entitlements.manage
      x-moonitoring-requirements:
      - '68.4'
      - '68.6'
      - '68.7'
      - '1006.24'
  /entitlements/{entitlement_id}/revoke:
    post:
      summary: Revoke an entitlement
      operationId: revokeEntitlement
      description: Closes the validity now; the row is kept. Publishes `entitlement.revoked.v1`.
      tags:
      - Entitlements
      parameters:
      - name: entitlement_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Entitlement'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.entitlements.manage
      x-moonitoring-requirements:
      - '68.4'
      - '1006.24'
  /organizations/{organization_id}/usage:
    get:
      summary: Read an Organization's usage
      operationId: listOrganizationUsage
      tags:
      - Usage
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      - name: feature
        in: query
        required: false
        schema:
          type: string
        description: Only this feature.
      - name: period
        in: query
        required: false
        schema:
          type: string
        description: Only this period, e.g. 2026-10; default the current one.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/UsageSummary'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - null
      - farm.entitlements.manage
      x-moonitoring-requirements:
      - '69'
      - '68.7'
  /moonitoring-users/{user_id}/usage:
    get:
      summary: Read a staff member's usage
      operationId: listStaffUsage
      description: Staff use of an agent or tool is metered per Moonitoring user.
      tags:
      - Usage
      parameters:
      - name: user_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: feature
        in: query
        required: false
        schema:
          type: string
      - name: period
        in: query
        required: false
        schema:
          type: string
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/UsageSummary'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.entitlements.manage
      x-moonitoring-requirements:
      - '69.9'
components:
  schemas:
    Entitlement:
      description: 'What an Organization has bought or been granted: a model, a product feature, a plan. Roles say
        what a user may do; entitlements say what their Organization has, so a product checks both before it serves
        a model or a feature. Entitlements belong to the Entitlements domain of the `farm` system (decided 2026-10-30)
        and are the same for every Moonitoring product.'
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        organization:
          type: string
          format: uuid
          description: ID of the Organization.
        feature:
          type: string
          description: 'Named like a capability, &lt;project>.&lt;feature>: e.g. `farm.models.mastitis`, `moochat.chat`.
            Each project publishes the features it offers, with its capability catalog.'
        validity:
          allOf:
          - $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
          description: '`valid_until` = open: it does not expire. Billing and contracts decide the dates; they are
            not modelled here.'
        granted_by:
          type: string
          format: uuid
          description: ID of the MoonitoringUser.
        system_version:
          $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SystemVersion
        limits:
          type: string
          description: 'How much of the feature the Organization may use, e.g. for `moochat.chat`: tokens per month,
            requests per minute, concurrent streams, model tier, what happens at 100% (block, degrade, bill). Empty:
            no limit. A plan is a set of entitlements with limits; changing a plan changes rows, not code.'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - organization
      - feature
      - validity
      - granted_by
      - system_version
      - created_at
      - updated_at
    FeatureUsage:
      description: How much of a metered feature an Organization has used in a period, for example MooChat tokens
        this month. The AI gateway updates it on every model call and checks it against the limits of the Organization's
        entitlement in the same statement, so a limit can never be overrun by two concurrent calls.
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        organization:
          type: string
          format: uuid
          description: ID of the Organization.
        feature:
          type: string
          description: The entitlement's feature, e.g. `moochat.chat`.
        period:
          type: string
          description: e.g. 2026-10, in the Organization's timezone.
        requests:
          type: integer
          default: 0
        input_tokens:
          type: integer
          default: 0
        output_tokens:
          type: integer
          default: 0
        cost_micros:
          type: integer
          default: 0
          description: Provider cost in millionths of a euro, for attribution; the limit is on tokens, not on money.
        moonitoring_user:
          type: string
          format: uuid
          description: ID of the MoonitoringUser. Staff use of an agent or tool is metered per Moonitoring user;
            exactly one of organization and `moonitoring_user` is set.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - feature
      - period
      - requests
      - input_tokens
      - output_tokens
      - cost_micros
      - created_at
      - updated_at
    Feature:
      type: object
      description: 'A feature a system publishes in its catalog, named like a capability: <system>.<feature>.'
      properties:
        name:
          type: string
          description: e.g. farm.models.mastitis, moochat.chat.
        system:
          type: string
        description:
          type: string
        metered:
          type: boolean
          description: Whether FeatureUsage is kept for it.
        limit_keys:
          type: array
          items:
            type: string
          description: The keys an entitlement's `limits` may set for it, e.g. tokens_per_month, requests_per_minute.
      required:
      - name
      - system
      - metered
    EntitlementInput:
      type: object
      description: Grants a feature to an Organization. `validity` with an open end does not expire; `limits` is
        a JSON object whose keys the feature publishes.
      properties:
        feature:
          type: string
          description: 'Named like a capability, &lt;project>.&lt;feature>: e.g. `farm.models.mastitis`, `moochat.chat`.
            Each project publishes the features it offers, with its capability catalog.'
        validity:
          allOf:
          - $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
          description: '`valid_until` = open: it does not expire. Billing and contracts decide the dates; they are
            not modelled here.'
        limits:
          type: string
          description: 'How much of the feature the Organization may use, e.g. for `moochat.chat`: tokens per month,
            requests per minute, concurrent streams, model tier, what happens at 100% (block, degrade, bill). Empty:
            no limit. A plan is a set of entitlements with limits; changing a plan changes rows, not code.'
      required:
      - feature
      - validity
      additionalProperties: false
    EntitlementUpdate:
      type: object
      description: Changes the validity or the limits of an entitlement; the previous image is kept.
      properties:
        validity:
          $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
        limits:
          type: string
          description: JSON object, as in Entitlement.limits.
    UsageSummary:
      type: object
      description: The usage of one feature in one period, next to the limit that applies.
      properties:
        usage:
          $ref: '#/components/schemas/FeatureUsage'
        limits:
          type: string
          description: The entitlement's limits in force, JSON.
        exhausted:
          type: boolean
          description: True when a limit of the period is reached.
      required:
      - usage
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'The short-lived token signed by `access`: an Organization user token, a staff token or a client
        token, as the operation''s audience says.'
