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 security: [] 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 security: [] requestBody: required: true content: application/x-www-form-urlencoded: schema: oneOf: - $ref: '#/components/schemas/AuthorizationCodeTokenRequest' - $ref: '#/components/schemas/RefreshTokenRequest' 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 security: [] 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 security: [] requestBody: required: true content: application/x-www-form-urlencoded: schema: $ref: '#/components/schemas/PasswordTokenRequest' responses: '200': description: Token response content: application/json: schema: $ref: '#/components/schemas/TokenResponse' /auth/refresh: post: summary: Refresh token security: [] requestBody: required: true content: application/x-www-form-urlencoded: schema: $ref: '#/components/schemas/RefreshTokenRequest' 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 security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ForgotPasswordRequest' responses: '204': description: Email sent /auth/password/reset: post: summary: Reset password security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResetPasswordRequest' responses: '204': description: Password reset /auth/email/verify: get: summary: Verify email security: [] parameters: - in: query name: token required: true schema: { type: string } - in: query name: email required: true schema: { type: string, format: email } 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) security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubscribeRequest' responses: '200': description: Pending subscription content: application/json: schema: $ref: '#/components/schemas/PendingSubscriptionResponse' /newsletter/confirm: get: summary: Confirm subscription (double opt-in) security: [] 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 security: [] 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/unsubscribe-token: post: summary: Issue unsubscribe token security: [] requestBody: required: true content: application/json: schema: type: object required: [list_id, email] properties: list_id: { type: string } email: { type: string, format: email } responses: '200': description: Unsubscribe token issued content: application/json: schema: type: object properties: unsubscribe_token: { type: string } /newsletter/preferences: get: summary: Get preferences parameters: - in: query name: list_id required: true schema: { type: string } - in: query name: email required: true 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: [list_id, email, preferences] properties: list_id: { type: string } email: { type: string, format: email } 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 } PasswordTokenRequest: type: object required: [grant_type, username, password] properties: grant_type: { type: string, enum: [password] } username: { type: string, format: email } password: { type: string } scope: { type: string } client_id: { type: string } AuthorizationCodeTokenRequest: type: object required: [grant_type, code, redirect_uri, code_verifier] properties: grant_type: { type: string, enum: [authorization_code] } code: { type: string } redirect_uri: { type: string } code_verifier: { type: string } client_id: { type: string } RefreshRequest: type: object required: [refresh_token, client_id] properties: refresh_token: { type: string } client_id: { type: string } RefreshTokenRequest: type: object required: [grant_type, refresh_token] properties: grant_type: { type: string, enum: [refresh_token] } refresh_token: { type: string } client_id: { type: string } ForgotPasswordRequest: type: object required: [email] properties: email: { type: string, format: email } ResetPasswordRequest: type: object required: [email, token, new_password] properties: email: { type: string, format: email } 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 } PendingSubscriptionResponse: allOf: - $ref: '#/components/schemas/Subscription' - type: object properties: confirm_token: { type: string } 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] }