member_center/docs/openapi.yaml

1077 lines
30 KiB
YAML

openapi: 3.1.0
info:
title: Member Center API
version: 0.1.0
description: OAuth2/OIDC + Newsletter Subscription API
servers:
- url: /
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'
- $ref: '#/components/schemas/ClientCredentialsTokenRequest'
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:
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'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/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'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/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'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/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 }
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/newsletter/one-click-unsubscribe-token:
post:
summary: Issue one-click unsubscribe token (Send Engine pre-send)
security: [{ BearerAuth: [] }]
description: Requires scope `newsletter:events.write` or `newsletter:events.write.global`.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueOneClickUnsubscribeTokenRequest'
responses:
'200':
description: Unsubscribe token issued
content:
application/json:
schema:
type: object
properties:
unsubscribe_token: { type: string }
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
'403':
description: Forbidden
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/newsletter/one-click-unsubscribe-tokens:
post:
summary: Issue one-click unsubscribe tokens in batch (Send Engine pre-send)
security: [{ BearerAuth: [] }]
description: Requires scope `newsletter:events.write` or `newsletter:events.write.global`.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueOneClickUnsubscribeTokensRequest'
responses:
'200':
description: Unsubscribe tokens issued
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/OneClickUnsubscribeTokenItem'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
'403':
description: Forbidden
/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'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
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'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/newsletter/subscriptions:
get:
summary: List subscriptions by list
security: [{ BearerAuth: [] }]
description: Requires scope `newsletter:list.read`.
parameters:
- in: query
name: list_id
required: true
schema: { type: string }
responses:
'200':
description: List
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Subscription'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/subscriptions/disable:
post:
summary: Disable subscription / blacklist by reason
security: [{ BearerAuth: [] }]
description: |
Requires scope `newsletter:events.write` (tenant client) or `newsletter:events.write.global` (platform client).
Request must include `tenant_id`, `subscriber_id`, and `list_id` for tenant-boundary validation.
Reason handling:
- `hard_bounce`, `soft_bounce_threshold`, `suppression`: unsubscribe all subscriptions for the email across tenants + add to global blacklist.
- `complaint`: unsubscribe only the target subscription, no blacklist.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DisableSubscriptionRequest'
responses:
'200':
description: Disabled
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/integrations/send-engine/webhook-clients/upsert:
post:
summary: Upsert Send Engine webhook client mapping for tenant
security: [{ BearerAuth: [] }]
description: Requires scope `newsletter:events.write.global`.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpsertSendEngineWebhookClientRequest'
responses:
'200':
description: Updated
'400':
description: Bad request
'401':
description: Unauthorized
'403':
description: Forbidden
'404':
description: Tenant not found
/webhooks/subscriptions:
post:
summary: (NOTE) Member Center -> Send Engine subscription events webhook
description: This endpoint is implemented by Send Engine; listed here as an integration note. Required headers are X-Signature, X-Timestamp, X-Nonce, X-Client-Id. X-Client-Id must be Send Engine `auth_clients.id` (UUID), tenant-bound, no fallback client id.
security: []
parameters:
- in: header
name: X-Signature
required: true
schema: { type: string }
- in: header
name: X-Timestamp
required: true
schema: { type: string }
- in: header
name: X-Nonce
required: true
schema: { type: string, format: uuid }
- in: header
name: X-Client-Id
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SendEngineSubscriptionEvent'
responses:
'200':
description: Accepted
/webhooks/lists/full-sync:
post:
summary: (NOTE) Member Center -> Send Engine full list sync webhook
description: This endpoint is implemented by Send Engine; listed here as an integration note. Required headers are X-Signature, X-Timestamp, X-Nonce, X-Client-Id. X-Client-Id must be Send Engine `auth_clients.id` (UUID), tenant-bound, no fallback client id.
security: []
parameters:
- in: header
name: X-Signature
required: true
schema: { type: string }
- in: header
name: X-Timestamp
required: true
schema: { type: string }
- in: header
name: X-Nonce
required: true
schema: { type: string, format: uuid }
- in: header
name: X-Client-Id
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SendEngineListFullSync'
responses:
'200':
description: Accepted
/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: /oauth/authorize
tokenUrl: /oauth/token
scopes:
openid: OpenID Connect
email: Email
profile: Basic profile
newsletter:list.read: Read newsletter subscriptions by list
newsletter:send.write: Create/send newsletter jobs
newsletter:send.read: Read newsletter send status
newsletter:events.read: Read newsletter events
newsletter:events.write: Write newsletter events (tenant scoped)
newsletter:events.write.global: Write newsletter events (platform scoped)
clientCredentials:
tokenUrl: /oauth/token
scopes:
newsletter:list.read: Read newsletter subscriptions by list
newsletter:send.write: Create/send newsletter jobs
newsletter:send.read: Read newsletter send status
newsletter:events.read: Read newsletter events
newsletter:events.write: Write newsletter events (tenant scoped)
newsletter:events.write.global: Write newsletter events (platform scoped)
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 }
ClientCredentialsTokenRequest:
type: object
required: [grant_type, client_id, client_secret]
properties:
grant_type: { type: string, enum: [client_credentials] }
client_id: { type: string }
client_secret: { type: string }
scope: { 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 }
DisableSubscriptionRequest:
type: object
required: [tenant_id, subscriber_id, list_id, reason, disabled_by, occurred_at]
properties:
tenant_id: { type: string, format: uuid }
subscriber_id: { type: string, format: uuid }
list_id: { type: string, format: uuid }
reason:
type: string
enum: [hard_bounce, soft_bounce_threshold, complaint, suppression]
disabled_by: { type: string }
occurred_at: { type: string, format: date-time }
IssueOneClickUnsubscribeTokenRequest:
type: object
required: [tenant_id, list_id, subscriber_id]
properties:
tenant_id: { type: string, format: uuid }
list_id: { type: string, format: uuid }
subscriber_id: { type: string, format: uuid }
IssueOneClickUnsubscribeTokensRequest:
type: object
required: [tenant_id, list_id, subscriber_ids]
properties:
tenant_id: { type: string, format: uuid }
list_id: { type: string, format: uuid }
subscriber_ids:
type: array
items: { type: string, format: uuid }
OneClickUnsubscribeTokenItem:
type: object
properties:
subscriber_id: { type: string, format: uuid }
unsubscribe_token: { type: string, nullable: true }
status: { type: string, enum: [issued, not_found, blacklisted] }
ErrorResponse:
type: object
required: [error, message, request_id]
properties:
error: { type: string }
message: { type: string }
request_id: { type: string }
SendEngineSubscriptionEvent:
type: object
required: [event_id, event_type, tenant_id, list_id, subscriber, occurred_at]
properties:
event_id: { type: string }
event_type: { type: string, enum: [subscription.activated, subscription.unsubscribed, preferences.updated] }
tenant_id: { type: string }
list_id: { type: string }
subscriber:
type: object
required: [id, email, status]
properties:
id: { type: string }
email: { type: string, format: email }
status: { type: string, enum: [pending, active, unsubscribed] }
preferences: { type: object }
occurred_at: { type: string, format: date-time }
SendEngineListFullSync:
type: object
required: [sync_id, batch_no, batch_total, tenant_id, list_id, subscribers, occurred_at]
properties:
sync_id: { type: string }
batch_no: { type: integer }
batch_total: { type: integer }
tenant_id: { type: string }
list_id: { type: string }
subscribers:
type: array
items:
type: object
required: [id, email, status]
properties:
id: { type: string }
email: { type: string, format: email }
status: { type: string, enum: [pending, active, unsubscribed] }
preferences: { type: object }
occurred_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 }
send_engine_webhook_client_id:
type: string
format: uuid
nullable: true
UpsertSendEngineWebhookClientRequest:
type: object
required: [tenant_id, webhook_client_id]
properties:
tenant_id: { type: string, format: uuid }
webhook_client_id: { type: string, format: uuid }
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
nullable: true
name: { type: string }
usage: { type: string, enum: [tenant_api, send_api, webhook_outbound, platform_service] }
redirect_uris: { type: array, items: { type: string } }
client_type: { type: string, enum: [public, confidential] }