openapi: 3.1.0 info: title: Send Engine API version: 0.1.0 description: | Send Engine external API and webhooks. servers: - url: https://send-engine.example.com security: - bearerAuth: [] paths: /api/send-jobs: post: summary: Create send job (legacy/internal) security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSendJobRequest' responses: '200': description: Created content: application/json: schema: $ref: '#/components/schemas/CreateSendJobResponse' '409': description: Conflict content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '422': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/send-jobs/{id}: get: summary: Get send job security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/SendJob' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/send-jobs/{id}/cancel: post: summary: Cancel send job security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/SendJobStatusResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /webhooks/subscriptions: post: summary: Member Center subscription events security: - webhookSignature: [] webhookTimestamp: [] webhookNonce: [] webhookClientId: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubscriptionEvent' responses: '200': description: Accepted '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: Duplicate event content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '422': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /webhooks/lists/full-sync: post: summary: Member Center full list sync security: - webhookSignature: [] webhookTimestamp: [] webhookNonce: [] webhookClientId: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FullSyncBatch' responses: '200': description: Accepted '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: Duplicate batch content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '422': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /webhooks/ses: post: summary: SES/SNS events security: - sesSignature: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SesEvent' responses: '200': description: OK '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT webhookSignature: type: apiKey in: header name: X-Signature webhookTimestamp: type: apiKey in: header name: X-Timestamp webhookNonce: type: apiKey in: header name: X-Nonce webhookClientId: type: apiKey in: header name: X-Client-Id sesSignature: type: apiKey in: header name: X-Amz-Sns-Signature schemas: CreateSendJobRequest: type: object required: [subject] description: | Request accepts both snake_case and camelCase keys. Recommended contract is snake_case for cross-language consistency. properties: tenant_id: type: string format: uuid tenantId: type: string format: uuid list_id: type: string format: uuid listId: type: string format: uuid name: type: string subject: type: string minLength: 1 body_html: type: string bodyHtml: type: string body_text: type: string bodyText: type: string template: type: object additionalProperties: true description: | Optional template metadata used by sender runtime. Supported keys: - unsubscribe_url: URL template, e.g. https://member.example/unsubscribe?token={{unsubscribe_token}} - ses_template_name: SES template name override scheduled_at: type: string format: date-time scheduledAt: type: string format: date-time window_start: type: string format: date-time windowStart: type: string format: date-time window_end: type: string format: date-time windowEnd: type: string format: date-time tracking: $ref: '#/components/schemas/TrackingOptions' allOf: - anyOf: - required: [list_id] - required: [listId] - anyOf: - required: [body_html] - required: [bodyHtml] - required: [body_text] - required: [bodyText] - required: [template] CreateSendJobResponse: type: object required: [send_job_id, sendJobId, status] properties: send_job_id: type: string format: uuid sendJobId: type: string format: uuid status: type: string enum: [pending, running, completed, failed, cancelled] SendJob: type: object required: [id, tenant_id, tenantId, list_id, listId, campaign_id, campaignId, status] properties: id: type: string format: uuid tenant_id: type: string format: uuid tenantId: type: string format: uuid list_id: type: string format: uuid listId: type: string format: uuid campaign_id: type: string format: uuid campaignId: type: string format: uuid status: type: string enum: [pending, running, completed, failed, cancelled] scheduled_at: type: string format: date-time scheduledAt: type: string format: date-time window_start: type: string format: date-time windowStart: type: string format: date-time window_end: type: string format: date-time windowEnd: type: string format: date-time SendJobStatusResponse: type: object required: [id, status] properties: id: type: string format: uuid status: type: string enum: [pending, running, completed, failed, cancelled] TrackingOptions: type: object properties: open: type: boolean click: type: boolean SubscriptionEvent: type: object required: [event_id, event_type, tenant_id, list_id, subscriber, occurred_at] properties: event_id: type: string format: uuid event_type: type: string enum: [subscription.activated, subscription.unsubscribed, preferences.updated] tenant_id: type: string format: uuid list_id: type: string format: uuid subscriber: $ref: '#/components/schemas/SubscriberPayload' occurred_at: type: string format: date-time SubscriberPayload: type: object required: [id, email, status] properties: id: type: string format: uuid email: type: string format: email status: type: string enum: [active, unsubscribed, bounced, complaint, suppressed] preferences: type: object additionalProperties: true FullSyncBatch: type: object required: [sync_id, batch_no, batch_total, tenant_id, list_id, subscribers, occurred_at] properties: sync_id: type: string format: uuid batch_no: type: integer minimum: 1 batch_total: type: integer minimum: 1 tenant_id: type: string format: uuid list_id: type: string format: uuid subscribers: type: array items: $ref: '#/components/schemas/SubscriberPayload' occurred_at: type: string format: date-time SesEvent: type: object required: [event_type, message_id, tenant_id, email, occurred_at] properties: event_type: type: string enum: [bounce, hard_bounced, soft_bounced, complaint, suppression, delivery, open, click] message_id: type: string tenant_id: type: string format: uuid email: type: string format: email bounce_type: type: string enum: [hard, soft] occurred_at: type: string format: date-time ErrorResponse: type: object required: [error] properties: error: type: string message: type: string reason: type: string request_id: type: string