openapi: 3.1.0 info: title: Member Center API version: 0.1.0 description: OAuth2/OIDC + Newsletter Subscription API servers: - url: /api security: - BearerAuth: [] paths: /oauth/authorize: get: summary: OAuth2 Authorization Endpoint description: Authorization Code + PKCE flow parameters: - in: query name: client_id required: true schema: { type: string } - in: query name: redirect_uri required: true schema: { type: string } - in: query name: response_type required: true schema: { type: string, enum: [code] } - in: query name: scope required: true schema: { type: string } - in: query name: code_challenge required: true schema: { type: string } - in: query name: code_challenge_method required: true schema: { type: string, enum: [S256] } - in: query name: state required: false schema: { type: string } responses: '302': description: Redirect to client with code /oauth/token: post: summary: OAuth2 Token Endpoint requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object required: [grant_type, code, redirect_uri, client_id, code_verifier] properties: grant_type: { type: string, enum: [authorization_code, refresh_token] } code: { type: string } redirect_uri: { type: string } client_id: { type: string } code_verifier: { type: string } refresh_token: { type: string } responses: '200': description: Token response content: application/json: schema: $ref: '#/components/schemas/TokenResponse' /.well-known/openid-configuration: get: summary: OIDC Discovery responses: '200': description: OIDC discovery document /.well-known/jwks.json: get: summary: JWKS responses: '200': description: JSON Web Key Set /auth/register: post: summary: Register user requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterRequest' responses: '200': description: Registered content: application/json: schema: $ref: '#/components/schemas/UserProfile' /auth/login: post: summary: API login requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LoginRequest' responses: '200': description: Token response content: application/json: schema: $ref: '#/components/schemas/TokenResponse' /auth/refresh: post: summary: Refresh token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RefreshRequest' responses: '200': description: Token response content: application/json: schema: $ref: '#/components/schemas/TokenResponse' /auth/logout: post: summary: Logout (revoke refresh token) security: - BearerAuth: [] requestBody: required: true content: application/json: schema: type: object properties: refresh_token: type: string responses: '204': description: Logged out /auth/password/forgot: post: summary: Request password reset requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ForgotPasswordRequest' responses: '204': description: Email sent /auth/password/reset: post: summary: Reset password requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResetPasswordRequest' responses: '204': description: Password reset /auth/email/verify: get: summary: Verify email parameters: - in: query name: token required: true schema: { type: string } responses: '200': description: Email verified /user/profile: get: summary: Get current user profile security: - BearerAuth: [] responses: '200': description: Profile content: application/json: schema: $ref: '#/components/schemas/UserProfile' /newsletter/subscribe: post: summary: Subscribe (unauthenticated allowed) requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubscribeRequest' responses: '200': description: Pending subscription content: application/json: schema: $ref: '#/components/schemas/Subscription' /newsletter/confirm: get: summary: Confirm subscription (double opt-in) parameters: - in: query name: token required: true schema: { type: string } responses: '200': description: Active subscription content: application/json: schema: $ref: '#/components/schemas/Subscription' /newsletter/unsubscribe: post: summary: Unsubscribe single list requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: { type: string } responses: '200': description: Unsubscribed content: application/json: schema: $ref: '#/components/schemas/Subscription' /newsletter/preferences: get: summary: Get preferences parameters: - in: query name: subscription_id required: false schema: { type: string } - in: query name: email required: false schema: { type: string, format: email } responses: '200': description: Preferences content: application/json: schema: $ref: '#/components/schemas/Subscription' post: summary: Update preferences requestBody: required: true content: application/json: schema: type: object required: [subscription_id, preferences] properties: subscription_id: { type: string } preferences: { type: object } responses: '200': description: Updated content: application/json: schema: $ref: '#/components/schemas/Subscription' /admin/tenants: get: summary: List tenants security: [{ BearerAuth: [] }] responses: '200': description: List post: summary: Create tenant security: [{ BearerAuth: [] }] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Tenant' responses: '201': description: Created /admin/tenants/{id}: get: summary: Get tenant security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: '200': description: Tenant put: summary: Update tenant security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Tenant' responses: '200': description: Updated delete: summary: Delete tenant security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: '204': description: Deleted /admin/newsletter-lists: get: summary: List newsletter lists security: [{ BearerAuth: [] }] responses: '200': description: List post: summary: Create list security: [{ BearerAuth: [] }] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NewsletterList' responses: '201': description: Created /admin/newsletter-lists/{id}: get: summary: Get list security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: '200': description: List put: summary: Update list security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NewsletterList' responses: '200': description: Updated delete: summary: Delete list security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: '204': description: Deleted /admin/oauth-clients: get: summary: List OAuth clients security: [{ BearerAuth: [] }] responses: '200': description: List post: summary: Create OAuth client security: [{ BearerAuth: [] }] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OAuthClient' responses: '201': description: Created /admin/oauth-clients/{id}: get: summary: Get OAuth client security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: '200': description: OAuth client put: summary: Update OAuth client security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OAuthClient' responses: '200': description: Updated delete: summary: Delete OAuth client security: [{ BearerAuth: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: '204': description: Deleted components: securitySchemes: OAuth2: type: oauth2 flows: authorizationCode: authorizationUrl: /api/oauth/authorize tokenUrl: /api/oauth/token scopes: openid: OpenID Connect email: Email profile: Basic profile BearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: TokenResponse: type: object properties: access_token: { type: string } refresh_token: { type: string } id_token: { type: string } token_type: { type: string, example: Bearer } expires_in: { type: integer } RegisterRequest: type: object required: [email, password] properties: email: { type: string, format: email } password: { type: string } LoginRequest: type: object required: [email, password, client_id] properties: email: { type: string, format: email } password: { type: string } client_id: { type: string } scope: { type: string } RefreshRequest: type: object required: [refresh_token, client_id] properties: refresh_token: { type: string } client_id: { type: string } ForgotPasswordRequest: type: object required: [email] properties: email: { type: string, format: email } ResetPasswordRequest: type: object required: [token, new_password] properties: token: { type: string } new_password: { type: string } UserProfile: type: object properties: id: { type: string } email: { type: string, format: email } email_verified: { type: boolean } created_at: { type: string, format: date-time } SubscribeRequest: type: object required: [list_id, email] properties: list_id: { type: string } email: { type: string, format: email } preferences: { type: object } source: { type: string } Subscription: type: object properties: id: { type: string } list_id: { type: string } email: { type: string, format: email } status: { type: string, enum: [pending, active, unsubscribed] } preferences: { type: object } created_at: { type: string, format: date-time } Tenant: type: object properties: id: { type: string } name: { type: string } domains: { type: array, items: { type: string } } status: { type: string } NewsletterList: type: object properties: id: { type: string } tenant_id: { type: string } name: { type: string } status: { type: string } OAuthClient: type: object properties: id: { type: string } tenant_id: { type: string } name: { type: string } redirect_uris: { type: array, items: { type: string } } client_type: { type: string, enum: [public, confidential] }