# 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: Access Tokens API
  version: 1.0.0
  description: 'Sign-in and tokens. `access` exchanges an Identity Platform sign-in, an Identity-Aware Proxy identity
    or a service-account assertion for the short-lived token every other system verifies with the keys published
    here. The token carries the user, the active Organization and its cell only: rights are read by each system
    from its own copy of memberships and entitlements. A Person''s token also carries `auth_time`, the time of their
    last real sign-in at Identity Platform, kept unchanged along the refresh chain: sensitive writes require it to
    be at most five minutes old and otherwise answer 403 with the problem type reauthentication-required; reads
    never do (decided 2026-11-11).'
servers:
- url: https://access.moonitoring.com/tokens/v1
  description: Prod
- url: https://stage-access.moonitoring.com/tokens/v1
  description: Stage, through IAP
security:
- bearer: []
tags:
- name: Keys
  description: Public keys
- name: Tokens
  description: Issuing and revoking tokens
paths:
  /jwks:
    get:
      summary: Public keys
      operationId: getJwks
      description: The keys every system uses to verify tokens. Keys rotate; a system caches the set and refreshes
        it when it meets an unknown key id.
      tags:
      - Keys
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Jwks'
        '429':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/TooManyRequests
      security: []
      x-moonitoring-audience: public
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '24'
      - '26'
      - '1'
  /tokens:
    post:
      summary: Issue a token
      operationId: issueToken
      description: Sign-in for every kind of caller. A disabled user or service account, a revoked membership or
        a retired sync client is refused.
      tags:
      - Tokens
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
        '429':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/TooManyRequests
      security: []
      x-moonitoring-audience: public
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '1'
      - 1.*.2
      - '26'
      - 26.*.2
      - 26.*.2.5
      - '140'
      - '67'
  /tokens/revoke:
    post:
      summary: Revoke a refresh token
      operationId: revokeToken
      tags:
      - Tokens
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RevokeRequest'
      responses:
        '204':
          description: Done.
        '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
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '1'
      - '26'
      - '140.10'
      - '140.11'
  /authorizations:
    post:
      summary: Authorize an app
      operationId: authorizeNativeApp
      description: 'The Account app calls it for a signed-in person on `/authorize`: `access` issues a one-time code
        for the app, bound to its client id, its redirect and its PKCE challenge, and the Account app sends the browser
        to the redirect with the code. Only registered apps can ask, each with its own redirects (decided 2026-11-11):
        the native apps `moonitoring-farm-sync` and `moonitoring-installer` with `http://127.0.0.1:<port>/callback`,
        after the person pressed Allow; the web app `moonitoring-dashboard` with the exact `/auth/callback` of the
        environment, at once, since our own web apps skip the Allow page. Any other client or redirect answers 422, and
        the challenge must be S256. For a native app the caller''s sign-in must be at most five minutes old, otherwise
        403 with the problem type reauthentication-required. The code lasts 60 seconds and works once. It is naturally
        safe to retry: a new call gives a new code and the old one expires unused. The operation id keeps its first
        name.'
      tags:
      - Tokens
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthorizationRequest'
      responses:
        '201':
          description: The code to send back to the app.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorizationResponse'
        '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
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
        '429':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/TooManyRequests
      x-moonitoring-audience: organization
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '1'
      - '26'
  /registrations:
    post:
      summary: Register as a new person
      operationId: registerPerson
      description: 'The first sign-in of someone with no Person yet. The token exchange of an unknown Identity Platform
        user answers 403 with the problem type registration-required; the app asks for name, surname and telephone,
        prefilled from the sign-in where it can, and sends them here with the same Identity Platform ID token. The
        Person is created with the verified e-mail of that token and the tokens are issued at once. Names are never
        guessed from a display name. It is naturally idempotent, so it takes no Idempotency-Key: a retry with an ID token
        of the same Identity Platform user signs that Person in with 200; an e-mail that belongs to another user answers
        409.'
      tags:
      - Tokens
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegistrationRequest'
      responses:
        '201':
          description: The Person exists and is signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '200':
          description: The Person already existed for this Identity Platform user and is signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '1'
      - 1.*.1
      - 1.*.1.5
      - '140'
components:
  schemas:
    Jwks:
      type: object
      description: 'The public keys of `access`, as a JSON Web Key Set. Tokens are signed with ES256, ECDSA on
        P-256 with SHA-256, by a Cloud KMS key in Cloud HSM (decided 2026-11-04, since KMS offers Ed25519 only in
        software), so every key is `kty` EC, `crv` P-256, `alg` ES256, `use` sig, with the coordinates `x` and `y`.'
      properties:
        keys:
          type: array
          items:
            type: object
            properties:
              kid:
                type: string
              kty:
                type: string
              crv:
                type: string
              x:
                type: string
              y:
                type: string
              alg:
                type: string
              use:
                type: string
            required:
            - kid
            - kty
      required:
      - keys
    TokenRequest:
      type: object
      description: 'A token request, in the shape of an OAuth 2.0 token request. The grant says who is signing in:
        `urn:moonitoring:identity-platform` an Organization user with an Identity Platform ID token; `urn:moonitoring:iap`
        a staff member whose request carries the Identity-Aware Proxy assertion, the enabled MoonitoringPerson with
        its e-mail or, for a Google service account, the enabled MoonitoringServiceAccount linked to it (decided
        2026-11-13), with no key and no refresh token; `client_credentials` a service
        account or farm sync client with a signed assertion (`private_key_jwt`, RFC 7523) or a client secret;
        `authorization_code` a person signing in to an app through the Account app, a native app from the browser
        or a web app from its own server, with the code of `POST /authorizations` and its PKCE verifier (RFC 8252,
        RFC 7636); `refresh_token` a renewal, which may also switch the active Organization. Only the Account app
        uses the identity-platform grant for a person (decided 2026-11-11); every other app signs in with a code.'
      properties:
        grant_type:
          type: string
          enum:
          - urn:moonitoring:identity-platform
          - urn:moonitoring:iap
          - client_credentials
          - authorization_code
          - refresh_token
        id_token:
          type: string
          description: The Identity Platform ID token (identity-platform grant).
        client_assertion_type:
          type: string
          enum:
          - urn:ietf:params:oauth:client-assertion-type:jwt-bearer
        client_assertion:
          type: string
          description: A JWT signed with the service account's registered key (client_credentials).
        client_id:
          type: string
          description: The service account's user ID (client_credentials with a client secret), or the app's client
            id (authorization_code), `moonitoring-farm-sync`, `moonitoring-installer` or `moonitoring-dashboard`.
        client_secret:
          type: string
        code:
          type: string
          description: The one-time code of `POST /authorizations` (authorization_code).
        code_verifier:
          type: string
          description: The PKCE verifier whose S256 challenge the code is bound to (authorization_code), 43 to 128
            characters.
        redirect_uri:
          type: string
          description: The same redirect the code was issued for (authorization_code).
        refresh_token:
          type: string
        organization_id:
          type: string
          format: uuid
          description: The Organization to act as, which must be one of the person's own, through a direct
            membership or a user group, never one reached only through a delegation (decided 2026-11-11). Without it, a user with one
            membership gets it and a user with more gets a token with no active Organization (decided 2026-11-10),
            which serves `GET /organizations/v1/me` and a refresh that chooses one; a token carries at most one.
      required:
      - grant_type
      additionalProperties: false
    AuthorizationRequest:
      type: object
      description: What the Account app sends when the person allows a native app, or at once for one of our web
        apps. Everything but the person comes from the app's `/authorize` link; the person comes from the caller's
        token.
      properties:
        client_id:
          type: string
          enum:
          - moonitoring-farm-sync
          - moonitoring-installer
          - moonitoring-dashboard
          description: The registered app, the farm sync client, the installer of `releases` or the Organization
            Dashboard (decided 2026-11-11; MooGrant joins later as a web app).
        redirect_uri:
          type: string
          description: 'For a native app `http://127.0.0.1:<port>/callback`, nothing else. For `moonitoring-dashboard`
            exactly the redirect of the environment: `https://dash.moonitoring.com/auth/callback` in prod,
            `https://stage-dash.moonitoring.com/auth/callback` in stage, and `http://localhost:9200/auth/callback`
            only with `MOONITORING_ENVIRONMENT=local`.'
        code_challenge:
          type: string
          description: The S256 challenge of the app's PKCE verifier, base64url without padding.
        code_challenge_method:
          type: string
          enum:
          - S256
      required:
      - client_id
      - redirect_uri
      - code_challenge
      - code_challenge_method
      additionalProperties: false
    AuthorizationResponse:
      type: object
      description: The one-time code; the Account app sends the browser to `redirect_uri` with it and the app's
        `state` in the query.
      properties:
        code:
          type: string
        expires_in:
          type: integer
          description: Seconds until the code expires, 60.
      required:
      - code
      - expires_in
    RegistrationRequest:
      type: object
      description: What a new person gives at their first sign-in. The e-mail and the Identity Platform user come from the
        ID token, never from the body.
      properties:
        id_token:
          type: string
          description: The Identity Platform ID token of the sign-in; its e-mail must be verified.
        name:
          type: string
        surname:
          type: string
        telephone_number:
          type: object
          description: The telephone, as a nation and the number without its prefix.
          properties:
            nation:
              type: string
              format: uuid
              description: ID of the Nation whose prefix the number uses.
            number:
              type: string
          required:
          - nation
          - number
        locale:
          type: string
          enum:
          - it
          - en
          default: it
      required:
      - id_token
      - name
      - surname
      - telephone_number
      additionalProperties: false
    TokenResponse:
      type: object
      description: 'The issued token. Access tokens are short-lived: five minutes for staff, fifteen for Organization
        users and for service accounts such as the farm sync client (D3). Refresh tokens last 30 days, slide on
        use, are rotated on every use and bound to the caller; a reuse of a rotated refresh token revokes the whole
        chain (D15).'
      properties:
        access_token:
          type: string
          description: 'A JWT signed by access with ES256, header `kid` naming the key of the JWKS. Claims: `iss` the
            host of the environment, `sub` the user, `aud` the kind of caller (`organization`, `staff` or `client`),
            `org` the active Organization and `cell` its cell (absent for staff), `exp`, `iat` and `jti`. **No
            capabilities**, for any kind of caller (decided 2026-11-04): each system reads rights from its own copy
            of memberships and staff roles. A farm sync client is a ServiceAccount of its Organization and gets a
            `client` token (decided 2026-10-30, D3).'
        token_type:
          type: string
          enum:
          - Bearer
        expires_in:
          type: integer
          description: Seconds until the access token expires.
        refresh_token:
          type: string
          description: 'Absent for client_credentials. Rotated on every use; 30 days, sliding. For authorization_code
            it is bound to the app and never reaches a page: a native app keeps it only in the operating system''s
            credential store, a web app only in its server''s session.'
        active_organization:
          type: string
          format: uuid
      required:
      - access_token
      - token_type
      - expires_in
    RevokeRequest:
      type: object
      description: Revokes a refresh token, for example at sign-out.
      properties:
        refresh_token:
          type: string
      required:
      - refresh_token
  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.'
