openapi: "3.1.0"

info:
  title: Authplane Public API
  description: >
    OAuth 2.1 Authorization Server for MCP (Model Context Protocol).
    Self-hosted, single-binary authorization server with support for
    authorization code flow (PKCE), client credentials, token exchange,
    DPoP proof-of-possession, dynamic client registration, token introspection,
    token revocation, OIDC upstream federation, and upstream provider
    connections (the unified Resource model with `mint` / `broker` backends).
  version: "0.1.2"
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0
  contact:
    name: Authplane
    url: https://docs.authplane.io

servers:
  - url: http://localhost:9000
    description: Local development server

tags:
  - name: Health
    description: Health and readiness probes
  - name: Discovery
    description: OAuth/OIDC discovery and key material (RFC 8414, RFC 9728, RFC 7517)
  - name: OAuth
    description: OAuth 2.1 core endpoints (authorize, token, register, introspect, revoke)
  - name: Login
    description: User authentication (HTML responses)
  - name: Consent
    description: User consent for OAuth authorization (HTML responses)
  - name: OIDC
    description: OIDC upstream federation (redirect-based)
  - name: Connect
    description: >
      Upstream provider connections — start a Connect flow, handle the
      callback, and list/disconnect a user's broker-backed providers.
      Replaces the legacy "Token Vault" surface.

security: []

paths:
  # ---------------------------------------------------------------------------
  # Health & Readiness
  # ---------------------------------------------------------------------------
  /health:
    get:
      operationId: getHealth
      summary: Health check
      description: >
        Returns the health status of the server and its backing database.
        Returns 200 when healthy, 503 when degraded.
      tags:
        - Health
      responses:
        "200":
          description: Server is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ok
                time: "2026-03-12T10:00:00Z"
                db: ok
        "503":
          description: Server is degraded (database unreachable)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: degraded
                time: "2026-03-12T10:00:00Z"
                db: error

  /ready:
    get:
      operationId: getReady
      summary: Readiness probe
      description: >
        Returns 200 when the server is ready to accept traffic (database reachable).
        Returns 503 when not ready.
      tags:
        - Health
      responses:
        "200":
          description: Server is ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ready
                time: "2026-03-12T10:00:00Z"
                db: ok
        "503":
          description: Server is not ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: not_ready
                time: "2026-03-12T10:00:00Z"
                db: error

  # ---------------------------------------------------------------------------
  # Discovery
  # ---------------------------------------------------------------------------
  /.well-known/oauth-authorization-server:
    get:
      operationId: getASMetadata
      summary: Authorization Server Metadata (RFC 8414)
      description: >
        Returns the OAuth 2.0 Authorization Server Metadata document.
        Response is cached with `Cache-Control: public, max-age=3600`.
      tags:
        - Discovery
      responses:
        "200":
          description: Authorization Server Metadata
          headers:
            Cache-Control:
              schema:
                type: string
                example: "public, max-age=3600"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ASMetadata"

  /.well-known/openid-configuration:
    get:
      operationId: getOIDCDiscovery
      summary: OIDC Discovery (OpenID Connect Discovery 1.0)
      description: >
        Alias for the Authorization Server Metadata endpoint. Returns the same
        response as `/.well-known/oauth-authorization-server`. Provided for
        compatibility with OIDC-based clients and libraries that look up
        configuration at this well-known path.
        Response is cached with `Cache-Control: public, max-age=3600`.
      tags:
        - Discovery
      responses:
        "200":
          description: Authorization Server Metadata
          headers:
            Cache-Control:
              schema:
                type: string
                example: "public, max-age=3600"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ASMetadata"

  # NOTE on `/.well-known/oauth-protected-resource` (RFC 9728):
  # The Authplane AS does NOT serve PRM — it is the *Resource Server's*
  # responsibility to serve its own PRM, per RFC 9728 §3.1 and the MCP
  # 2025-11-25 authorization spec. The `PRMMetadata` schema below is
  # retained as a documented contract for MCP server implementors who
  # need to emit this metadata themselves; the path is intentionally
  # absent from this AS public-API spec.

  /.well-known/jwks.json:
    get:
      operationId: getJWKS
      summary: JSON Web Key Set (RFC 7517)
      description: >
        Returns the server's public signing keys in JWKS format.
        Used by resource servers to verify access token signatures.
        Response is cached with `Cache-Control: public, max-age=3600`.
      tags:
        - Discovery
      responses:
        "200":
          description: JWKS document
          headers:
            Cache-Control:
              schema:
                type: string
                example: "public, max-age=3600"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JWKS"
        "500":
          description: Failed to build JWKS document

  # ---------------------------------------------------------------------------
  # OAuth Core
  # ---------------------------------------------------------------------------
  /oauth/authorize:
    get:
      operationId: authorize
      summary: Authorization endpoint (RFC 6749 / OAuth 2.1)
      description: >
        Initiates the authorization code flow. If the user is not authenticated,
        redirects to the login page. If consent is required, redirects to the
        consent page. On success, redirects to the client's redirect_uri with
        an authorization code and state.


        PKCE is mandatory — `code_challenge` with method `S256` must be provided.
      tags:
        - OAuth
      parameters:
        - name: client_id
          in: query
          required: true
          description: The registered client identifier.
          schema:
            type: string
        - name: response_type
          in: query
          required: true
          description: Must be `code`.
          schema:
            type: string
            enum:
              - code
        - name: redirect_uri
          in: query
          required: true
          description: >
            Must exactly match one of the client's registered redirect URIs.
          schema:
            type: string
            format: uri
        - name: scope
          in: query
          required: false
          description: Space-separated list of requested scopes.
          schema:
            type: string
        - name: state
          in: query
          required: false
          description: Opaque value for CSRF protection. Echoed back on redirect.
          schema:
            type: string
        - name: code_challenge
          in: query
          required: true
          description: PKCE code challenge (BASE64URL(SHA256(code_verifier))).
          schema:
            type: string
        - name: code_challenge_method
          in: query
          required: true
          description: Must be `S256`. The `plain` method is rejected.
          schema:
            type: string
            enum:
              - S256
        - name: resource
          in: query
          required: false
          description: RFC 8707 resource indicator. Sets the audience of the issued access token.
          schema:
            type: string
            format: uri
      responses:
        "303":
          description: >
            Redirect to login page, consent page, or client redirect_uri with
            authorization code.
          headers:
            Location:
              description: Redirect target URL
              schema:
                type: string
                format: uri
        "400":
          description: >
            Invalid client_id, redirect_uri, or response_type. Rendered as an HTML error
            page (not a redirect) to prevent open-redirect attacks.
          content:
            text/html:
              schema:
                type: string
        "403":
          description: Client is suspended.
          content:
            text/html:
              schema:
                type: string

  /oauth/token:
    post:
      operationId: token
      summary: Token endpoint (RFC 6749)
      description: >
        Issues tokens. Supports five grant types:

        - `authorization_code` — exchanges an auth code + PKCE verifier for tokens

        - `refresh_token` — rotates a refresh token and issues new tokens

        - `client_credentials` (RFC 6749 section 4.4) — machine-to-machine token issuance

        - `urn:ietf:params:oauth:grant-type:token-exchange` (RFC 8693) — token exchange

        - `urn:ietf:params:oauth:grant-type:jwt-bearer` (RFC 7523 / MCP XAA) —
          enterprise-asserted identity via signed ID-JAG assertion;
          enabled via `xaa.enabled` and a registered `trusted_idps` row


        Client authentication is via HTTP Basic (`Authorization: Basic base64(client_id:client_secret)`)
        or form body (`client_id` + `client_secret` fields).


        When DPoP is enabled, the server includes a `DPoP-Nonce` response header on every
        response. Clients should include the `DPoP` request header with a signed proof JWT.


        Responses include `Cache-Control: no-store` and `Pragma: no-cache`.
      tags:
        - OAuth
      security:
        - basicAuth: []
        - {}
      parameters:
        - name: DPoP
          in: header
          required: false
          description: >
            DPoP proof JWT (RFC 9449). Required when the authorization was
            bound to a DPoP key. Must use ES256, RS256, or PS256 algorithm.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: "#/components/schemas/AuthorizationCodeTokenRequest"
                - $ref: "#/components/schemas/RefreshTokenRequest"
                - $ref: "#/components/schemas/ClientCredentialsTokenRequest"
                - $ref: "#/components/schemas/TokenExchangeTokenRequest"
                - $ref: "#/components/schemas/JWTBearerTokenRequest"
              discriminator:
                propertyName: grant_type
                mapping:
                  authorization_code: "#/components/schemas/AuthorizationCodeTokenRequest"
                  refresh_token: "#/components/schemas/RefreshTokenRequest"
                  client_credentials: "#/components/schemas/ClientCredentialsTokenRequest"
                  "urn:ietf:params:oauth:grant-type:token-exchange": "#/components/schemas/TokenExchangeTokenRequest"
                  "urn:ietf:params:oauth:grant-type:jwt-bearer": "#/components/schemas/JWTBearerTokenRequest"
      responses:
        "200":
          description: Token issued successfully
          headers:
            Cache-Control:
              schema:
                type: string
                example: no-store
            Pragma:
              schema:
                type: string
                example: no-cache
            DPoP-Nonce:
              description: >
                Server-issued DPoP nonce (RFC 9449 section 8). Only present when DPoP is enabled.
                Clients should include this nonce in subsequent DPoP proofs.
              schema:
                type: string
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/TokenResponse"
                  - $ref: "#/components/schemas/TokenExchangeResponse"
        "400":
          description: >
            Invalid request. OAuth error codes include `invalid_request`,
            `unsupported_grant_type`, `invalid_grant`, `invalid_scope`,
            `unauthorized_client`, `invalid_dpop_proof`, `use_dpop_nonce`.
          headers:
            DPoP-Nonce:
              description: Server-issued DPoP nonce (when DPoP is enabled).
              schema:
                type: string
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "401":
          description: Client authentication failed.
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="authserver"
            DPoP-Nonce:
              description: Server-issued DPoP nonce (when DPoP is enabled).
              schema:
                type: string
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "403":
          description: Access denied (e.g., token exchange not authorized for this client).
          headers:
            DPoP-Nonce:
              description: Server-issued DPoP nonce (when DPoP is enabled).
              schema:
                type: string
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "500":
          description: Internal server error.
          headers:
            DPoP-Nonce:
              description: Server-issued DPoP nonce (when DPoP is enabled).
              schema:
                type: string
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

  /oauth/introspect:
    post:
      operationId: introspect
      summary: Token introspection (RFC 7662)
      description: >
        Determines the active state and metadata of an OAuth 2.0 token.
        Requires client authentication via HTTP Basic or form body.
      tags:
        - OAuth
      security:
        - basicAuth: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - token
              properties:
                token:
                  type: string
                  description: The token to introspect.
                token_type_hint:
                  type: string
                  description: Hint about the token type. Optional.
                  enum:
                    - access_token
                    - refresh_token
                client_id:
                  type: string
                  description: Client ID (alternative to Basic auth).
                client_secret:
                  type: string
                  description: Client secret (alternative to Basic auth).
      responses:
        "200":
          description: Introspection result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntrospectionResponse"
        "400":
          description: Invalid request (missing token parameter).
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "401":
          description: Client authentication failed.
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="authserver"
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "500":
          description: Internal server error.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

  /oauth/revoke:
    post:
      operationId: revoke
      summary: Token revocation (RFC 7009)
      description: >
        Revokes an access or refresh token. Per RFC 7009, always returns 200 for
        valid revocation requests — unknown or already-revoked tokens are silently
        ignored. Client authentication is optional depending on client type.
      tags:
        - OAuth
      security:
        - basicAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - token
              properties:
                token:
                  type: string
                  description: The token to revoke.
                token_type_hint:
                  type: string
                  description: Hint about the token type. Optional.
                  enum:
                    - access_token
                    - refresh_token
                client_id:
                  type: string
                  description: Client ID (alternative to Basic auth).
                client_secret:
                  type: string
                  description: Client secret (alternative to Basic auth).
      responses:
        "200":
          description: >
            Token revoked (or was already invalid/unknown). No response body.
        "400":
          description: Invalid request (missing token parameter).
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "401":
          description: Client authentication failed.
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="authserver"
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

  /oauth/register:
    post:
      operationId: registerClient
      summary: Dynamic Client Registration (RFC 7591)
      description: >
        Registers a new OAuth client. Supports open registration,
        approved-redirects-only, or admin-only modes depending on server
        configuration. The response includes the issued `client_id` and
        optionally a `client_secret` for confidential clients.


        Includes Authplane extensions for agent clients (`agent`, `agent_description`).
      tags:
        - OAuth
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DCRRequest"
      responses:
        "201":
          description: Client registered successfully
          headers:
            Cache-Control:
              schema:
                type: string
                example: no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DCRResponse"
        "400":
          description: Invalid client metadata or redirect URI.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "403":
          description: Registration is disabled or client is suspended.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "500":
          description: Internal server error.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

  # ---------------------------------------------------------------------------
  # Login / Logout
  # ---------------------------------------------------------------------------
  /login:
    get:
      operationId: getLogin
      summary: Login page
      description: >
        Renders the HTML login form. If OIDC upstream federation is configured,
        shows an OIDC sign-in button. If local login is enabled, shows email/password
        fields.
      tags:
        - Login
      parameters:
        - name: redirect
          in: query
          required: false
          description: URL to redirect to after successful login.
          schema:
            type: string
      responses:
        "200":
          description: HTML login page
          content:
            text/html:
              schema:
                type: string

    post:
      operationId: postLogin
      summary: Submit login credentials
      description: >
        Authenticates the user with email and password. On success, sets a
        session cookie and redirects to the `redirect` URL. On failure, re-renders
        the login page with an error message. Includes CSRF protection via a
        hidden form token.
      tags:
        - Login
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - email
                - password
                - csrf_token
              properties:
                email:
                  type: string
                  format: email
                  description: User email address.
                password:
                  type: string
                  format: password
                  description: User password.
                csrf_token:
                  type: string
                  description: CSRF token from the login form.
                redirect:
                  type: string
                  description: URL to redirect to after successful login.
      responses:
        "303":
          description: Login successful. Redirects to the target URL.
          headers:
            Location:
              schema:
                type: string
            Set-Cookie:
              description: Session cookie.
              schema:
                type: string
        "422":
          description: Invalid credentials or CSRF token. Re-renders login page with error.
          content:
            text/html:
              schema:
                type: string

  /logout:
    post:
      operationId: postLogout
      summary: Log out
      description: >
        Clears the session cookie and redirects to the login page.
      tags:
        - Login
      responses:
        "303":
          description: Logged out. Redirects to /login.
          headers:
            Location:
              schema:
                type: string
                example: /login
            Set-Cookie:
              description: Cleared session cookie.
              schema:
                type: string

  # ---------------------------------------------------------------------------
  # Consent
  # ---------------------------------------------------------------------------
  /consent:
    get:
      operationId: getConsent
      summary: Consent page
      description: >
        Renders the HTML consent form, showing the requesting client's name,
        requested scopes, and resource. Requires an active session (redirects
        to login if not authenticated). The `session_id` query parameter
        identifies the pending authorization session.
      tags:
        - Consent
      parameters:
        - name: session_id
          in: query
          required: true
          description: The authorization session ID from the authorize endpoint.
          schema:
            type: string
      responses:
        "200":
          description: HTML consent page
          content:
            text/html:
              schema:
                type: string
        "303":
          description: Not authenticated. Redirects to login page.
          headers:
            Location:
              schema:
                type: string
        "400":
          description: Missing or invalid session_id.
          content:
            text/html:
              schema:
                type: string

    post:
      operationId: postConsent
      summary: Submit consent decision
      description: >
        Processes the user's consent decision (allow or deny). On allow, redirects
        to the client's redirect_uri with an authorization code. On deny, shows
        an access denied page. Includes CSRF protection.
      tags:
        - Consent
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - session_id
                - csrf_token
                - action
              properties:
                session_id:
                  type: string
                  description: The authorization session ID.
                csrf_token:
                  type: string
                  description: CSRF token.
                action:
                  type: string
                  enum:
                    - allow
                    - deny
                  description: The user's consent decision.
                scopes:
                  type: array
                  items:
                    type: string
                  description: >
                    Approved scope names. Sent as multiple `scopes` form values
                    (one per checked checkbox).
                remember:
                  type: string
                  enum:
                    - "on"
                  description: If `on`, persists the consent decision for future requests.
      responses:
        "303":
          description: >
            Consent granted. Redirects to the client's redirect_uri with
            authorization code and state.
          headers:
            Location:
              schema:
                type: string
                format: uri
        "200":
          description: Consent denied. Renders an access denied HTML page.
          content:
            text/html:
              schema:
                type: string
        "400":
          description: Invalid form data or expired session.
          content:
            text/html:
              schema:
                type: string
        "401":
          description: Not authenticated.
          content:
            text/html:
              schema:
                type: string
        "403":
          description: CSRF validation failed.
          content:
            text/html:
              schema:
                type: string

  # ---------------------------------------------------------------------------
  # OIDC Federation
  # ---------------------------------------------------------------------------
  /oidc/start:
    get:
      operationId: oidcStart
      summary: Start OIDC upstream login
      description: >
        Initiates the OIDC federation flow by redirecting the user to the
        upstream identity provider. Sets a state cookie for CSRF protection
        and encodes a PKCE challenge, nonce, and redirect target in the state
        parameter.
      tags:
        - OIDC
      parameters:
        - name: redirect
          in: query
          required: false
          description: URL to redirect to after successful OIDC authentication.
          schema:
            type: string
      responses:
        "302":
          description: Redirects to the upstream OIDC provider's authorization endpoint.
          headers:
            Location:
              description: Upstream IdP authorization URL with PKCE, state, and nonce.
              schema:
                type: string
                format: uri
            Set-Cookie:
              description: >
                OIDC state cookie (`authserver_oidc_state`) for CSRF protection. HttpOnly, 10-minute TTL.
              schema:
                type: string

  /oidc/callback:
    get:
      operationId: oidcCallback
      summary: OIDC callback
      description: >
        Handles the callback from the upstream OIDC provider. Validates the
        state parameter (signature + browser cookie binding + timestamp), exchanges
        the authorization code for tokens, authenticates or creates the user,
        sets a session cookie, and redirects to the original target URL.
      tags:
        - OIDC
      parameters:
        - name: code
          in: query
          required: true
          description: Authorization code from the upstream IdP.
          schema:
            type: string
        - name: state
          in: query
          required: true
          description: State parameter (base64url-encoded signed payload).
          schema:
            type: string
        - name: error
          in: query
          required: false
          description: Error code from the upstream IdP.
          schema:
            type: string
        - name: error_description
          in: query
          required: false
          description: Human-readable error description from the upstream IdP.
          schema:
            type: string
      responses:
        "303":
          description: Authentication successful. Redirects to the original target URL.
          headers:
            Location:
              schema:
                type: string
            Set-Cookie:
              description: Session cookie + cleared OIDC state cookie.
              schema:
                type: string
        "400":
          description: Invalid state, missing code, or upstream error.
          content:
            text/html:
              schema:
                type: string
        "401":
          description: OIDC authentication failed.
          content:
            text/html:
              schema:
                type: string

  # ---------------------------------------------------------------------------
  # Upstream Provider Connect
  # ---------------------------------------------------------------------------
  /connect/{provider}:
    get:
      operationId: startConnect
      summary: Start an upstream provider connection
      description: >
        Initiates the OAuth flow to connect a third-party upstream provider
        (e.g. GitHub, Google, Notion, Atlassian, Slack, Linear) registered as
        a `broker_provider`. Requires an active session. Redirects to the
        upstream provider's authorization endpoint. The `return_url` parameter
        specifies where to redirect after completion.
      tags:
        - Connect
      parameters:
        - name: provider
          in: path
          required: true
          description: Broker provider slug registered by the operator (e.g., `github`, `notion`, `atlassian-jira`).
          schema:
            type: string
            pattern: "^[a-z][a-z0-9_-]{0,63}$"
        - name: return_url
          in: query
          required: true
          description: URL to redirect to after the connection is complete. Must be in the allowed list.
          schema:
            type: string
            format: uri
        - name: resource
          in: query
          required: false
          description: >
            Optional originating-resource slug. Gates the per-resource
            `policy.connect.allowed_return_urls` check — when present, the
            return_url is validated against the allowlist on the
            referenced Mint Resource rather than the global allowlist.
          schema:
            type: string
            pattern: "^[a-z0-9][a-z0-9-]{0,63}$"
      responses:
        "302":
          description: Redirects to the upstream provider's authorization endpoint, or to the login page if the session is not authenticated.
          headers:
            Location:
              schema:
                type: string
                format: uri
        "400":
          description: Missing provider, missing return_url, unknown provider, or disallowed return URL.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

  /connect/{provider}/callback:
    get:
      operationId: completeConnect
      summary: Upstream provider OAuth callback
      description: >
        Handles the callback from the upstream OAuth provider after the user
        authorizes access. Exchanges the authorization code for an upstream
        refresh-grant, persists it as a `broker_grants` row (encrypted), and
        redirects to the connections listing page.
      tags:
        - Connect
      parameters:
        - name: provider
          in: path
          required: true
          description: The broker provider slug this callback is for.
          schema:
            type: string
        - name: code
          in: query
          required: true
          description: Authorization code from the upstream provider.
          schema:
            type: string
        - name: state
          in: query
          required: true
          description: State parameter for CSRF protection.
          schema:
            type: string
      responses:
        "302":
          description: Connection successful. Redirects to `/connections`.
          headers:
            Location:
              schema:
                type: string
                example: /connections
        "400":
          description: Missing code/state, invalid or expired state, or state tampering.
          content:
            text/html:
              schema:
                type: string
        "403":
          description: State belongs to a different user.
          content:
            text/html:
              schema:
                type: string
        "500":
          description: Failed to complete connection.
          content:
            text/html:
              schema:
                type: string

  /connections:
    get:
      operationId: listConnections
      summary: List upstream provider connections
      description: >
        Returns a list of the authenticated user's upstream provider
        connections (one per `broker_grants` row). Each entry contains
        non-sensitive metadata (provider slug, scopes granted, timestamps).
        Never includes decrypted refresh-grants.
      tags:
        - Connect
      security:
        - bearerAuth: []
      responses:
        "200":
          description: List of connections
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Connection"
        "401":
          description: Authentication required.
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Bearer
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "500":
          description: Failed to list connections.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

  /connections/{provider}:
    delete:
      operationId: disconnectProvider
      summary: Disconnect an upstream provider
      description: >
        Removes the user's connection for the specified upstream provider.
        Deletes the encrypted `broker_grants` row and any cached state.
        Returns 204 on success.
      tags:
        - Connect
      security:
        - bearerAuth: []
      parameters:
        - name: provider
          in: path
          required: true
          description: The provider slug to disconnect.
          schema:
            type: string
      responses:
        "204":
          description: Connection removed successfully. No response body.
        "400":
          description: Missing provider parameter.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "401":
          description: Authentication required.
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Bearer
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "404":
          description: Connection not found.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"
        "500":
          description: Failed to disconnect.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/OAuthError"

# =============================================================================
# Components
# =============================================================================
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Bearer JWT access token.

    basicAuth:
      type: http
      scheme: basic
      description: HTTP Basic authentication with client_id as username and client_secret as password.

  schemas:
    # -------------------------------------------------------------------------
    # Token Endpoint Request Bodies
    # -------------------------------------------------------------------------
    AuthorizationCodeTokenRequest:
      type: object
      required:
        - grant_type
        - code
        - redirect_uri
        - code_verifier
      properties:
        grant_type:
          type: string
          enum:
            - authorization_code
        code:
          type: string
          description: The authorization code received from the authorize endpoint.
        redirect_uri:
          type: string
          format: uri
          description: Must match the redirect_uri used in the authorization request.
        code_verifier:
          type: string
          description: PKCE code verifier (original random string).
        client_id:
          type: string
          description: Client ID (alternative to Basic auth).
        client_secret:
          type: string
          description: Client secret (alternative to Basic auth, for confidential clients).

    RefreshTokenRequest:
      type: object
      required:
        - grant_type
        - refresh_token
      properties:
        grant_type:
          type: string
          enum:
            - refresh_token
        refresh_token:
          type: string
          description: The refresh token to rotate.
        scope:
          type: string
          description: Optional space-separated list to request a narrower scope.
        client_id:
          type: string
          description: Client ID (alternative to Basic auth).
        client_secret:
          type: string
          description: Client secret (alternative to Basic auth, for confidential clients).

    ClientCredentialsTokenRequest:
      type: object
      required:
        - grant_type
      properties:
        grant_type:
          type: string
          enum:
            - client_credentials
        scope:
          type: string
          description: Optional space-separated list of requested scopes.
        resource:
          type: string
          format: uri
          description: RFC 8707 resource indicator. Sets the JWT audience claim.
        client_id:
          type: string
          description: Client ID (alternative to Basic auth).
        client_secret:
          type: string
          description: Client secret (alternative to Basic auth).

    TokenExchangeTokenRequest:
      type: object
      required:
        - grant_type
        - subject_token
        - subject_token_type
      properties:
        grant_type:
          type: string
          enum:
            - "urn:ietf:params:oauth:grant-type:token-exchange"
        subject_token:
          type: string
          description: The subject token to exchange.
        subject_token_type:
          type: string
          description: >
            URI identifying the type of subject_token (e.g.,
            `urn:ietf:params:oauth:token-type:access_token`).
        actor_token:
          type: string
          description: Optional actor token for delegation flows.
        actor_token_type:
          type: string
          description: URI identifying the type of actor_token.
        scope:
          type: string
          description: Optional space-separated list of requested scopes.
        resource:
          type: string
          format: uri
          description: RFC 8707 resource indicator.
        client_id:
          type: string
          description: Client ID (alternative to Basic auth).
        client_secret:
          type: string
          description: Client secret (alternative to Basic auth).

    JWTBearerTokenRequest:
      type: object
      description: >
        JWT-Bearer (RFC 7523) grant for the MCP Enterprise-Managed
        Authorization (XAA) flow. The `assertion` is an ID-JAG signed
        by a `trusted_idps` issuer; authserver verifies the signature
        against the IdP's JWKS, applies the configured XAA policy and
        subject mapping, then issues an access token. No refresh
        token is issued (per spec). Wire shape is `JWTBearerRequest`
        in `internal/ports/input/jwt_bearer.go`.
      required:
        - grant_type
        - assertion
      properties:
        grant_type:
          type: string
          enum:
            - "urn:ietf:params:oauth:grant-type:jwt-bearer"
        assertion:
          type: string
          description: The signed ID-JAG JWT assertion.
        scope:
          type: string
          description: Optional space-separated list of requested scopes.
        resource:
          type: string
          format: uri
          description: RFC 8707 resource indicator.
        client_id:
          type: string
          description: Client ID (alternative to Basic auth).
        client_secret:
          type: string
          description: Client secret (alternative to Basic auth, for confidential clients).

    # -------------------------------------------------------------------------
    # Token Endpoint Responses
    # -------------------------------------------------------------------------
    TokenResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
      properties:
        access_token:
          type: string
          description: The issued access token (JWT).
        token_type:
          type: string
          description: Token type — `Bearer` or `DPoP`.
          enum:
            - Bearer
            - DPoP
        expires_in:
          type: integer
          description: Token lifetime in seconds.
        refresh_token:
          type: string
          description: >
            Rotated refresh token. Present for authorization_code and refresh_token
            grants. Not issued for client_credentials.
        scope:
          type: string
          description: Space-separated list of granted scopes (may differ from requested).

    TokenExchangeResponse:
      type: object
      required:
        - access_token
        - issued_token_type
        - token_type
        - expires_in
      properties:
        access_token:
          type: string
          description: The issued access token (JWT).
        issued_token_type:
          type: string
          description: >
            URI identifying the type of issued token
            (e.g., `urn:ietf:params:oauth:token-type:access_token`).
        token_type:
          type: string
          description: Token type — `Bearer` or `DPoP`.
          enum:
            - Bearer
            - DPoP
        expires_in:
          type: integer
          description: Token lifetime in seconds.
        scope:
          type: string
          description: Space-separated list of granted scopes.

    # -------------------------------------------------------------------------
    # Introspection
    # -------------------------------------------------------------------------
    IntrospectionResponse:
      type: object
      required:
        - active
      properties:
        active:
          type: boolean
          description: >
            Whether the token is active. If false, no other fields are returned.
        scope:
          type: string
          description: Space-separated list of scopes.
        client_id:
          type: string
          description: Client that the token was issued to.
        username:
          type: string
          description: Human-readable identifier for the resource owner.
        sub:
          type: string
          description: Subject — the resource owner's user ID.
        aud:
          type: string
          description: Audience the token is intended for.
        iss:
          type: string
          description: Issuer of the token.
        exp:
          type: integer
          format: int64
          description: Token expiration time (Unix timestamp).
        iat:
          type: integer
          format: int64
          description: Token issuance time (Unix timestamp).
        jti:
          type: string
          description: Unique token identifier.
        token_type:
          type: string
          description: Type of the introspected token.
        cnf:
          type: object
          description: >
            Confirmation claim (RFC 9449). Present for DPoP-bound tokens.
            Contains `jkt` (JWK Thumbprint of the DPoP public key).
          properties:
            jkt:
              type: string
              description: JWK Thumbprint (base64url-encoded SHA-256).
          additionalProperties: true

    # -------------------------------------------------------------------------
    # Dynamic Client Registration (RFC 7591)
    # -------------------------------------------------------------------------
    DCRRequest:
      type: object
      required:
        - redirect_uris
      properties:
        redirect_uris:
          type: array
          items:
            type: string
            format: uri
          description: List of allowed redirect URIs. At least one required.
        client_name:
          type: string
          description: Human-readable client name.
        grant_types:
          type: array
          items:
            type: string
          description: >
            Grant types the client will use. Defaults to `["authorization_code"]`.
          example:
            - authorization_code
            - refresh_token
        response_types:
          type: array
          items:
            type: string
          description: Response types the client will use. Defaults to `["code"]`.
          example:
            - code
        token_endpoint_auth_method:
          type: string
          description: >
            Authentication method for the token endpoint.
            One of `none`, `client_secret_basic`, `client_secret_post`.
          enum:
            - none
            - client_secret_basic
            - client_secret_post
        agent:
          type: boolean
          description: >
            Authplane extension. If true, marks this client as an agent client
            for agent identity claims.
        agent_description:
          type: string
          description: >
            Authplane extension. Human-readable description of the agent.

    DCRResponse:
      type: object
      required:
        - client_id
        - client_id_issued_at
        - redirect_uris
        - client_name
        - grant_types
        - response_types
        - token_endpoint_auth_method
      properties:
        client_id:
          type: string
          description: The issued client identifier.
        client_secret:
          type: string
          description: >
            The client secret. Only issued for confidential clients
            (`client_secret_basic` or `client_secret_post`).
        client_id_issued_at:
          type: integer
          format: int64
          description: Unix timestamp when the client_id was issued.
        client_secret_expires_at:
          type: integer
          format: int64
          nullable: true
          description: Unix timestamp when the client_secret expires. Null if it does not expire.
        redirect_uris:
          type: array
          items:
            type: string
            format: uri
        client_name:
          type: string
        grant_types:
          type: array
          items:
            type: string
        response_types:
          type: array
          items:
            type: string
        token_endpoint_auth_method:
          type: string
        agent:
          type: boolean
          description: Authplane extension. True if this is an agent client.
        agent_description:
          type: string
          description: Authplane extension. Agent description.

    # -------------------------------------------------------------------------
    # Discovery Metadata
    # -------------------------------------------------------------------------
    ASMetadata:
      type: object
      description: OAuth 2.0 Authorization Server Metadata (RFC 8414).
      required:
        - issuer
        - authorization_endpoint
        - token_endpoint
        - registration_endpoint
        - revocation_endpoint
        - jwks_uri
        - response_types_supported
        - grant_types_supported
        - token_endpoint_auth_methods_supported
        - revocation_endpoint_auth_methods_supported
        - code_challenge_methods_supported
        - scopes_supported
        - resource_indicators_supported
      properties:
        issuer:
          type: string
          format: uri
          description: The authorization server's issuer identifier (URL).
        authorization_endpoint:
          type: string
          format: uri
        token_endpoint:
          type: string
          format: uri
        registration_endpoint:
          type: string
          format: uri
        revocation_endpoint:
          type: string
          format: uri
        introspection_endpoint:
          type: string
          format: uri
          description: Present only when token introspection is enabled.
        jwks_uri:
          type: string
          format: uri
        response_types_supported:
          type: array
          items:
            type: string
          example:
            - code
        grant_types_supported:
          type: array
          items:
            type: string
          description: >
            Always includes `authorization_code` and `refresh_token`.
            May also include `client_credentials` and
            `urn:ietf:params:oauth:grant-type:token-exchange` if enabled.
          example:
            - authorization_code
            - refresh_token
            - client_credentials
        token_endpoint_auth_methods_supported:
          type: array
          items:
            type: string
          example:
            - none
            - client_secret_basic
            - client_secret_post
        introspection_endpoint_auth_methods_supported:
          type: array
          items:
            type: string
          description: Present only when token introspection is enabled.
          example:
            - client_secret_basic
            - client_secret_post
        revocation_endpoint_auth_methods_supported:
          type: array
          items:
            type: string
          example:
            - none
            - client_secret_basic
            - client_secret_post
        code_challenge_methods_supported:
          type: array
          items:
            type: string
          example:
            - S256
        scopes_supported:
          type: array
          items:
            type: string
          description: List of supported scope values.
        resource_indicators_supported:
          type: boolean
          description: Always true. Indicates RFC 8707 resource indicators are supported.
        dpop_signing_alg_values_supported:
          type: array
          items:
            type: string
          description: >
            DPoP signing algorithms supported (RFC 9449). Present only when
            DPoP is enabled.
          example:
            - ES256
            - RS256
            - PS256
        client_id_metadata_document_supported:
          type: boolean
          description: >
            CIMD (Client ID Metadata Document) auto-registration support.
            Present only when CIMD is enabled.
        authplane_agent_identity_supported:
          type: boolean
          description: >
            Authplane extension. True when agent identity claims are enabled.
            Non-standard field.
        identity_assertion_supported:
          type: boolean
          description: >
            MCP XAA extension. True when JWT-Bearer (RFC 7523) /
            ID-JAG assertions are enabled at `/oauth/token`.

    PRMMetadata:
      type: object
      description: OAuth 2.0 Protected Resource Metadata (RFC 9728).
      required:
        - resource
        - authorization_servers
        - scopes_supported
        - bearer_methods_supported
      properties:
        resource:
          type: string
          format: uri
          description: The protected resource's identifier (URI).
        authorization_servers:
          type: array
          items:
            type: string
            format: uri
          description: List of authorization server issuer URIs that protect this resource.
        scopes_supported:
          type: array
          items:
            type: string
          description: Scopes the resource supports.
        bearer_methods_supported:
          type: array
          items:
            type: string
          description: Methods for presenting bearer tokens.
          example:
            - header

    JWKS:
      type: object
      description: JSON Web Key Set (RFC 7517).
      required:
        - keys
      properties:
        keys:
          type: array
          items:
            type: object
            description: >
              A JSON Web Key. The exact properties depend on the key type
              (EC or RSA). Common fields include `kty`, `kid`, `use`, `alg`,
              and key-type-specific parameters.
            properties:
              kty:
                type: string
                description: Key type (`EC` or `RSA`).
              kid:
                type: string
                description: Key identifier.
              use:
                type: string
                description: Key usage (`sig`).
              alg:
                type: string
                description: Algorithm (`ES256` or `RS256`).
              crv:
                type: string
                description: EC curve name (for EC keys). `P-256`.
              x:
                type: string
                description: EC x coordinate (base64url, for EC keys).
              "y":
                type: string
                description: EC y coordinate (base64url, for EC keys).
              n:
                type: string
                description: RSA modulus (base64url, for RSA keys).
              e:
                type: string
                description: RSA exponent (base64url, for RSA keys).
            additionalProperties: true

    # -------------------------------------------------------------------------
    # Error Responses
    # -------------------------------------------------------------------------
    OAuthError:
      type: object
      description: >
        Combined OAuth 2.0 error (RFC 6749) and RFC 9457 Problem Details response.
        Content-Type is `application/problem+json`.
      required:
        - error
        - error_description
        - type
        - title
        - detail
        - status
      properties:
        error:
          type: string
          description: >
            OAuth error code (e.g., `invalid_request`, `invalid_client`,
            `invalid_grant`, `unauthorized_client`, `unsupported_grant_type`,
            `invalid_scope`, `invalid_dpop_proof`, `use_dpop_nonce`,
            `access_denied`, `consent_required`, `server_error`).
        error_description:
          type: string
          description: Human-readable description of the error.
        type:
          type: string
          format: uri
          description: >
            RFC 9457 Problem Details type URI.
            Format: `https://docs.authplane.io/errors/{error_code}`.
        title:
          type: string
          description: HTTP status text (e.g., "Bad Request", "Unauthorized").
        detail:
          type: string
          description: Same as error_description.
        status:
          type: integer
          description: HTTP status code.
        consent_url:
          type: string
          format: uri
          description: >
            URL the client should send the user to so the AS can collect the
            missing consent. Only present when `error=consent_required` is
            returned from the token endpoint for a Broker-backed resource.
            Two flavors (selected by the AS based on the `cause`):

            - upstream provider connect — `/connect/{provider}` — when the
              user has not yet authorized the upstream provider, or when the
              authorization is missing scopes (bound-D / bound-E failures).
            - AS-side re-consent — `/authorize?resource=...&scope=...` —
              when the user has authorized the upstream but has not consented
              to this agent for this resource (bound-B / bound-C failures).

            Clients can append their own `return_url` query parameter before
            opening the URL.
          example: "https://as.example.com/connect/google-calendar"
        cause:
          type: string
          enum:
            - consent_missing
            - scope_insufficient
          description: >
            Sub-discriminator for `consent_required` errors. `consent_missing`
            means no relevant grant row exists for the (user, agent, resource)
            tuple; `scope_insufficient` means a grant exists but does not cover
            one or more of the requested scopes. Omitted for non-consent
            errors and for legacy consent errors that predate the field.
            Diagnostic detail (the specific missing scopes) is logged
            server-side only — clients re-discover via PRM and the user's
            consent surface.
      example:
        error: invalid_grant
        error_description: "PKCE verification failed"
        type: "https://docs.authplane.io/errors/invalid_grant"
        title: Bad Request
        detail: "PKCE verification failed"
        status: 400

    # -------------------------------------------------------------------------
    # Health
    # -------------------------------------------------------------------------
    HealthResponse:
      type: object
      required:
        - status
        - time
      properties:
        status:
          type: string
          description: >
            Server status. Values: `ok`, `degraded` (for /health),
            `ready`, `not_ready` (for /ready).
          enum:
            - ok
            - degraded
            - ready
            - not_ready
        time:
          type: string
          format: date-time
          description: Current server time in RFC 3339 format.
        db:
          type: string
          description: Database connectivity status.
          enum:
            - ok
            - error

    # -------------------------------------------------------------------------
    # Connect (broker-backed upstream connections)
    # -------------------------------------------------------------------------
    Connection:
      type: object
      description: >
        Non-sensitive metadata about a user's upstream provider
        connection (one per `broker_grants` row). Wire shape is
        `input.ConnectionMeta` in
        `internal/ports/input/connect.go`. Never includes the
        decrypted upstream refresh-grant. The connection UUID and
        owning user-id are intentionally omitted from this view —
        provider slug is the operator-meaningful identifier.
      properties:
        provider:
          type: string
          description: Broker provider slug (e.g., `github`, `notion`, `atlassian-jira`).
        scopes_granted:
          type: array
          items:
            type: string
          description: OAuth scopes granted by the upstream provider.
        created_at:
          type: string
          format: date-time
          description: When the connection was created.
        updated_at:
          type: string
          format: date-time
          description: When the connection was last updated.
      required:
        - provider
        - scopes_granted
        - created_at
        - updated_at

