{
  "openapi": "3.0.3",
  "info": {
    "title": "ReliPay API",
    "description": "Self-hostable authentication + billing + admin REST API. All money is in integer minor units (cents). Every error carries a `code`, human `message`, and a `fix`. Authenticate public routes with an Application secret key (`rp_live_…` / `rp_test_…`) in the `Authorization: Bearer` header; per-user routes also take the user JWT in `X-Relipay-User-Token`.",
    "version": "0.1.0-beta.1"
  },
  "components": {
    "securitySchemes": {
      "superAdminKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bootstrap admin credential (SUPER_ADMIN_KEY env var). Required for every /api/v1/admin/* route."
      },
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Application-scoped secret key (rp_live_… or rp_test_…). Used by @relipay/node for the public API."
      }
    },
    "schemas": {}
  },
  "paths": {
    "/health": {
      "get": {
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/health/ready": {
      "get": {
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "summary": "Inspect the Application this credential resolves to",
        "tags": [
          "Public · Me"
        ],
        "description": "Use this as the SDK smoke test — if it returns 200, your secret key is good. No secrets are returned in the response.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/sign-up": {
      "post": {
        "summary": "Create an end-user via email + password",
        "tags": [
          "Public · Auth"
        ],
        "description": "Creates a new EndUser in the calling Application and issues a JWT. Email is unique per Application, not globally.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "password": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form per-app metadata (display name, avatar, custom fields)."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/sign-in": {
      "post": {
        "summary": "Authenticate an existing end-user with email + password",
        "tags": [
          "Public · Auth"
        ],
        "description": "Verifies the password and issues a JWT. Returns 401 INVALID_CREDENTIALS for any auth failure (wrong email, wrong password, or sign-up via different method) — we never disclose which.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "password": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/mfa-verify": {
      "post": {
        "summary": "Exchange an MFA challenge token + TOTP/backup code for a session",
        "tags": [
          "Public · Auth"
        ],
        "description": "Used after /sign-in or OAuth callback returns `mfaRequired: true`. The challenge token is short-lived (5 minutes) and bound to (endUser, application). On success returns the same shape as /sign-in when MFA is not required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mfaChallengeToken",
                  "code"
                ],
                "properties": {
                  "mfaChallengeToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2048
                  },
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/refresh": {
      "post": {
        "summary": "Exchange a refresh token for a new {access, refresh} pair",
        "tags": [
          "Public · Auth"
        ],
        "description": "The presented refresh token is revoked atomically with issuing the replacement. A presented-but-revoked token is treated as a replay and rejected with REFRESH_TOKEN_REUSED.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refreshToken"
                ],
                "properties": {
                  "refreshToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/sign-out": {
      "post": {
        "summary": "Revoke a refresh token",
        "tags": [
          "Public · Auth"
        ],
        "description": "Idempotent. Returns 200 even for unknown tokens — we don't disclose whether the token existed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refreshToken"
                ],
                "properties": {
                  "refreshToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/forgot-password": {
      "post": {
        "summary": "Request a password-reset token for an email",
        "tags": [
          "Public · Auth"
        ],
        "description": "Always returns 200 with `{ delivered: boolean, emailSent: boolean, resetToken: string|null }`. Never discloses whether the email exists. When the Application has email transport configured (BYO Resend or RESEND_DEFAULT_*), the email is sent and `resetToken` is null. Otherwise the legacy contract applies — caller forwards `resetToken` via their own provider.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "resetUrl": {
                    "type": "string",
                    "maxLength": 2048
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/magic-link/request": {
      "post": {
        "summary": "Request a magic-link sign-in email for an address",
        "tags": [
          "Public · Auth"
        ],
        "description": "Enumeration-safe: returns the same shape whether the email exists or not. When the Application has email transport configured, the link is sent and `magicLinkToken` is null. Otherwise the raw token is returned for the caller to forward via their own provider. Honours `authConfig.signupEnabled` — when disabled, magic links for new emails are silently refused (same enumeration-safe shape).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "signInUrl": {
                    "type": "string",
                    "maxLength": 2048
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/magic-link/verify": {
      "post": {
        "summary": "Consume a magic-link token + complete sign-in",
        "tags": [
          "Public · Auth"
        ],
        "description": "Single-use, 15-minute lifetime. Returns the same `SignInOutcome` shape as /sign-in — MFA-enrolled users get a challenge token; otherwise a full session. For tokens issued when the email had no account yet, the EndUser is created atomically with the consume (sign-up must be enabled on the Application).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/passkey/authenticate/start": {
      "post": {
        "summary": "Begin a passkey authentication ceremony",
        "tags": [
          "Public · Auth"
        ],
        "description": "Returns `{ options, expectedChallenge }`. Forward `options` to the browser (`navigator.credentials.get(...)`), persist `expectedChallenge` server-side in the customer app session (cookie / Redis), and send both back to /passkey/authenticate/complete. Usernameless when `email` is omitted; email-first when supplied (allowCredentials scoped to that user).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/passkey/authenticate/complete": {
      "post": {
        "summary": "Complete a passkey authentication and mint a session",
        "tags": [
          "Public · Auth"
        ],
        "description": "Verifies the browser response against `expectedChallenge` from /start. On success returns the same `SignInOutcome` shape as /sign-in — passkeys bypass MFA challenge (the passkey itself is a strong factor).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "response",
                  "expectedChallenge"
                ],
                "properties": {
                  "response": {
                    "type": "object"
                  },
                  "expectedChallenge": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/verify-email": {
      "post": {
        "summary": "Consume an email-verification token; marks `emailVerified: true`",
        "tags": [
          "Public · Auth"
        ],
        "description": "Single-use token, 24-hour lifetime. Refuses if the email on the token differs from the current account email (e.g. user changed email after the token was issued).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/reset-password": {
      "post": {
        "summary": "Consume a reset token + set a new password",
        "tags": [
          "Public · Auth"
        ],
        "description": "Single-use token; consumed atomically. On success, every refresh token for this end-user is revoked — anyone holding a session via the compromised credential is signed out.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "newPassword"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  },
                  "newPassword": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/change-password": {
      "post": {
        "summary": "Change the current user's password",
        "tags": [
          "Public · Auth"
        ],
        "description": "Requires the current password. On success, revokes every refresh token for this user — other devices are signed out. The caller's access token stays valid until its 15-min expiry.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "currentPassword",
                  "newPassword"
                ],
                "properties": {
                  "currentPassword": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "newPassword": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/send-verification": {
      "post": {
        "summary": "Send (or re-send) an email-verification link to the current user",
        "tags": [
          "Public · Auth"
        ],
        "description": "Mints a 24-hour single-use verification token bound to (user, application, email). If email transport is configured (BYO Resend or RESEND_DEFAULT_*), we send the email and `verificationToken` is null. Otherwise we return the raw token for the caller to forward via their own provider.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "verifyUrl": {
                    "type": "string",
                    "maxLength": 2048
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/passkey/register/start": {
      "post": {
        "summary": "Begin a passkey registration ceremony for the current user",
        "tags": [
          "Public · Auth"
        ],
        "description": "Returns `{ options, expectedChallenge }`. Forward `options` to `navigator.credentials.create(...)` and POST the result back to /passkey/register/complete along with the same `expectedChallenge`.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/passkey/register/complete": {
      "post": {
        "summary": "Complete a passkey registration; stores the credential",
        "tags": [
          "Public · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "response",
                  "expectedChallenge"
                ],
                "properties": {
                  "response": {
                    "type": "object"
                  },
                  "expectedChallenge": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "deviceName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/passkeys": {
      "get": {
        "summary": "List the current user's registered passkeys",
        "tags": [
          "Public · Auth"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/passkeys/{id}": {
      "delete": {
        "summary": "Remove a passkey from the current user",
        "tags": [
          "Public · Auth"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/sessions": {
      "get": {
        "summary": "List the current user's active sessions (live refresh tokens)",
        "tags": [
          "Public · Auth"
        ],
        "description": "Returns active sessions ordered newest-first, with the User-Agent + IP captured at issue time. Use the returned `id` to revoke individual sessions via DELETE /sessions/:id.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/sessions/{id}": {
      "delete": {
        "summary": "Revoke one session by id. Idempotent.",
        "tags": [
          "Public · Auth"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/sign-out-everywhere": {
      "post": {
        "summary": "Revoke every refresh token for the current user",
        "tags": [
          "Public · Auth"
        ],
        "description": "Used for \"sign out of all devices\" / suspected compromise. The caller's access token stays valid until its 15-min expiry; clear it client-side for full logout.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/me": {
      "get": {
        "summary": "Get the current end-user (from the user token alone)",
        "tags": [
          "Public · Auth"
        ],
        "description": "Resolves the end-user from the X-Relipay-User-Token JWT only — no Application secret key required. Intended for browser/client SDKs that hold only the user access token. Returns 401 USER_TOKEN_INVALID when the token is missing, expired, or malformed.",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/oauth/{provider}/start": {
      "post": {
        "summary": "Get the authorization URL for an OAuth provider",
        "tags": [
          "Public · OAuth"
        ],
        "description": "Returns the URL the browser should be redirected to. Pass an unguessable `state` to round-trip; verify it on callback before calling /callback here.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "state"
                ],
                "properties": {
                  "state": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/oauth/{provider}/callback": {
      "post": {
        "summary": "Exchange an OAuth code for a ReliPay session",
        "tags": [
          "Public · OAuth"
        ],
        "description": "Pass the `code` query param from the provider callback. Returns a fresh access+refresh pair for the matched-or-created EndUser. Verify state CSRF *before* calling.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/oauth/identities": {
      "get": {
        "summary": "List OAuth providers linked to the current user",
        "tags": [
          "Public · OAuth"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/oauth/{provider}/link/start": {
      "post": {
        "summary": "Begin an OAuth link flow for the current user",
        "tags": [
          "Public · OAuth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "state"
                ],
                "properties": {
                  "state": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/oauth/{provider}/link/complete": {
      "post": {
        "summary": "Complete an OAuth link — attaches the provider identity to the current user",
        "tags": [
          "Public · OAuth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/oauth/{provider}": {
      "delete": {
        "summary": "Remove an OAuth provider from the current user. Refuses if it would lock the account out.",
        "tags": [
          "Public · OAuth"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me": {
      "get": {
        "summary": "Get the current end-user (resolved from the user JWT)",
        "tags": [
          "Public · Auth"
        ],
        "description": "Requires both the Application secret key (Authorization header) and the user JWT (X-Relipay-User-Token header). Refuses to return data if the JWT was issued by a different Application than the secret key represents.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations": {
      "post": {
        "summary": "Create an organization; caller becomes the OWNER",
        "tags": [
          "Public · Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "slug"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "metadata": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "get": {
        "summary": "List the caller's organizations (with their role in each)",
        "tags": [
          "Public · Organizations"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations/{id}": {
      "get": {
        "summary": "Fetch one organization the caller belongs to",
        "tags": [
          "Public · Organizations"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "patch": {
        "summary": "Update org name / metadata (OWNER + ADMIN)",
        "tags": [
          "Public · Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "metadata": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations/{id}/members": {
      "get": {
        "summary": "List members of an org the caller belongs to",
        "tags": [
          "Public · Organizations"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations/{id}/invitations": {
      "post": {
        "summary": "Invite a user (OWNER + ADMIN). Returns the raw token once.",
        "tags": [
          "Public · Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MEMBER"
                    ]
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations/{id}/invitations/{invId}/revoke": {
      "post": {
        "summary": "Revoke a pending invitation (OWNER + ADMIN)",
        "tags": [
          "Public · Organizations"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "invId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations/{id}/members/{euid}": {
      "patch": {
        "summary": "Change a member's role (OWNER manages anyone; ADMIN manages MEMBER + ADMIN)",
        "tags": [
          "Public · Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MEMBER"
                    ]
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Remove a member (or self). Refuses removing the last OWNER.",
        "tags": [
          "Public · Organizations"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/users/me/organizations/{id}/leave": {
      "post": {
        "summary": "Self-leave. Refuses leaving as the last OWNER.",
        "tags": [
          "Public · Organizations"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/organizations/accept-invitation": {
      "post": {
        "summary": "Accept an organization invitation. Creates the membership.",
        "tags": [
          "Public · Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/plans": {
      "get": {
        "summary": "List active plans for the calling application",
        "tags": [
          "Public · Billing"
        ],
        "description": "Returns the public plan catalogue. End-users typically reach this via your pricing page.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/subscription": {
      "get": {
        "summary": "Get the current end-user's active subscription, or null",
        "tags": [
          "Public · Billing"
        ],
        "description": "Returns null when the user has no active/pending/past-due subscription. Requires the user JWT in X-Relipay-User-Token.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/checkout": {
      "post": {
        "summary": "Start a checkout session for the current end-user",
        "tags": [
          "Public · Billing"
        ],
        "description": "Creates (or reuses) a PENDING Subscription locally and returns a provider-hosted checkout URL. Activation happens via the provider's webhook — not synchronously here.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "planSlug",
                  "successUrl",
                  "cancelUrl"
                ],
                "properties": {
                  "planSlug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "successUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "cancelUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "couponCode": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "Optional coupon to apply. Validated server-side; a bad code rejects the whole checkout."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/providers": {
      "get": {
        "summary": "List enabled billing providers for the calling application",
        "tags": [
          "Public · Billing"
        ],
        "description": "Returns providers configured + enabled for this Application, in the order the geo router would prefer them given the request country (CF-IPCountry header).",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/coupons/validate": {
      "post": {
        "summary": "Validate a coupon for the current user against a plan",
        "tags": [
          "Public · Billing"
        ],
        "description": "Returns the discounted amount on success. Surfaces a precise RelipayError on every failure mode so the panel / pricing page can render a useful message.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "planSlug"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "planSlug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/mfa/status": {
      "get": {
        "summary": "MFA status for the current user",
        "tags": [
          "Public · MFA"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/mfa/setup": {
      "post": {
        "summary": "Mint a new TOTP secret + 10 backup codes (one-time-show). Not enrolled until /setup-confirm.",
        "tags": [
          "Public · MFA"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/mfa/setup-confirm": {
      "post": {
        "summary": "Confirm MFA enrollment by entering the current 6-digit code",
        "tags": [
          "Public · MFA"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/mfa/challenge": {
      "post": {
        "summary": "Verify a TOTP or backup code (step-up auth). Returns { ok: bool }.",
        "tags": [
          "Public · MFA"
        ],
        "description": "Backup codes are single-use — consumed on success.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/auth/mfa/disable": {
      "post": {
        "summary": "Disable MFA for the current user",
        "tags": [
          "Public · MFA"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/licenses/verify": {
      "post": {
        "summary": "Verify a license key + record an activation for this machine",
        "tags": [
          "Public · Licenses"
        ],
        "description": "Returns { ok, license?, reason? }. `ok=false` is intentional for invalid licenses — the customer's software loops on this and we want a deterministic body, not an HTTP error.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key",
                  "machineFingerprint"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "machineFingerprint": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/usage/record": {
      "post": {
        "summary": "Record a usage event against a named meter",
        "tags": [
          "Public · Usage"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "meterSlug",
                  "quantity"
                ],
                "properties": {
                  "meterSlug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "quantity": {
                    "type": "integer"
                  },
                  "endUserId": {
                    "type": "string"
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/usage/aggregate": {
      "get": {
        "summary": "Sum recorded quantity for a meter (with optional time window + end-user filter)",
        "tags": [
          "Public · Usage"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "in": "query",
            "name": "meterSlug",
            "required": true
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "from",
            "required": false
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "to",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "endUserId",
            "required": false
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/credits/balance": {
      "get": {
        "summary": "Get an end-user's current credit balance",
        "tags": [
          "Public · Credits"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "endUserId",
            "required": true
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/credits/consume": {
      "post": {
        "summary": "Deduct credits from an end-user (idempotent)",
        "tags": [
          "Public · Credits"
        ],
        "description": "Atomically debits `amount` credits. Returns 402 CREDITS_INSUFFICIENT when the balance is too low. Pass `idempotencyKey` (e.g. the lead id) to make retries safe — a repeat with the same key returns the original result without double-charging.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endUserId",
                  "amount"
                ],
                "properties": {
                  "endUserId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/credits/ledger": {
      "get": {
        "summary": "List an end-user's recent credit ledger entries (newest first)",
        "tags": [
          "Public · Credits"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "endUserId",
            "required": true
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "in": "query",
            "name": "limit",
            "required": false
          }
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/webhook/stripe": {
      "post": {
        "summary": "Receive a Stripe webhook event (global endpoint, BYO not yet configured)",
        "tags": [
          "Webhooks · Stripe"
        ],
        "description": "Uses the deployment-wide STRIPE_WEBHOOK_SECRET. Prefer the per-app endpoint /api/v1/billing/webhook/stripe/:appSlug once BYO credentials are configured.",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/webhook/stripe/{slug}": {
      "post": {
        "summary": "Receive a Stripe webhook event for a specific Application (BYO secret)",
        "tags": [
          "Webhooks · Stripe"
        ],
        "description": "The slug must match an Application with stripe credentials configured. The signature is verified against THAT Application's webhook secret — no events from other Stripe accounts will be accepted.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/billing/webhook/paypal/{slug}": {
      "post": {
        "summary": "Receive a PayPal webhook event for a specific Application (BYO webhook id)",
        "tags": [
          "Webhooks · PayPal"
        ],
        "description": "The slug must match an Application with PayPal credentials configured. Signature is verified against THAT Application's webhook id via PayPal's verify-webhook-signature API.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/sign-up": {
      "post": {
        "summary": "Self-serve sign-up — creates an operator account, a Tenant, and an OWNER membership",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password",
                  "workspaceName"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "password": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 256
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "workspaceName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/sign-in": {
      "post": {
        "summary": "Operator sign-in. Returns memberships + a session scoped to the first workspace.",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/mfa-verify": {
      "post": {
        "summary": "Exchange an operator MFA challenge token + code for a real session",
        "tags": [
          "Tenant · Auth"
        ],
        "description": "Called after /sign-in returns `mfaRequired: true`. The challenge token expires after 5 minutes and is bound to the operator that just passed the primary factor.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mfaChallengeToken",
                  "code"
                ],
                "properties": {
                  "mfaChallengeToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2048
                  },
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/refresh": {
      "post": {
        "summary": "Exchange a refresh token for a new pair (rotated)",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refreshToken"
                ],
                "properties": {
                  "refreshToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/sign-out": {
      "post": {
        "summary": "Revoke the presented refresh token (idempotent)",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refreshToken"
                ],
                "properties": {
                  "refreshToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/forgot-password": {
      "post": {
        "summary": "Request a password-reset token. ReliPay does not send email — caller forwards the token.",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/reset-password": {
      "post": {
        "summary": "Consume a reset token + set new password (revokes all sessions)",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "newPassword"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  },
                  "newPassword": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 256
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/me": {
      "get": {
        "summary": "Current operator + memberships + active workspace",
        "tags": [
          "Tenant · Auth"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/switch-workspace": {
      "post": {
        "summary": "Switch the active workspace. Returns a new {access, refresh} pair scoped to the target.",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tenantId"
                ],
                "properties": {
                  "tenantId": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/sessions": {
      "get": {
        "summary": "List the operator's active sessions",
        "tags": [
          "Tenant · Auth"
        ],
        "description": "Returns active sessions (live refresh tokens) ordered newest-first, with the User-Agent + IP captured at issue time. Use the returned `id` to revoke individual sessions via DELETE /sessions/:id.",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/sessions/{id}": {
      "delete": {
        "summary": "Revoke one operator session by id. Idempotent.",
        "tags": [
          "Tenant · Auth"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/sign-out-everywhere": {
      "post": {
        "summary": "Revoke every refresh token for the calling operator (logout all devices)",
        "tags": [
          "Tenant · Auth"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/change-password": {
      "post": {
        "summary": "Authenticated password change. Revokes other sessions on success.",
        "tags": [
          "Tenant · Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "currentPassword",
                  "newPassword"
                ],
                "properties": {
                  "currentPassword": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "newPassword": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 256
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/invitations/preview": {
      "get": {
        "summary": "Preview an invitation by token (unauthenticated)",
        "tags": [
          "Tenant · Workspace"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            },
            "in": "query",
            "name": "token",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/invitations/accept": {
      "post": {
        "summary": "Accept an invitation. Returns a session scoped to the joined workspace.",
        "tags": [
          "Tenant · Workspace"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/workspace": {
      "get": {
        "summary": "Get the active workspace",
        "tags": [
          "Tenant · Workspace"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a new workspace for the current operator (becomes OWNER)",
        "tags": [
          "Tenant · Workspace"
        ],
        "description": "Lets a signed-in user spin up an additional Tenant without registering a new account. After creation, switch into it via POST /api/v1/tenant/auth/switch-workspace.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 80
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "patch": {
        "summary": "Rename the active workspace",
        "tags": [
          "Tenant · Workspace"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 80
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/workspace/members": {
      "get": {
        "summary": "List members of the active workspace",
        "tags": [
          "Tenant · Workspace"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/workspace/members/{id}": {
      "delete": {
        "summary": "Remove a member from the workspace",
        "tags": [
          "Tenant · Workspace"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "patch": {
        "summary": "Change a member's role",
        "tags": [
          "Tenant · Workspace"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MEMBER"
                    ]
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/workspace/invitations": {
      "get": {
        "summary": "List invitations for this workspace",
        "tags": [
          "Tenant · Workspace"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create an invitation. Returns a one-time-show token to share via the link.",
        "tags": [
          "Tenant · Workspace"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MEMBER"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/workspace/invitations/{id}": {
      "delete": {
        "summary": "Revoke a pending invitation",
        "tags": [
          "Tenant · Workspace"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications": {
      "get": {
        "summary": "List Applications in the active workspace",
        "tags": [
          "Tenant · Applications"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create an Application in the active workspace",
        "tags": [
          "Tenant · Applications"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "slug"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "billingProvider": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "paypal",
                      "razorpay"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/check-slug": {
      "get": {
        "summary": "Check if an Application slug is available",
        "tags": [
          "Tenant · Applications"
        ],
        "description": "Used by the \"Create application\" form for live availability feedback. Returns the same shape regardless — never leaks WHICH tenant owns a taken slug.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "in": "query",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}": {
      "get": {
        "summary": "Get one Application (must belong to the active workspace)",
        "tags": [
          "Tenant · Applications"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/auth-config": {
      "patch": {
        "summary": "Patch the auth configuration for an Application",
        "tags": [
          "Tenant · Applications"
        ],
        "description": "Toggle which auth methods are enabled (`password`, `magic_link`, …), set `signupEnabled=false` for invite-only apps, set the password minimum length, or update the redirect URL allowlist. OAuth provider availability is implicit from the per-provider oauth-config endpoints.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "methods": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 40
                    }
                  },
                  "passwordMinLength": {
                    "type": "integer",
                    "minimum": 8,
                    "maximum": 128
                  },
                  "redirectUrls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "organizationsEnabled": {
                    "type": "boolean"
                  },
                  "signupEnabled": {
                    "type": "boolean"
                  },
                  "mfa": {
                    "type": "string",
                    "enum": [
                      "off",
                      "optional",
                      "required"
                    ],
                    "description": "End-user 2FA policy."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/api-keys": {
      "get": {
        "summary": "List active API keys for an application",
        "tags": [
          "Tenant · API Keys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Mint an API key (raw shown once)",
        "tags": [
          "Tenant · API Keys"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ],
                    "default": "live"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/api-keys/{keyId}": {
      "delete": {
        "summary": "Revoke an API key",
        "tags": [
          "Tenant · API Keys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "keyId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/plans": {
      "get": {
        "summary": "List Plans",
        "tags": [
          "Tenant · Plans"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a Plan",
        "tags": [
          "Tenant · Plans"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slug",
                  "name",
                  "amount"
                ],
                "properties": {
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "amount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "interval": {
                    "type": "string",
                    "enum": [
                      "MONTH",
                      "YEAR"
                    ]
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "SUBSCRIPTION",
                      "LICENSE",
                      "USAGE",
                      "CREDIT"
                    ]
                  },
                  "licenseKind": {
                    "type": "string",
                    "enum": [
                      "PERPETUAL",
                      "TIMED",
                      "SEATS"
                    ]
                  },
                  "licenseSeatsAllowed": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "licenseDurationDays": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "meterSlug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "pricePerUnitCents": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "creditsAmount": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/plans/{slug}": {
      "patch": {
        "summary": "Toggle a Plan's active flag",
        "tags": [
          "Tenant · Plans"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "active"
                ],
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/coupons": {
      "get": {
        "summary": "List Coupons",
        "tags": [
          "Tenant · Coupons"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a Coupon",
        "tags": [
          "Tenant · Coupons"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "discountType",
                  "amountOff"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "discountType": {
                    "type": "string",
                    "enum": [
                      "PERCENT",
                      "AMOUNT"
                    ]
                  },
                  "amountOff": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "planSlugs": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "startsAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endsAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "maxRedemptions": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "maxRedemptionsPerUser": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/coupons/{code}": {
      "patch": {
        "summary": "Toggle a Coupon's active flag",
        "tags": [
          "Tenant · Coupons"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "active"
                ],
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "code",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/billing-credentials": {
      "get": {
        "summary": "List all billing providers configured for this Application",
        "tags": [
          "Tenant · Billing"
        ],
        "description": "Returns one entry per configured provider with its enabled flag, country list, and priority. Never returns the credentials themselves — those are write-only.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/billing-credentials/{provider}": {
      "put": {
        "summary": "Set or rotate BYO credentials for one provider",
        "tags": [
          "Tenant · Billing"
        ],
        "description": "Body shape depends on `provider`:\n  stripe   → { data: { apiKey, webhookSecret }, countries?, priority?, enabled? }\n  paypal   → { data: { clientId, clientSecret, webhookId }, countries?, priority?, enabled? }\n  razorpay → { data: { keyId, keySecret, webhookSecret }, countries?, priority?, enabled? }\n\nCredentials are AES-256-GCM encrypted at rest. There is no GET that returns plaintext.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "stripe",
                "paypal",
                "razorpay"
              ]
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "patch": {
        "summary": "Update routing or enabled flag for one provider (no secret rotation)",
        "tags": [
          "Tenant · Billing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "countries": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 2,
                      "maxLength": 2
                    }
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "test",
                      "live"
                    ]
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "stripe",
                "paypal",
                "razorpay"
              ]
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Remove credentials for one provider entirely",
        "tags": [
          "Tenant · Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "stripe",
                "paypal",
                "razorpay"
              ]
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/billing-credentials/{provider}/register-webhook": {
      "post": {
        "summary": "Auto-configure the provider webhook via its API (no manual paste)",
        "tags": [
          "Tenant · Billing"
        ],
        "description": "Creates the webhook endpoint at this Application's ReliPay URL and stores the returned signing secret (Stripe) / webhook id (PayPal) into the credentials. Save the provider credentials first. Razorpay is not supported — configure it manually.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "stripe",
                "paypal",
                "razorpay"
              ]
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/oauth-config/{provider}": {
      "put": {
        "summary": "Set or rotate an OAuth provider config (clientId + clientSecret + redirectUri)",
        "tags": [
          "Tenant · OAuth"
        ],
        "description": "clientSecret is encrypted at rest. Public bits (clientId, redirectUri, scopes, issuerUrl) live in `oauthConfig`. Built-in providers: google, github, microsoft, discord, gitlab, slack, oidc. For `oidc`, also pass `issuerUrl` (e.g. https://login.example.com) — endpoints are auto-discovered.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "clientId",
                  "clientSecret",
                  "redirectUri"
                ],
                "properties": {
                  "clientId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "clientSecret": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "redirectUri": {
                    "type": "string",
                    "format": "uri"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "issuerUrl": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Remove an OAuth provider config",
        "tags": [
          "Tenant · OAuth"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "provider",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-users": {
      "get": {
        "summary": "List recent end-users (for license issuance pickers etc.)",
        "tags": [
          "Tenant · End-users"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 254
            },
            "in": "query",
            "name": "search",
            "required": false
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "in": "query",
            "name": "limit",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create an end-user manually (operator-driven)",
        "tags": [
          "Tenant · End-users"
        ],
        "description": "Use this for support seeding / data migrations. The SDK's public sign-up endpoint is the normal path. Password is optional — if omitted, the user can only sign in via OAuth or via password-reset flow. Marks the email verified by default since an operator vouched.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "password": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 128
                  },
                  "role": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "emailVerified": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-users/{euid}": {
      "get": {
        "summary": "Get one end-user with their passkeys + recent impersonation audits",
        "tags": [
          "Tenant · End-users"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "patch": {
        "summary": "Patch an end-user (role, metadata, verified flag)",
        "tags": [
          "Tenant · End-users"
        ],
        "description": "Email is immutable (it's the natural key per-Application). To change a password use the password-reset flow. Pass `metadata: null` to clear; pass an object to overwrite — partial merges aren't supported because Json columns can't deep-merge atomically.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "metadata": {},
                  "emailVerified": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Delete an end-user (cascades to subs / refresh tokens / OAuth links / licenses)",
        "tags": [
          "Tenant · End-users"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-users/{euid}/credits": {
      "get": {
        "summary": "Get an end-user's credit balance + recent ledger",
        "tags": [
          "Tenant · Credits"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-users/{euid}/credits/grant": {
      "post": {
        "summary": "Manually grant / refund / adjust an end-user's credits",
        "tags": [
          "Tenant · Credits"
        ],
        "description": "Positive `amount` adds credits (GRANT / REFUND). Negative `amount` with reason ADJUST removes them (refused if it would overdraw). Idempotent on `idempotencyKey` when provided.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "integer"
                  },
                  "reason": {
                    "type": "string",
                    "enum": [
                      "GRANT",
                      "REFUND",
                      "ADJUST"
                    ]
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-users/{euid}/impersonate": {
      "post": {
        "summary": "Mint a short-lived impersonation token for an end-user (audited)",
        "tags": [
          "Tenant · End-users"
        ],
        "description": "Operator-as-user access token, 5-minute lifetime, no refresh. Every minting writes an `impersonation_audits` row. Use sparingly — every action taken with this token is attributed to the end-user in their own activity logs, with the operator id in the JWT `imp` claim for downstream attribution.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 280,
                    "description": "Short justification, captured in audit."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "euid",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-user-roles": {
      "get": {
        "summary": "List the role catalog for an Application",
        "tags": [
          "Tenant · End-users"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Add a role to the catalog",
        "tags": [
          "Tenant · End-users"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 40
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 240
                  },
                  "isDefault": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/end-user-roles/{name}": {
      "patch": {
        "summary": "Update a role (description, default flag)",
        "tags": [
          "Tenant · End-users"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": "string",
                    "maxLength": 240
                  },
                  "isDefault": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "name",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Delete a role; pass ?reassignTo=name to bulk-move users in one transaction",
        "tags": [
          "Tenant · End-users"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "in": "query",
            "name": "reassignTo",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "name",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/licenses": {
      "get": {
        "summary": "List licenses for an Application",
        "tags": [
          "Tenant · Licenses"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Issue a license to an end-user",
        "tags": [
          "Tenant · Licenses"
        ],
        "description": "Returns the raw key in `data.rawKey` — show ONCE. PERPETUAL/TIMED/SEATS kinds. Customer apps validate via POST /api/v1/licenses/verify.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endUserId",
                  "kind"
                ],
                "properties": {
                  "endUserId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "PERPETUAL",
                      "TIMED",
                      "SEATS"
                    ]
                  },
                  "planId": {
                    "type": "string"
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "seatsAllowed": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/licenses/{licenseId}": {
      "delete": {
        "summary": "Revoke a license",
        "tags": [
          "Tenant · Licenses"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "licenseId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/usage-meters": {
      "get": {
        "summary": "List usage meters",
        "tags": [
          "Tenant · Usage"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a usage meter",
        "tags": [
          "Tenant · Usage"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slug",
                  "name",
                  "unit"
                ],
                "properties": {
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "unit": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/usage-meters/{slug}": {
      "patch": {
        "summary": "Toggle a usage meter active/inactive",
        "tags": [
          "Tenant · Usage"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Permanently delete a usage meter (cascades to records)",
        "tags": [
          "Tenant · Usage"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/email-config": {
      "get": {
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/email-credentials": {
      "put": {
        "summary": "Set or rotate the Application's BYO Resend API key + sender",
        "tags": [
          "Tenant · Email"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "apiKey",
                  "fromAddress"
                ],
                "properties": {
                  "apiKey": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "fromAddress": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "fromName": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "replyTo": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Revert this Application to the default (or no) email transport",
        "tags": [
          "Tenant · Email"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/email-templates": {
      "get": {
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/email-templates/{eventKey}": {
      "get": {
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventKey",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "put": {
        "summary": "Upsert a customised template for one event",
        "tags": [
          "Tenant · Email"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventKey",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Revert one event to the built-in default template",
        "tags": [
          "Tenant · Email"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventKey",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/email-templates/{eventKey}/preview": {
      "post": {
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventKey",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/email-templates/{eventKey}/test-send": {
      "post": {
        "summary": "Render the template with sample values and send to a chosen address",
        "tags": [
          "Tenant · Email"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventKey",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/webhooks": {
      "get": {
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a webhook endpoint. Returns the signing secret once.",
        "tags": [
          "Tenant · Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/webhooks/{endpointId}": {
      "patch": {
        "summary": "Update an endpoint",
        "tags": [
          "Tenant · Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "endpointId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "summary": "Remove an endpoint",
        "tags": [
          "Tenant · Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "endpointId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/webhooks/{endpointId}/rotate-secret": {
      "post": {
        "summary": "Rotate the endpoint's signing secret. Returns the new value once.",
        "tags": [
          "Tenant · Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "endpointId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/webhooks/{endpointId}/deliveries": {
      "get": {
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "endpointId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/applications/{id}/webhooks/{endpointId}/deliveries/{deliveryId}/retry": {
      "post": {
        "summary": "Force a re-attempt of a failed delivery",
        "tags": [
          "Tenant · Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "endpointId",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "deliveryId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/mfa/status": {
      "get": {
        "summary": "MFA status for the operator",
        "tags": [
          "Tenant · MFA"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/mfa/setup": {
      "post": {
        "summary": "Mint a new TOTP secret + 10 backup codes",
        "tags": [
          "Tenant · MFA"
        ],
        "description": "Returns the otpauth URI for QR + backup codes (one-time-show). Not enrolled until /setup-confirm.",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/mfa/setup-confirm": {
      "post": {
        "summary": "Confirm enrollment with the current TOTP code",
        "tags": [
          "Tenant · MFA"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/mfa/disable": {
      "post": {
        "summary": "Disable MFA for the operator",
        "tags": [
          "Tenant · MFA"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/passkeys": {
      "get": {
        "summary": "List operator passkeys",
        "tags": [
          "Tenant · Passkeys"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/passkeys/register/start": {
      "post": {
        "summary": "Begin a registration ceremony for the current operator",
        "tags": [
          "Tenant · Passkeys"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/passkeys/register/complete": {
      "post": {
        "summary": "Complete a registration ceremony; stores the credential",
        "tags": [
          "Tenant · Passkeys"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "response",
                  "expectedChallenge"
                ],
                "properties": {
                  "response": {
                    "type": "object"
                  },
                  "expectedChallenge": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "deviceName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/passkeys/{id}": {
      "delete": {
        "summary": "Remove a passkey from the current operator",
        "tags": [
          "Tenant · Passkeys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/passkeys/authenticate/start": {
      "post": {
        "summary": "Begin a passkey sign-in ceremony for an operator (usernameless)",
        "tags": [
          "Tenant · Passkeys"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/tenant/auth/passkeys/authenticate/complete": {
      "post": {
        "summary": "Complete a passkey sign-in; mints a session",
        "tags": [
          "Tenant · Passkeys"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "response",
                  "expectedChallenge"
                ],
                "properties": {
                  "response": {
                    "type": "object"
                  },
                  "expectedChallenge": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/tenants": {
      "get": {
        "summary": "List all tenants",
        "tags": [
          "Admin · Tenants"
        ],
        "description": "Returns every Tenant in the system. Bootstrap-admin only.",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a tenant",
        "tags": [
          "Admin · Tenants"
        ],
        "description": "Creates a new Tenant. The first call also bootstraps the system. Subsequent Applications are created under POST /api/v1/admin/tenants/:id/applications.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "ownerEmail"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "ownerEmail": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/tenants/{id}": {
      "get": {
        "summary": "Get a tenant by id",
        "tags": [
          "Admin · Tenants"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications": {
      "get": {
        "summary": "List applications",
        "tags": [
          "Admin · Applications"
        ],
        "description": "Optionally filter by `tenantId`.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "tenantId",
            "required": false
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create an application under a tenant",
        "tags": [
          "Admin · Applications"
        ],
        "description": "Mints a unique slug, public key, and default auth/billing config. Use POST /api/v1/admin/applications/:id/api-keys to mint a secret key for the SDK.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tenantId",
                  "name",
                  "slug"
                ],
                "properties": {
                  "tenantId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "URL-safe lowercase identifier. Letters, digits, hyphens only."
                  },
                  "billingProvider": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "paypal",
                      "razorpay"
                    ]
                  },
                  "authConfig": {
                    "type": "object",
                    "properties": {
                      "methods": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "password",
                            "google",
                            "github",
                            "magic_link"
                          ]
                        }
                      },
                      "passwordMinLength": {
                        "type": "integer",
                        "minimum": 8
                      },
                      "redirectUrls": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uri"
                        }
                      },
                      "organizationsEnabled": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}": {
      "get": {
        "summary": "Get an application by id",
        "tags": [
          "Admin · Applications"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}/api-keys": {
      "get": {
        "summary": "List active API keys for an application",
        "tags": [
          "Admin · API Keys"
        ],
        "description": "Returns key metadata only. The raw key value is unrecoverable after creation.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Mint an API key for an application",
        "tags": [
          "Admin · API Keys"
        ],
        "description": "Returns the raw key in `data.rawKey`. **Show this to the operator exactly once** — we only store its SHA-256 hash, so the value is unrecoverable after this response.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "Human-readable label, e.g. \"CI server\", \"Staging worker\"."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ],
                    "default": "live",
                    "description": "`test` keys are intended for sandboxes; format is otherwise identical."
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Defaults to `[\"*\"]` (full access). Use `auth:read`, `billing:write`, etc., to scope."
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Optional ISO-8601 expiry. Omit for non-expiring keys."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}/api-keys/{keyId}": {
      "delete": {
        "summary": "Revoke an API key",
        "tags": [
          "Admin · API Keys"
        ],
        "description": "Soft-revokes the key (sets `revokedAt`). Idempotent.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "keyId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}/plans": {
      "get": {
        "summary": "List plans for an application",
        "tags": [
          "Admin · Plans"
        ],
        "parameters": [
          {
            "schema": {
              "type": "boolean"
            },
            "in": "query",
            "name": "includeInactive",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a plan",
        "tags": [
          "Admin · Plans"
        ],
        "description": "Creates a Plan locally, then registers it with the Application's billing provider. Amount is in the smallest currency unit (cents/paise/sen — never a decimal float).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slug",
                  "name",
                  "amount"
                ],
                "properties": {
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "amount": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Smallest currency unit (e.g. cents)."
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3,
                    "description": "ISO 4217. Defaults to USD."
                  },
                  "interval": {
                    "type": "string",
                    "enum": [
                      "MONTH",
                      "YEAR"
                    ]
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}/plans/{slug}": {
      "patch": {
        "summary": "Toggle a plan's active flag",
        "tags": [
          "Admin · Plans"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "active"
                ],
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "slug",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}/coupons": {
      "get": {
        "summary": "List coupons for an application",
        "tags": [
          "Admin · Coupons"
        ],
        "parameters": [
          {
            "schema": {
              "type": "boolean"
            },
            "in": "query",
            "name": "includeInactive",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "Create a coupon",
        "tags": [
          "Admin · Coupons"
        ],
        "description": "Discount kinds: PERCENT (amountOff in basis-points × 10, so 1500 = 15%) or AMOUNT (amountOff in smallest currency unit). Codes are stored lowercase.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "discountType",
                  "amountOff"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "discountType": {
                    "type": "string",
                    "enum": [
                      "PERCENT",
                      "AMOUNT"
                    ]
                  },
                  "amountOff": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "planSlugs": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "startsAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endsAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "maxRedemptions": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "maxRedemptionsPerUser": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/api/v1/admin/applications/{id}/coupons/{code}": {
      "patch": {
        "summary": "Toggle a coupon's active flag",
        "tags": [
          "Admin · Coupons"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "active"
                ],
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "code",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://api.relipay.dev",
      "description": "Production"
    },
    {
      "url": "http://localhost:3030",
      "description": "Local development"
    }
  ]
}