openapi: 3.1.0
info:
  title: PuzzParty API
  version: "1.0.0"
  summary: Player identity and friends for web games.
  description: |
    Games sign players in with "Continue with PuzzParty" (OAuth 2.1 authorization code + PKCE)
    and call these endpoints with the player's access token.

    Privacy rule: a game only ever sees the friends who have **also** connected that same game.

    Most games should use the browser SDK (`https://puzzparty.com/sdk/v1/puzzparty.js`), which
    handles sign-in, token storage and refresh. Docs: https://puzzparty.com/docs
  contact:
    url: https://puzzparty.com

servers:
  - url: https://api.puzzparty.com
    description: Production
  - url: http://127.0.0.1:54321/functions/v1/api
    description: Local development (supabase start + supabase functions serve)

security:
  - puzzpartyOAuth: [openid, profile]

paths:
  /v1/me:
    get:
      operationId: getMe
      summary: The signed-in player
      description: Returns the player the access token belongs to. Use `id` as the player's stable key.
      responses:
        "200":
          description: The player.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Player"
              example:
                id: 0b6f2c1e-8d7a-4f5e-9c3b-2a1d4e5f6a7b
                username: manav
                display_name: Manav
                avatar_url: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`profile_not_found`: the player's profile no longer exists (e.g. the account was deleted)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/me/friends:
    get:
      operationId: getMyFriends
      summary: Friends who also play this game
      description: |
        The player's accepted friends who have also connected the calling game, sorted by username.
        Friends who haven't connected the game are never included.
      responses:
        "200":
          description: The player's friends in this game.
          content:
            application/json:
              schema:
                type: object
                required: [friends]
                properties:
                  friends:
                    type: array
                    items:
                      $ref: "#/components/schemas/Player"
              example:
                friends:
                  - id: 7c1d9e2f-3a4b-4c5d-8e6f-1a2b3c4d5e6f
                    username: bob
                    display_name: Bob
                    avatar_url: https://example.com/bob.png
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    puzzpartyOAuth:
      type: oauth2
      description: |
        Public clients only: no client secret; PKCE (S256) is required. Redirect URIs must exactly match
        the ones registered for the game. Send the resulting access token (ppa_…) as
        `Authorization: Bearer <token>`. Tokens only work on this API. Each refresh returns a new
        refresh token (ppr_…); reusing an old one ends the player's sign-in.
      flows:
        authorizationCode:
          authorizationUrl: https://auth.puzzparty.com/auth/v1/oauth/authorize
          tokenUrl: https://api.puzzparty.com/oauth/token
          refreshUrl: https://api.puzzparty.com/oauth/token
          scopes:
            openid: Required. Signs the player in.
            profile: Username, display name and avatar.

  schemas:
    Player:
      type: object
      required: [id, username, display_name, avatar_url]
      properties:
        id:
          type: string
          format: uuid
          description: Stable PuzzParty user ID. Never changes; use it as the player's key.
        username:
          type: string
          pattern: "^[a-z0-9_]{3,20}$"
          description: Unique handle. Players can change it, so don't use it as a key. Safe to show publicly.
        display_name:
          type: [string, "null"]
          maxLength: 40
          description: May be a real name. Show only to the player and their friends.
        avatar_url:
          type: [string, "null"]
          format: uri
          description: HTTPS image URL. May be a real photo. Show only to the player and their friends.

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          enum:
            - missing_token
            - invalid_token
            - game_not_connected
            - not_found
            - profile_not_found
            - method_not_allowed
            - internal_error

  responses:
    Unauthorized:
      description: |
        `missing_token`: no bearer token. `invalid_token`: expired or not a PuzzParty token;
        refresh it and retry once.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: invalid_token
    Forbidden:
      description: |
        `game_not_connected`: the player disconnected this game (or the game isn't verified yet);
        treat the player as signed out.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: game_not_connected
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: internal_error
